mkxp-freebird/src/graphics.cpp

750 lines
14 KiB
C++
Raw Normal View History

2013-09-01 14:27:21 +00:00
/*
** 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 "sharedstate.h"
2013-09-01 14:27:21 +00:00
#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 "perftimer.h"
2013-09-01 14:27:21 +00:00
#include "SDL2/SDL_video.h"
#include "SDL2/SDL_timer.h"
struct PingPong
{
2013-09-06 10:26:41 +00:00
TEXFBO rt[2];
2013-09-01 14:27:21 +00:00
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)
{
2013-09-06 10:26:41 +00:00
TEXFBO::init(rt[i]);
TEXFBO::allocEmpty(rt[i], screenW, screenH);
TEXFBO::linkFBO(rt[i]);
2013-09-01 14:27:21 +00:00
glClearColor(0, 0, 0, 1);
FBO::clear();
2013-09-01 14:27:21 +00:00
}
}
~PingPong()
{
for (int i = 0; i < 2; ++i)
2013-09-06 10:26:41 +00:00
TEXFBO::fini(rt[i]);
2013-09-01 14:27:21 +00:00
}
/* 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)
{
2013-09-06 10:26:41 +00:00
TEX::bind(rt[i].tex);
TEX::allocEmpty(width, height);
2013-09-01 14:27:21 +00:00
}
}
void startRender()
{
bind();
}
void swapRender()
{
swapIndices();
/* Discard dest buffer */
2013-09-06 10:26:41 +00:00
TEX::bind(rt[dstInd].tex);
TEX::allocEmpty(screenW, screenH);
2013-09-01 14:27:21 +00:00
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::bind(rt[srcInd].tex);
2013-09-01 14:27:21 +00:00
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),
actW(width), actH(height)
{
updateReso(width, height);
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
brightEffect = false;
2013-09-01 14:27:21 +00:00
brightnessQuad.setColor(Vec4());
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
}
void composite()
{
const int w = geometry.rect.w;
const int h = geometry.rect.h;
shState->prepareDraw();
2013-09-01 14:27:21 +00:00
pp.startRender();
glState.viewport.set(IntRect(0, 0, w, h));
2013-09-01 14:27:21 +00:00
FBO::clear();
2013-09-01 14:27:21 +00:00
Scene::composite();
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
if (brightEffect)
{
SimpleColorShader &shader = shState->simpleColorShader();
shader.bind();
shader.applyViewportProj();
shader.setTranslation(Vec2i());
2013-09-01 14:27:21 +00:00
brightnessQuad.draw();
}
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
pp.finishRender();
}
void requestViewportRender(Vec4 &c, Vec4 &f, Vec4 &t)
{
pp.swapRender();
pp.blitFBOs();
PlaneShader &shader = shState->planeShader();
2013-09-01 14:27:21 +00:00
shader.bind();
shader.setColor(c);
shader.setFlash(f);
shader.setTone(t);
shader.applyViewportProj();
shader.setTexSize(geometry.rect.size());
2013-09-01 14:27:21 +00:00
glState.blendMode.pushSet(BlendNone);
screenQuad.draw();
glState.blendMode.pop();
shader.unbind();
}
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
void setBrightness(float norm)
{
brightnessQuad.setColor(Vec4(0, 0, 0, 1.0 - norm));
brightEffect = norm < 1.0;
}
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
void updateReso(int width, int height)
{
geometry.rect.w = width;
geometry.rect.h = height;
screenQuad.setTexPosRect(geometry.rect, geometry.rect);
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
brightnessQuad.setTexPosRect(geometry.rect, geometry.rect);
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
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;
2013-10-06 07:55:27 +00:00
int actW, actH;
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
Quad brightnessQuad;
bool brightEffect;
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
};
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 GraphicsPrivate
{
2013-09-23 08:39:16 +00:00
/* Screen resolution, ie. the resolution at which
2013-09-23 09:18:20 +00:00
* RGSS renders at (settable with Graphics.resize_screen).
2013-09-23 08:39:16 +00:00
* Can only be changed from within RGSS */
2013-09-01 14:27:21 +00:00
Vec2i scRes;
2013-09-23 08:39:16 +00:00
/* Screen size, to which the rendered frames are scaled up.
* This can be smaller than the window size when fixed aspect
* ratio is enforced */
2013-09-01 14:27:21 +00:00
Vec2i scSize;
2013-09-23 08:39:16 +00:00
/* Actual physical size of the game window */
Vec2i winSize;
/* Offset in the game window at which the scaled game screen
* is blitted inside the game window */
Vec2i scOffset;
2013-09-01 14:27:21 +00:00
ScreenScene screen;
RGSSThreadData *threadData;
int frameRate;
int frameCount;
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
int brightness;
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
FPSLimiter fpsLimiter;
PerfTimer *gpuTimer;
PerfTimer *cpuTimer;
2013-09-01 14:27:21 +00:00
bool frozen;
2013-09-06 10:26:41 +00:00
TEXFBO frozenScene;
TEXFBO currentScene;
2013-09-01 14:27:21 +00:00
Quad screenQuad;
RBOFBO transBuffer;
2013-09-01 14:27:21 +00:00
GraphicsPrivate()
: scRes(640, 480),
scSize(scRes),
2013-09-23 08:39:16 +00:00
winSize(scRes),
2013-09-01 14:27:21 +00:00
screen(scRes.x, scRes.y),
frameRate(40),
frameCount(0),
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
brightness(255),
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
fpsLimiter(frameRate),
frozen(false)
{
gpuTimer = createGPUTimer(frameRate);
cpuTimer = createCPUTimer(frameRate);
2013-09-06 10:26:41 +00:00
TEXFBO::init(frozenScene);
TEXFBO::allocEmpty(frozenScene, scRes.x, scRes.y);
TEXFBO::linkFBO(frozenScene);
2013-09-01 14:27:21 +00:00
2013-09-06 10:26:41 +00:00
TEXFBO::init(currentScene);
TEXFBO::allocEmpty(currentScene, scRes.x, scRes.y);
TEXFBO::linkFBO(currentScene);
2013-09-01 14:27:21 +00:00
FloatRect screenRect(0, 0, scRes.x, scRes.y);
screenQuad.setTexPosRect(screenRect, screenRect);
RBOFBO::init(transBuffer);
RBOFBO::allocEmpty(transBuffer, scRes.x, scRes.y);
RBOFBO::linkFBO(transBuffer);
2013-09-01 14:27:21 +00:00
}
~GraphicsPrivate()
{
delete gpuTimer;
delete cpuTimer;
2013-09-06 10:26:41 +00:00
TEXFBO::fini(frozenScene);
TEXFBO::fini(currentScene);
2013-09-01 14:27:21 +00:00
RBOFBO::fini(transBuffer);
2013-09-01 14:27:21 +00:00
}
void updateScreenResoRatio()
{
Vec2 &ratio = shState->rtData().sizeResoRatio;
2013-09-01 14:27:21 +00:00
ratio.x = (float) scRes.x / scSize.x;
ratio.y = (float) scRes.y / scSize.y;
2013-09-23 08:39:16 +00:00
shState->rtData().screenOffset = scOffset;
2013-09-23 08:39:16 +00:00
}
/* Enforces fixed aspect ratio, if desired */
void recalculateScreenSize()
{
scSize = winSize;
2013-09-23 09:00:50 +00:00
if (!threadData->config.fixedAspectRatio)
2013-09-23 08:39:16 +00:00
{
scOffset = Vec2i(0, 0);
return;
}
float resRatio = (float) scRes.x / scRes.y;
float winRatio = (float) winSize.x / winSize.y;
if (resRatio > winRatio)
scSize.y = scSize.x / resRatio;
else if (resRatio < winRatio)
scSize.x = scSize.y * resRatio;
scOffset.x = (winSize.x - scSize.x) / 2.f;
scOffset.y = (winSize.y - scSize.y) / 2.f;
2013-09-01 14:27:21 +00:00
}
void checkResize()
{
2013-09-23 08:39:16 +00:00
if (threadData->windowSizeMsg.pollChange(&winSize.x, &winSize.y))
2013-09-01 14:27:21 +00:00
{
2013-09-23 08:39:16 +00:00
recalculateScreenSize();
2013-09-01 14:27:21 +00:00
screen.setScreenSize(scSize.x, scSize.y);
updateScreenResoRatio();
}
}
void shutdown()
{
threadData->rqTermAck = true;
shState->texPool().disable();
2013-09-01 14:27:21 +00:00
scriptBinding->terminate();
}
void swapGLBuffer()
{
SDL_GL_SwapWindow(threadData->window);
fpsLimiter.delay();
++frameCount;
threadData->ethread->notifyFrame();
2013-09-01 14:27:21 +00:00
}
void compositeToBuffer(FBO::ID fbo)
{
screen.composite();
FBO::bind(fbo, FBO::Draw);
FBO::blit(0, 0, 0, 0, scRes.x, scRes.y);
}
void blitBufferFlippedScaled()
{
2013-09-23 08:39:16 +00:00
FBO::blit(0, 0, scRes.x, scRes.y,
2013-09-23 09:00:50 +00:00
scOffset.x, scSize.y+scOffset.y, scSize.x, -scSize.y,
threadData->config.smoothScaling ? FBO::Linear : FBO::Nearest);
2013-09-01 14:27:21 +00:00
}
/* Blits currently bound read FBO to screen (upside-down) */
void blitToScreen()
{
FBO::unbind(FBO::Draw);
FBO::clear();
2013-09-01 14:27:21 +00:00
blitBufferFlippedScaled();
}
void redrawScreen()
{
screen.composite();
blitToScreen();
swapGLBuffer();
}
};
Graphics::Graphics(RGSSThreadData *data)
{
p = new GraphicsPrivate;
p->threadData = data;
}
Graphics::~Graphics()
{
delete p;
}
void Graphics::update()
{
shState->checkShutdown();
2013-09-01 14:27:21 +00:00
// p->cpuTimer->endTiming();
// p->gpuTimer->startTiming();
2013-09-01 14:27:21 +00:00
if (p->frozen)
return;
p->checkResize();
p->redrawScreen();
// p->gpuTimer->endTiming();
// p->cpuTimer->startTiming();
2013-09-01 14:27:21 +00:00
}
void Graphics::freeze()
{
p->frozen = true;
shState->checkShutdown();
2013-09-01 14:27:21 +00:00
p->checkResize();
/* Capture scene into frozen buffer */
p->compositeToBuffer(p->frozenScene.fbo);
}
void Graphics::transition(int duration,
const char *filename,
int vague)
{
2013-09-03 13:31:29 +00:00
vague = clamp(vague, 0, 512);
2013-09-01 14:27:21 +00:00
Bitmap *transMap = filename ? new Bitmap(filename) : 0;
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
2013-09-01 14:27:21 +00:00
setBrightness(255);
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
/* Capture new scene */
p->compositeToBuffer(p->currentScene.fbo);
/* If no transition bitmap is provided,
* we can use a simplified shader */
2013-09-01 14:27:21 +00:00
if (transMap)
{
TransShader &shader = shState->transShader();
2013-09-01 14:27:21 +00:00
shader.bind();
shader.applyViewportProj();
2013-09-01 14:27:21 +00:00
shader.setFrozenScene(p->frozenScene.tex);
shader.setCurrentScene(p->currentScene.tex);
shader.setTransMap(transMap->getGLTypes().tex);
shader.setVague(vague / 512.0f);
shader.setTexSize(p->scRes);
2013-09-01 14:27:21 +00:00
}
else
{
SimpleTransShader &shader = shState->sTransShader();
2013-09-01 14:27:21 +00:00
shader.bind();
shader.applyViewportProj();
2013-09-01 14:27:21 +00:00
shader.setFrozenScene(p->frozenScene.tex);
shader.setCurrentScene(p->currentScene.tex);
shader.setTexSize(p->scRes);
2013-09-01 14:27:21 +00:00
}
glState.blendMode.pushSet(BlendNone);
for (int i = 0; i < duration; ++i)
{
if (p->threadData->rqTerm)
{
delete transMap;
p->shutdown();
}
const float prog = i * (1.0 / duration);
if (transMap)
shState->transShader().setProg(prog);
2013-09-01 14:27:21 +00:00
else
shState->sTransShader().setProg(prog);
2013-09-01 14:27:21 +00:00
/* Draw the composed frame to a buffer first
* (we need this because we're skipping PingPong) */
FBO::bind(p->transBuffer.fbo, FBO::Draw);
FBO::clear();
2013-09-01 14:27:21 +00:00
p->screenQuad.draw();
p->checkResize();
/* Then blit it flipped and scaled to the screen */
2013-09-01 14:27:21 +00:00
FBO::bind(p->transBuffer.fbo, FBO::Read);
p->blitToScreen();
p->swapGLBuffer();
}
glState.blendMode.pop();
delete transMap;
p->frozen = false;
}
2013-10-06 07:55:27 +00:00
void Graphics::frameReset()
2013-09-01 14:27:21 +00:00
{
2013-10-06 07:55:27 +00:00
}
2013-09-01 14:27:21 +00:00
2013-10-06 07:55:27 +00:00
#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_SIMPLE(Graphics, FrameCount, int, p->frameCount)
void Graphics::setFrameRate(int value)
{
p->frameRate = clamp(value, 10, 120);
p->fpsLimiter.setDesiredFPS(p->frameRate);
2013-09-01 14:27:21 +00:00
}
2013-10-06 07:55:27 +00:00
#ifdef RGSS2
void Graphics::wait(int duration)
{
for (int i = 0; i < duration; ++i)
{
shState->checkShutdown();
2013-10-06 07:55:27 +00:00
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();
}
}
}
Bitmap *Graphics::snapToBitmap()
2013-09-01 14:27:21 +00:00
{
2013-10-06 07:55:27 +00:00
Bitmap *bitmap = new Bitmap(width(), height());
2013-09-01 14:27:21 +00:00
2013-10-06 07:55:27 +00:00
p->compositeToBuffer(bitmap->getGLTypes().fbo);
return bitmap;
2013-09-01 14:27:21 +00:00
}
int Graphics::width() const
{
return p->scRes.x;
}
int Graphics::height() const
{
return p->scRes.y;
}
void Graphics::resizeScreen(int width, int height)
{
2013-09-03 13:31:29 +00:00
width = clamp(width, 1, 640);
height = clamp(height, 1, 480);
2013-09-01 14:27:21 +00:00
Vec2i size(width, height);
if (p->scRes == size)
return;
shState->eThread().requestWindowResize(width, height);
2013-09-01 14:27:21 +00:00
p->scRes = size;
p->screen.setResolution(width, height);
2013-09-06 10:26:41 +00:00
TEX::bind(p->frozenScene.tex);
TEX::allocEmpty(width, height);
TEX::bind(p->currentScene.tex);
TEX::allocEmpty(width, height);
2013-09-01 14:27:21 +00:00
FloatRect screenRect(0, 0, width, height);
p->screenQuad.setTexPosRect(screenRect, screenRect);
RBO::bind(p->transBuffer.rbo);
RBO::allocEmpty(width, height);
2013-09-01 14:27:21 +00:00
p->updateScreenResoRatio();
}
DEF_ATTR_RD_SIMPLE(Graphics, Brightness, int, p->brightness)
void Graphics::setBrightness(int value)
{
2013-09-03 13:31:29 +00:00
value = clamp(value, 0, 255);
2013-09-01 14:27:21 +00:00
if (p->brightness == value)
return;
p->brightness = value;
p->screen.setBrightness(value / 255.0);
}
2013-10-06 07:55:27 +00:00
#endif
2013-09-01 14:27:21 +00:00
bool Graphics::getFullscreen() const
{
return p->threadData->ethread->getFullscreen();
}
void Graphics::setFullscreen(bool value)
{
p->threadData->ethread->requestFullscreenMode(value);
}
bool Graphics::getShowCursor() const
{
return p->threadData->ethread->getShowCursor();
}
void Graphics::setShowCursor(bool value)
{
p->threadData->ethread->requestShowCursor(value);
}
2013-09-01 14:27:21 +00:00
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)
{
shState->checkShutdown();
2013-09-01 14:27:21 +00:00
FBO::clear();
2013-09-01 14:27:21 +00:00
p->blitBufferFlippedScaled();
SDL_GL_SwapWindow(p->threadData->window);
p->fpsLimiter.delay();
p->threadData->ethread->notifyFrame();
2013-09-01 14:27:21 +00:00
}
}