mkxp-freebird/src/bitmap.cpp

776 lines
15 KiB
C++
Raw Normal View History

2013-09-01 14:27:21 +00:00
/*
** bitmap.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 "bitmap.h"
#include "SDL2/SDL.h"
#include "SDL2/SDL_image.h"
#include "SDL2/SDL_ttf.h"
#include "SDL2/SDL_rect.h"
#include "pixman.h"
2013-09-01 14:27:21 +00:00
#include "gl-util.h"
#include "quad.h"
#include "quadarray.h"
#include "exception.h"
#include "globalstate.h"
#include "glstate.h"
#include "texpool.h"
#include "shader.h"
#include "filesystem.h"
#include "font.h"
#include "eventthread.h"
#define DISP_CLASS_NAME "bitmap"
#define GUARD_MEGA \
{ \
if (p->megaSurface) \
throw Exception(Exception::MKXPError, \
"Operation not supported for mega surfaces"); \
}
2013-09-01 14:27:21 +00:00
struct BitmapPrivate
{
2013-09-06 10:26:41 +00:00
TEXFBO tex;
2013-09-01 14:27:21 +00:00
/* 'setPixel()' calls are cached and executed
* in batches on 'flush()' */
PointArray pointArray;
Font *font;
/* "Mega surfaces" are a hack to allow Tilesets to be used
* whose Bitmaps don't fit into a regular texture. They're
* kept in RAM and will throw an error if they're used in
* any context other than as Tilesets */
SDL_Surface *megaSurface;
/* The 'tainted' area describes which parts of the
* bitmap are not cleared, ie. don't have 0 opacity.
* If we're blitting / drawing text to a cleared part
* with full opacity, we can disregard any old contents
* in the texture and blit to it directly, saving
* ourselves the expensive blending calculation */
pixman_region16_t tainted;
2013-09-01 14:27:21 +00:00
BitmapPrivate()
: megaSurface(0)
2013-09-01 14:27:21 +00:00
{
font = &gState->defaultFont();
pixman_region_init(&tainted);
}
~BitmapPrivate()
{
pixman_region_fini(&tainted);
}
void clearTaintedArea()
{
pixman_region_clear(&tainted);
}
void addTaintedArea(const IntRect &rect)
{
pixman_region_union_rect
(&tainted, &tainted, rect.x, rect.y, rect.w, rect.h);
}
void substractTaintedArea(const IntRect &rect)
{
if (!touchesTaintedArea(rect))
return;
pixman_region16_t m_reg;
pixman_region_init_rect(&m_reg, rect.x, rect.y, rect.w, rect.h);
pixman_region_subtract(&tainted, &m_reg, &tainted);
pixman_region_fini(&m_reg);
}
bool touchesTaintedArea(const IntRect &rect)
{
pixman_box16_t box;
box.x1 = rect.x;
box.y1 = rect.y;
box.x2 = rect.x + rect.w;
box.y2 = rect.y + rect.h;
pixman_region_overlap_t result =
pixman_region_contains_rectangle(&tainted, &box);
return result != PIXMAN_REGION_OUT;
2013-09-01 14:27:21 +00:00
}
void bindTexture(ShaderBase &shader)
2013-09-01 14:27:21 +00:00
{
2013-09-06 10:26:41 +00:00
TEX::bind(tex.tex);
shader.setTexSize(Vec2i(tex.width, tex.height));
2013-09-01 14:27:21 +00:00
}
void bindFBO()
{
FBO::bind(tex.fbo, FBO::Draw);
2013-09-01 14:27:21 +00:00
}
void pushSetViewport(ShaderBase &shader) const
2013-09-01 14:27:21 +00:00
{
glState.viewport.pushSet(IntRect(0, 0, tex.width, tex.height));
shader.applyViewportProj();
2013-09-01 14:27:21 +00:00
}
void popViewport() const
{
glState.viewport.pop();
2013-09-01 14:27:21 +00:00
}
void blitQuad(Quad &quad)
{
glState.blendMode.pushSet(BlendNone);
quad.draw();
glState.blendMode.pop();
}
void flushPoints()
{
if (pointArray.count() == 0)
return;
SimpleColorShader &shader = gState->simpleColorShader();
shader.bind();
shader.setTranslation(Vec2i());
2013-09-01 14:27:21 +00:00
bindFBO();
pushSetViewport(shader);
2013-09-01 14:27:21 +00:00
glState.blendMode.pushSet(BlendNone);
pointArray.commit();
pointArray.draw();
pointArray.reset();
glState.blendMode.pop();
popViewport();
}
void fillRect(const IntRect &rect,
const Vec4 &color)
{
flushPoints();
bindFBO();
glState.scissorTest.pushSet(true);
glState.scissorBox.pushSet(rect);
glState.clearColor.pushSet(color);
glClear(GL_COLOR_BUFFER_BIT);
glState.clearColor.pop();
glState.scissorBox.pop();
glState.scissorTest.pop();
}
static void ensureFormat(SDL_Surface *&surf, Uint32 format)
{
if (surf->format->format == format)
return;
SDL_Surface *surfConv = SDL_ConvertSurfaceFormat(surf, format, 0);
SDL_FreeSurface(surf);
surf = surfConv;
}
};
Bitmap::Bitmap(const char *filename)
{
SDL_RWops ops;
gState->fileSystem().openRead(ops, filename, FileSystem::Image);
SDL_Surface *imgSurf = IMG_Load_RW(&ops, 1);
if (!imgSurf)
throw Exception(Exception::SDLError, "SDL: %s", SDL_GetError());
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);
if (imgSurf->w > glState.caps.maxTexSize || imgSurf->h > glState.caps.maxTexSize)
{
/* Mega surface */
p = new BitmapPrivate;
p->megaSurface = imgSurf;
}
else
{
/* Regular surface */
TEXFBO tex;
try
{
tex = gState->texPool().request(imgSurf->w, imgSurf->h);
}
catch (const Exception &e)
{
SDL_FreeSurface(imgSurf);
throw e;
}
p = new BitmapPrivate;
p->tex = tex;
2013-09-01 14:27:21 +00:00
TEX::bind(p->tex.tex);
TEX::uploadImage(p->tex.width, p->tex.height, imgSurf->pixels, GL_RGBA);
2013-09-01 14:27:21 +00:00
SDL_FreeSurface(imgSurf);
}
p->addTaintedArea(rect());
2013-09-01 14:27:21 +00:00
}
Bitmap::Bitmap(int width, int height)
{
if (width <= 0 || height <= 0)
throw Exception(Exception::RGSSError, "failed to create bitmap");
2013-09-06 10:26:41 +00:00
TEXFBO tex = gState->texPool().request(width, height);
2013-09-01 14:27:21 +00:00
p = new BitmapPrivate;
p->tex = tex;
2013-09-01 14:27:21 +00:00
clear();
}
Bitmap::Bitmap(const Bitmap &other)
{
p = new BitmapPrivate;
p->tex = gState->texPool().request(other.width(), other.height());
other.flush();
blt(0, 0, other, rect());
}
Bitmap::~Bitmap()
{
dispose();
}
int Bitmap::width() const
{
GUARD_DISPOSED;
if (p->megaSurface)
return p->megaSurface->w;
2013-09-01 14:27:21 +00:00
return p->tex.width;
}
int Bitmap::height() const
{
GUARD_DISPOSED;
if (p->megaSurface)
return p->megaSurface->h;
2013-09-01 14:27:21 +00:00
return p->tex.height;
}
IntRect Bitmap::rect() const
{
return IntRect(0, 0, width(), height());
}
void Bitmap::blt(int x, int y,
const Bitmap &source, const IntRect &rect,
int opacity)
{
stretchBlt(IntRect(x, y, rect.w, rect.h),
source, rect, opacity);
}
void Bitmap::stretchBlt(const IntRect &destRect,
const Bitmap &source, const IntRect &sourceRect,
int opacity)
2013-09-01 14:27:21 +00:00
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-03 13:31:29 +00:00
opacity = clamp(opacity, 0, 255);
2013-09-01 14:27:21 +00:00
if (opacity == 0)
{
return;
}
else if (opacity == 255 && !p->touchesTaintedArea(destRect))
{
/* Fast blit */
flush();
2013-09-01 14:27:21 +00:00
FBO::bind(source.p->tex.fbo, FBO::Read);
FBO::bind(p->tex.fbo, FBO::Draw);
2013-09-01 14:27:21 +00:00
FBO::blit(sourceRect.x, sourceRect.y, sourceRect.w, sourceRect.h,
destRect.x, destRect.y, destRect.w, destRect.h);
}
else
2013-09-01 14:27:21 +00:00
{
/* Fragment pipeline */
2013-09-01 14:27:21 +00:00
flush();
float normOpacity = (float) opacity / 255.0f;
2013-09-06 10:26:41 +00:00
TEXFBO &gpTex = gState->gpTexFBO(destRect.w, destRect.h);
2013-09-01 14:27:21 +00:00
FBO::bind(gpTex.fbo, FBO::Draw);
FBO::bind(p->tex.fbo, FBO::Read);
FBO::blit(destRect.x, destRect.y, 0, 0, destRect.w, destRect.h);
FloatRect bltSubRect((float) sourceRect.x / source.width(),
(float) sourceRect.y / source.height(),
((float) source.width() / sourceRect.w) * ((float) destRect.w / gpTex.width),
((float) source.height() / sourceRect.h) * ((float) destRect.h / gpTex.height));
BltShader &shader = gState->bltShader();
shader.bind();
shader.setDestination(gpTex.tex);
shader.setSubRect(bltSubRect);
shader.setOpacity(normOpacity);
Quad &quad = gState->gpQuad();
2013-09-01 14:27:21 +00:00
quad.setTexPosRect(sourceRect, destRect);
quad.setColor(Vec4(1, 1, 1, normOpacity));
source.p->bindTexture(shader);
2013-09-01 14:27:21 +00:00
p->bindFBO();
p->pushSetViewport(shader);
2013-09-01 14:27:21 +00:00
p->blitQuad(quad);
p->popViewport();
}
p->addTaintedArea(destRect);
2013-09-01 14:27:21 +00:00
modified();
}
void Bitmap::fillRect(int x, int y,
int width, int height,
const Vec4 &color)
{
fillRect(IntRect(x, y, width, height), color);
}
void Bitmap::fillRect(const IntRect &rect, const Vec4 &color)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
p->fillRect(rect, color);
if (color.w == 0)
/* Clear op */
p->substractTaintedArea(rect);
else
/* Fill op */
p->addTaintedArea(rect);
2013-09-01 14:27:21 +00:00
modified();
}
void Bitmap::gradientFillRect(int x, int y,
int width, int height,
const Vec4 &color1, const Vec4 &color2,
bool vertical)
{
gradientFillRect(IntRect(x, y, width, height), color1, color2, vertical);
}
void Bitmap::gradientFillRect(const IntRect &rect,
const Vec4 &color1, const Vec4 &color2,
bool vertical)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
flush();
SimpleColorShader &shader = gState->simpleColorShader();
shader.bind();
shader.setTranslation(Vec2i());
Quad &quad = gState->gpQuad();
2013-09-01 14:27:21 +00:00
if (vertical)
{
quad.vert[0].color = color2;
quad.vert[1].color = color2;
quad.vert[2].color = color1;
quad.vert[3].color = color1;
}
else
{
quad.vert[0].color = color1;
quad.vert[3].color = color1;
quad.vert[1].color = color2;
quad.vert[2].color = color2;
}
quad.setPosRect(rect);
p->bindFBO();
p->pushSetViewport(shader);
2013-09-01 14:27:21 +00:00
p->blitQuad(quad);
p->popViewport();
p->addTaintedArea(rect);
2013-09-01 14:27:21 +00:00
modified();
}
void Bitmap::clearRect(int x, int y, int width, int height)
{
clearRect(IntRect(x, y, width, height));
}
void Bitmap::clearRect(const IntRect &rect)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
p->fillRect(rect, Vec4());
modified();
}
void Bitmap::clear()
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
/* Any queued points won't be visible after this anyway */
p->pointArray.reset();
p->bindFBO();
glState.clearColor.pushSet(Vec4());
glClear(GL_COLOR_BUFFER_BIT);
glState.clearColor.pop();
p->clearTaintedArea();
2013-09-01 14:27:21 +00:00
modified();
}
Vec4 Bitmap::getPixel(int x, int y) const
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
if (x < 0 || y < 0 || x >= width() || y >= height())
return Vec4();
flush();
p->bindFBO();
glState.viewport.pushSet(IntRect(0, 0, width(), height()));
Vec4 pixel = FBO::getPixel(x, y);
2013-09-01 14:27:21 +00:00
glState.viewport.pop();
return pixel;
}
void Bitmap::setPixel(int x, int y, const Vec4 &color)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
p->pointArray.append(Vec2(x+.5, y+.5), color);
p->addTaintedArea(IntRect(x, y, 1, 1));
2013-09-01 14:27:21 +00:00
modified();
}
void Bitmap::hueChange(int hue)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
if ((hue % 360) == 0)
return;
flush();
2013-09-06 10:26:41 +00:00
TEXFBO newTex = gState->texPool().request(width(), height());
2013-09-01 14:27:21 +00:00
FloatRect texRect(rect());
Quad &quad = gState->gpQuad();
2013-09-01 14:27:21 +00:00
quad.setTexPosRect(texRect, texRect);
quad.setColor(Vec4(1, 1, 1, 1));
2013-09-01 14:27:21 +00:00
/* Calculate hue parameter */
hue = wrapRange(hue, 0, 359);
float hueAdj = -((M_PI * 2) / 360) * hue;
HueShader &shader = gState->hueShader();
shader.bind();
shader.setHueAdjust(hueAdj);
FBO::bind(newTex.fbo, FBO::Draw);
p->pushSetViewport(shader);
p->bindTexture(shader);
2013-09-01 14:27:21 +00:00
p->blitQuad(quad);
shader.unbind();
p->popViewport();
TEX::unbind();
2013-09-01 14:27:21 +00:00
gState->texPool().release(p->tex);
p->tex = newTex;
modified();
}
void Bitmap::drawText(int x, int y,
int width, int height,
const char *str, int align)
{
drawText(IntRect(x, y, width, height), str, align);
}
void Bitmap::drawText(const IntRect &rect, const char *str, int align)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
if (*str == '\0')
return;
flush();
TTF_Font *font = p->font->getSdlFont();
Color *fontColor = p->font->getColor();
2013-09-01 14:27:21 +00:00
SDL_Color c;
fontColor->toSDLColor(c);
float txtAlpha = fontColor->norm.w;
2013-09-01 14:27:21 +00:00
SDL_Surface *txtSurf;
if (gState->rtData().config.solidFonts)
txtSurf = TTF_RenderUTF8_Solid(font, str, c);
else
txtSurf = TTF_RenderUTF8_Blended(font, str, c);
p->ensureFormat(txtSurf, SDL_PIXELFORMAT_ARGB8888);
int alignX = rect.x;
switch (align)
{
default:
case Left :
break;
case Center :
alignX += (rect.w - txtSurf->w) / 2;
break;
case Right :
alignX += rect.w - txtSurf->w;
break;
}
if (alignX < rect.x)
alignX = rect.x;
int alignY = rect.y + (rect.h - txtSurf->h) / 2;
float squeeze = (float) rect.w / txtSurf->w;
if (squeeze > 1)
squeeze = 1;
FloatRect posRect(alignX, alignY, txtSurf->w * squeeze, txtSurf->h);
Vec2i gpTexSize;
2013-09-01 14:27:21 +00:00
gState->ensureTexSize(txtSurf->w, txtSurf->h, gpTexSize);
IntRect drawnRect = posRect;
bool fastBlit = !p->touchesTaintedArea(drawnRect) && txtAlpha == 1.0;
if (fastBlit)
{
if (squeeze == 1.0)
{
/* Even faster: upload directly to bitmap texture */
TEX::bind(p->tex.tex);
TEX::uploadSubImage(posRect.x, posRect.y, posRect.w, posRect.h, txtSurf->pixels, GL_RGBA);
}
else
{
/* Squeezing involved: need to use intermediary TexFBO */
TEXFBO &gpTF = gState->gpTexFBO(txtSurf->w, txtSurf->h);
TEX::bind(gpTF.tex);
TEX::uploadSubImage(0, 0, txtSurf->w, txtSurf->h, txtSurf->pixels, GL_RGBA);
FBO::bind(gpTF.fbo, FBO::Read);
p->bindFBO();
FBO::blit(0, 0, txtSurf->w, txtSurf->h,
posRect.x, posRect.y, posRect.w, posRect.h,
FBO::Linear);
}
}
else
2013-09-01 14:27:21 +00:00
{
/* Aquire a partial copy of the destination
* buffer we're about to render to */
2013-09-06 10:26:41 +00:00
TEXFBO &gpTex2 = gState->gpTexFBO(posRect.w, posRect.h);
2013-09-01 14:27:21 +00:00
FBO::bind(gpTex2.fbo, FBO::Draw);
FBO::bind(p->tex.fbo, FBO::Read);
FBO::blit(posRect.x, posRect.y, 0, 0, posRect.w, posRect.h);
FloatRect bltRect(0, 0,
(float) gpTexSize.x / gpTex2.width,
(float) gpTexSize.y / gpTex2.height);
2013-09-01 14:27:21 +00:00
BltShader &shader = gState->bltShader();
shader.bind();
shader.setTexSize(gpTexSize);
2013-09-01 14:27:21 +00:00
shader.setSource();
shader.setDestination(gpTex2.tex);
shader.setSubRect(bltRect);
shader.setOpacity(txtAlpha);
2013-09-01 14:27:21 +00:00
gState->bindTex();
TEX::uploadSubImage(0, 0, txtSurf->w, txtSurf->h, txtSurf->pixels, GL_BGRA_EXT);
TEX::setSmooth(true);
2013-09-01 14:27:21 +00:00
Quad &quad = gState->gpQuad();
quad.setTexRect(FloatRect(0, 0, txtSurf->w, txtSurf->h));
quad.setPosRect(posRect);
2013-09-01 14:27:21 +00:00
p->bindFBO();
p->pushSetViewport(shader);
2013-09-01 14:27:21 +00:00
glState.blendMode.pushSet(BlendNone);
2013-09-01 14:27:21 +00:00
quad.draw();
glState.blendMode.pop();
p->popViewport();
}
SDL_FreeSurface(txtSurf);
p->addTaintedArea(drawnRect);
2013-09-01 14:27:21 +00:00
modified();
}
IntRect Bitmap::textSize(const char *str)
{
GUARD_DISPOSED;
GUARD_MEGA;
2013-09-01 14:27:21 +00:00
TTF_Font *font = p->font->getSdlFont();
int w, h;
TTF_SizeUTF8(font, str, &w, &h);
if (p->font->getItalic() && strlen(str) == 1)
TTF_GlyphMetrics(font, *str, 0, 0, 0, 0, &w);
2013-09-01 14:27:21 +00:00
return IntRect(0, 0, w, h);
}
DEF_ATTR_SIMPLE(Bitmap, Font, Font*, p->font)
void Bitmap::flush() const
{
if (isDisposed())
return;
if (p->megaSurface)
return;
2013-09-01 14:27:21 +00:00
p->flushPoints();
}
2013-09-06 10:26:41 +00:00
TEXFBO &Bitmap::getGLTypes()
2013-09-01 14:27:21 +00:00
{
return p->tex;
}
SDL_Surface *Bitmap::megaSurface()
{
return p->megaSurface;
}
void Bitmap::ensureNonMega()
{
2013-09-24 20:42:10 +00:00
if (isDisposed())
return;
GUARD_MEGA;
}
void Bitmap::bindTex(ShaderBase &shader)
2013-09-01 14:27:21 +00:00
{
p->bindTexture(shader);
2013-09-01 14:27:21 +00:00
}
void Bitmap::releaseResources()
{
if (p->megaSurface)
SDL_FreeSurface(p->megaSurface);
else
gState->texPool().release(p->tex);
2013-09-01 14:27:21 +00:00
delete p;
}