Implement a new tileset atlas layout to allow for bigger tilesets

The atlas packing algorithm has been reworked to pack autotiles
and tileset very efficiently into a texture, splitting the tileset
in multiple ways and eliminating the previous duplication of image
data in the atlas across "frames". Animation, which these frames
were designed for, is now done via duplicated buffer frames,
ie. each animation frame has its own VBO and IBO data. This was
not done to save on VRAM (hardly less memory is used), but to
make place for the new atlas layout.
Thanks to this new layout, even with a max texture size of 2048,
one can use tilesets with up to 15000 height. Of course, such
a tileset couldn't be stored in a regular Bitmap to begin with,
which is why I also introduced a hack called "mega surfaces":
software surfaces stored in RAM and wrapped inside a Bitmap,
whose sole purpose is to be passed to a Tilemap as tilesets.

Various other minor changes and fixes are included.
This commit is contained in:
Jonas Kulla 2013-09-23 22:21:58 +02:00
parent 6b2c892280
commit 10ec55e39b
9 changed files with 637 additions and 177 deletions

View File

@ -59,7 +59,8 @@ HEADERS += \
src/binding.h \
src/gl-util.h \
src/util.h \
src/config.h
src/config.h \
src/tileatlas.h
SOURCES += \
src/main.cpp \
@ -85,7 +86,8 @@ SOURCES += \
src/graphics.cpp \
src/debuglogger.cpp \
src/etc.cpp \
src/config.cpp
src/config.cpp \
src/tileatlas.cpp
EMBED = shader/transSimple.frag \
shader/trans.frag \

View File

@ -40,6 +40,13 @@
#define DISP_CLASS_NAME "bitmap"
#define GUARD_MEGA \
{ \
if (p->megaSurface) \
throw Exception(Exception::MKXPError, \
"Operation not supported for mega surfaces"); \
}
struct BitmapPrivate
{
TEXFBO tex;
@ -50,7 +57,14 @@ struct BitmapPrivate
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;
BitmapPrivate()
: megaSurface(0)
{
font = &gState->defaultFont();
}
@ -143,27 +157,37 @@ Bitmap::Bitmap(const char *filename)
if (!imgSurf)
throw Exception(Exception::SDLError, "SDL: %s", SDL_GetError());
TEXFBO tex;
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);
try
if (imgSurf->w > glState.caps.maxTexSize || imgSurf->h > glState.caps.maxTexSize)
{
tex = gState->texPool().request(imgSurf->w, imgSurf->h);
/* Mega surface */
p = new BitmapPrivate;
p->megaSurface = imgSurf;
}
catch (const Exception &e)
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;
TEX::bind(p->tex.tex);
TEX::uploadImage(p->tex.width, p->tex.height, imgSurf->pixels, GL_RGBA);
SDL_FreeSurface(imgSurf);
throw e;
}
p = new BitmapPrivate;
p->tex = tex;
TEX::bind(p->tex.tex);
TEX::uploadImage(p->tex.width, p->tex.height, imgSurf->pixels, GL_RGBA);
SDL_FreeSurface(imgSurf);
}
Bitmap::Bitmap(int width, int height)
@ -196,14 +220,20 @@ Bitmap::~Bitmap()
int Bitmap::width() const
{
GUARD_DISPOSED
GUARD_DISPOSED;
if (p->megaSurface)
return p->megaSurface->w;
return p->tex.width;
}
int Bitmap::height() const
{
GUARD_DISPOSED
GUARD_DISPOSED;
if (p->megaSurface)
return p->megaSurface->h;
return p->tex.height;
}
@ -227,6 +257,8 @@ void Bitmap::stretchBlt(const IntRect &destRect,
{
GUARD_DISPOSED;
GUARD_MEGA;
opacity = clamp(opacity, 0, 255);
if (opacity == 0)
@ -291,7 +323,9 @@ void Bitmap::fillRect(int x, int y,
void Bitmap::fillRect(const IntRect &rect, const Vec4 &color)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
p->fillRect(rect, color);
@ -310,7 +344,9 @@ void Bitmap::gradientFillRect(const IntRect &rect,
const Vec4 &color1, const Vec4 &color2,
bool vertical)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
flush();
@ -354,7 +390,9 @@ void Bitmap::clearRect(int x, int y, int width, int height)
void Bitmap::clearRect(const IntRect &rect)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
p->fillRect(rect, Vec4());
@ -363,7 +401,9 @@ void Bitmap::clearRect(const IntRect &rect)
void Bitmap::clear()
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
/* Any queued points won't be visible after this anyway */
p->pointArray.reset();
@ -383,6 +423,8 @@ Vec4 Bitmap::getPixel(int x, int y) const
{
GUARD_DISPOSED;
GUARD_MEGA;
if (x < 0 || y < 0 || x >= width() || y >= height())
return Vec4();
@ -399,7 +441,9 @@ Vec4 Bitmap::getPixel(int x, int y) const
void Bitmap::setPixel(int x, int y, const Vec4 &color)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
p->pointArray.append(Vec2(x+.5, y+.5), color);
@ -408,7 +452,9 @@ void Bitmap::setPixel(int x, int y, const Vec4 &color)
void Bitmap::hueChange(int hue)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
if ((hue % 360) == 0)
return;
@ -458,7 +504,9 @@ void Bitmap::drawText(int x, int y,
void Bitmap::drawText(const IntRect &rect, const char *str, int align)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
if (*str == '\0')
return;
@ -557,7 +605,9 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
IntRect Bitmap::textSize(const char *str)
{
GUARD_DISPOSED
GUARD_DISPOSED;
GUARD_MEGA;
TTF_Font *font = p->font->getSdlFont();
@ -577,6 +627,9 @@ void Bitmap::flush() const
if (isDisposed())
return;
if (p->megaSurface)
return;
p->flushPoints();
}
@ -585,6 +638,16 @@ TEXFBO &Bitmap::getGLTypes()
return p->tex;
}
SDL_Surface *Bitmap::megaSurface()
{
return p->megaSurface;
}
void Bitmap::ensureNonMega()
{
GUARD_MEGA;
}
void Bitmap::bindTex(ShaderBase &shader)
{
p->bindTexture(shader);
@ -592,6 +655,10 @@ void Bitmap::bindTex(ShaderBase &shader)
void Bitmap::releaseResources()
{
gState->texPool().release(p->tex);
if (p->megaSurface)
SDL_FreeSurface(p->megaSurface);
else
gState->texPool().release(p->tex);
delete p;
}

View File

@ -31,6 +31,7 @@
class Font;
class ShaderBase;
struct TEXFBO;
struct SDL_Surface;
struct BitmapPrivate;
// FIXME make this class use proper RGSS classes again
@ -104,6 +105,8 @@ public:
/* <internal> */
void flush() const;
TEXFBO &getGLTypes();
SDL_Surface *megaSurface();
void ensureNonMega();
/* Binds the backing texture and sets the correct
* texture size uniform in shader */

View File

@ -85,14 +85,14 @@ Plane::Plane(Viewport *viewport)
#define DISP_CLASS_NAME "plane"
DEF_ATTR_RD_SIMPLE(Plane, OX, int, p->ox)
DEF_ATTR_RD_SIMPLE(Plane, OY, int, p->oy)
DEF_ATTR_RD_SIMPLE(Plane, ZoomX, float, p->zoomX)
DEF_ATTR_RD_SIMPLE(Plane, ZoomY, float, p->zoomY)
DEF_ATTR_RD_SIMPLE(Plane, BlendType, int, p->blendType)
DEF_ATTR_RD_SIMPLE(Plane, Bitmap, Bitmap*, p->bitmap)
DEF_ATTR_RD_SIMPLE(Plane, OX, int, p->ox)
DEF_ATTR_RD_SIMPLE(Plane, OY, int, p->oy)
DEF_ATTR_RD_SIMPLE(Plane, ZoomX, float, p->zoomX)
DEF_ATTR_RD_SIMPLE(Plane, ZoomY, float, p->zoomY)
DEF_ATTR_RD_SIMPLE(Plane, BlendType, int, p->blendType)
DEF_ATTR_SIMPLE(Plane, Opacity, int, p->opacity)
DEF_ATTR_SIMPLE(Plane, Bitmap, Bitmap*, p->bitmap)
DEF_ATTR_SIMPLE(Plane, Color, Color*, p->color)
DEF_ATTR_SIMPLE(Plane, Tone, Tone*, p->tone)
@ -101,6 +101,15 @@ Plane::~Plane()
dispose();
}
void Plane::setBitmap(Bitmap *value)
{
GUARD_DISPOSED;
value->ensureNonMega();
p->bitmap = value;
}
void Plane::setOX(int value)
{
GUARD_DISPOSED
@ -205,6 +214,8 @@ void Plane::draw()
p->quad.draw();
TEX::setRepeat(false);
glState.blendMode.pop();
}
void Plane::onGeometryChange(const Scene::Geometry &geo)

View File

@ -152,6 +152,8 @@ void Sprite::setBitmap(Bitmap *bitmap)
if (p->bitmap == bitmap)
return;
bitmap->ensureNonMega();
p->bitmap = bitmap;
*p->srcRect = bitmap->rect();
p->onSrcRectChange();

181
src/tileatlas.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "tileatlas.h"
namespace TileAtlas
{
/* A Row represents a Rect
* with undefined width */
struct Row
{
int x, y, h;
Row(int x, int y, int h)
: x(x), y(y), h(h)
{}
};
typedef QList<Row> RowList;
/* Autotile area width */
static const int atAreaW = 96*4;
/* Autotile area height */
static const int atAreaH = 128*7;
/* Autotile area */
static const int atArea = atAreaW * atAreaH;
static const int tilesetW = 256;
static const int tsLaneW = tilesetW / 2;
static int freeArea(int width, int height)
{
return width * height - atArea;
}
Vec2i minSize(int tilesetH, int maxAtlasSize)
{
int width = atAreaW;
int height = atAreaH;
const int tsArea = tilesetW * tilesetH;
/* Expand vertically */
while (freeArea(width, height) < tsArea && height < maxAtlasSize)
height += 32;
if (freeArea(width, height) >= tsArea && height <= maxAtlasSize)
return Vec2i(width, height);
/* Expand horizontally */
while (freeArea(width, height) < tsArea && width < maxAtlasSize)
width += tsLaneW;
if (freeArea(width, height) >= tsArea && width <= maxAtlasSize)
return Vec2i(width, height);
return Vec2i(-1, -1);
}
static RowList calcSrcRows(int tilesetH)
{
RowList rows;
rows << Row(0, 0, tilesetH);
rows << Row(tsLaneW, 0, tilesetH);
return rows;
}
static RowList calcDstRows(int atlasW, int atlasH)
{
RowList rows;
/* Rows below the autotile area */
const int underAt = atlasH - atAreaH;
for (int i = 0; i < 3; ++i)
rows << Row(i*tsLaneW, atAreaH, underAt);
if (atlasW <= atAreaW)
return rows;
const int remRows = (atlasW - atAreaW) / tsLaneW;
for (int i = 0; i < remRows; ++i)
rows << Row(i*tsLaneW + atAreaW, 0, atlasH);
return rows;
}
static BlitList calcBlitsInt(RowList &srcRows, RowList &dstRows)
{
BlitList blits;
while (!srcRows.empty())
{
Row srcRow = srcRows.takeFirst();
Q_ASSERT(srcRow.h > 0);
while (!dstRows.empty() && srcRow.h > 0)
{
Row dstRow = dstRows.takeFirst();
if (srcRow.h > dstRow.h)
{
/* srcRow doesn't fully fit into dstRow */
blits << Blit(srcRow.x, srcRow.y,
dstRow.x, dstRow.y, dstRow.h);
srcRow.y += dstRow.h;
srcRow.h -= dstRow.h;
}
else if (srcRow.h < dstRow.h)
{
/* srcRow fits into dstRow with space remaining */
blits << Blit(srcRow.x, srcRow.y,
dstRow.x, dstRow.y, srcRow.h);
dstRow.y += srcRow.h;
dstRow.h -= srcRow.h;
dstRows.prepend(dstRow);
srcRow.h = 0;
}
else
{
/* srcRow fits perfectly into dstRow */
blits << Blit(srcRow.x, srcRow.y,
dstRow.x, dstRow.y, dstRow.h);
}
}
}
return blits;
}
BlitList calcBlits(int tilesetH, const Vec2i &atlasSize)
{
RowList srcRows = calcSrcRows(tilesetH);
RowList dstRows = calcDstRows(atlasSize.x, atlasSize.y);
return calcBlitsInt(srcRows, dstRows);
}
Vec2i tileToAtlasCoor(int tileX, int tileY, int tilesetH, int atlasH)
{
int laneX = tileX*32;
int laneY = tileY*32;
int longlaneH = atlasH;
int shortlaneH = longlaneH - atAreaH;
int longlaneOffset = shortlaneH * 3;
int laneIdx = 0;
int atlasY = 0;
/* Check if we're inside the 2nd lane */
if (laneX >= tsLaneW)
{
laneY += tilesetH;
laneX -= tsLaneW;
}
if (laneY < longlaneOffset)
{
/* Below autotile area */
laneIdx = laneY / shortlaneH;
atlasY = laneY % shortlaneH + atAreaH;
}
else
{
/* Right of autotile area */
int _y = laneY - longlaneOffset;
laneIdx = 3 + _y / longlaneH;
atlasY = _y % longlaneH;
}
int atlasX = laneIdx * tsLaneW + laneX;
return Vec2i(atlasX, atlasY);
}
}

45
src/tileatlas.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef TILEATLAS_H
#define TILEATLAS_H
#include "etc-internal.h"
#include <QList>
namespace TileAtlas
{
/* Abstract definition of a blit
* operation with undefined rect width */
struct Blit
{
Vec2i src;
Vec2i dst;
int h;
Blit(int sx, int sy,
int dx, int dy,
int h)
: src(sx, sy),
dst(dx, dy),
h(h)
{}
};
typedef QList<Blit> BlitList;
/* Calculates the minimum atlas size required to hold
* a tileset of height 'tilesetH'. If the required dimensions
* exceed 'maxAtlasSize', Vec2i(-1, -1) is returned. */
Vec2i minSize(int tilesetH, int maxAtlasSize);
/* Calculates a series of blits necessary to fill dstRows
* with srcRows without wasting any space */
BlitList calcBlits(int tilesetH, const Vec2i &atlasSize);
/* Translates a tile coordinate (not pixel!) to a physical
* pixel coordinate in the atlas */
Vec2i tileToAtlasCoor(int tileX, int tileY, int tilesetH, int atlasH);
}
#endif // TILEATLAS_H

View File

@ -32,6 +32,7 @@
#include "quadarray.h"
#include "texpool.h"
#include "quad.h"
#include "tileatlas.h"
#include "sigc++/connection.h"
@ -40,14 +41,23 @@
#include <QVector>
#include <SDL2/SDL_surface.h>
extern const StaticRect autotileRects[];
typedef QVector<SVertex> SVVector;
typedef struct { SVVector v[4]; } TileVBuffer;
static const int tilesetW = 8 * 32;
static const int autotileW = 3 * 32;
static const int autotileH = 4 * 32;
static const int atlasFrameW = tilesetW + autotileW;
static const int autotileCount = 7;
static const int atAreaW = autotileW * 4;
static const int atAreaH = autotileH * autotileCount;
static const int tsLaneW = tilesetW / 2;
/* Vocabulary:
*
@ -57,32 +67,57 @@ static const int atlasFrameW = tilesetW + autotileW;
* This means that we have to watch the 'modified' signals
* of all Bitmaps that make up the atlas, and update it
* as required during runtime.
* The atlas is made up of one or four 'atlas frames'.
* They look like this: ('AT' = Autotile)
* The atlas is tightly packed, with the autotiles located
* in the top left corener and the tileset image filing the
* remaining open space (below the autotiles as well as
* besides it). The tileset is vertically cut in half, where
* the first half fills available texture space, and then the
* other half (as if the right half was cut and pasted below
* the left half before fitting it all into the atlas).
* Internally these halves are called "tileset lanes".
*
* Atlas frame
* ----------------*--------
* | | |
* | | AT1 |
* | | |
* | | |
* | |-------|
* | Tileset | |
* | | AT2 |
* | | |
* | | |
* | |-------|
* | | |
* ...
* Tile atlas
* *-----------------------*--------------*
* | | | | | ¦ |
* | AT1 | AT1 | AT1 | AT1 | ¦ |
* | FR0 | FR1 | FR2 | FR3 | | ¦ | |
* |-----|-----|-----|-----| v ¦ v |
* | | | | | ¦ |
* | AT1 | | | | ¦ |
* | | | | | ¦ |
* |-----|-----|-----|-----| ¦ |
* |[...]| | | | ¦ |
* |-----|-----|-----|-----| ¦ |
* | | | | | | ¦ | |
* | AT7 | | | | v ¦ v |
* | | | | | ¦ |
* |-----|-----|-----|-----| ¦ |
* | ¦ ¦ ¦ ¦ |
* | Tile- ¦ | ¦ | ¦ ¦ |
* | set ¦ v ¦ v ¦ ¦ |
* | ¦ ¦ ¦ | ¦ | |
* | | ¦ ¦ ¦ v ¦ v |
* | v ¦ | ¦ | ¦ ¦ |
* | ¦ v ¦ v ¦ ¦ |
* | ¦ ¦ ¦ ¦ |
* *---------------------------------------*
*
* If none of the attached autotiles is animated, the
* atlas consists of only one such frame. In case of
* animated autotiles, it consists of four such frames,
* where each autotile area contains the respective
* animation frame of the autotile. Static autotiles
* and the tileset area are identical for all frames.
* This allows us to keep vertex data static and only
* change the x-offset in the atlas texture during animation.
* When allocating the atlas size, we first expand vertically
* until all the space immediately below the autotile area
* is used up, and then, when the max texture size
* is reached, horizontally.
*
* To animate the autotiles, we keep 4 buffers (packed into
* one big VBO and accessed using offsets) with vertex data
* corresponding to the respective animation frame. Likewise,
* the IBO is expanded to 4 times its usual size. In practice
* this means that all vertex data which does not stem from an
* animated autotile is duplicated across all 4 buffers.
* The range of one such buffer inside the VBO is called
* buffer frame, and tiles.bufferFrameSize * bufferIndex gives
* us the base offset into the IBO to access it.
* If there are no animated autotiles attached, we only use
* the first buffer.
*
* Elements:
* Even though the Tilemap carries similarities with other
@ -190,7 +225,7 @@ struct TilemapPrivate
Viewport *viewport;
Tilemap::Autotiles autotilesProxy;
Bitmap *autotiles[7];
Bitmap *autotiles[autotileCount];
Bitmap *tileset;
Table *mapData;
@ -207,14 +242,15 @@ struct TilemapPrivate
/* Tile atlas */
struct {
TEXFBO gl;
bool animated;
int frameH;
int width;
Vec2i size;
/* Indices of usable
* (not null, not disposed) autotiles */
QVector<uint8_t> usableATs;
/* Indices of animated autotiles */
QVector<uint8_t> animatedATs;
/* Animation state */
uint8_t frameIdx;
uint8_t aniIdx;
} atlas;
/* Map size in tiles */
@ -222,10 +258,10 @@ struct TilemapPrivate
int mapHeight;
/* Ground layer vertices */
SVVector groundVert;
TileVBuffer groundVert;
/* Scanrow vertices */
QVector<SVVector> scanrowVert;
QVector<TileVBuffer> scanrowVert;
/* Base quad indices of each scanrow
* in the shared buffer */
@ -237,6 +273,17 @@ struct TilemapPrivate
{
VAO::ID vao;
VBO::ID vbo;
bool animated;
/* Buffer count is either 1 or 4 */
uint8_t bufferCount;
/* Size of an IBO buffer frame, in bytes */
GLintptr bufferFrameSize;
/* Animation state */
uint8_t frameIdx;
uint8_t aniIdx;
} tiles;
/* Flash buffers */
@ -276,13 +323,13 @@ struct TilemapPrivate
/* Change watches */
sigc::connection tilesetCon;
sigc::connection autotilesCon[7];
sigc::connection autotilesCon[autotileCount];
sigc::connection mapDataCon;
sigc::connection prioritiesCon;
sigc::connection flashDataCon;
/* Dispose watches */
sigc::connection autotilesDispCon[7];
sigc::connection autotilesDispCon[autotileCount];
/* Draw prepare call */
sigc::connection prepareCon;
@ -295,6 +342,8 @@ struct TilemapPrivate
priorities(0),
visible(true),
tileYOffset(0),
mapWidth(0),
mapHeight(0),
replicas(Normal),
atlasSizeDirty(false),
atlasDirty(false),
@ -305,9 +354,11 @@ struct TilemapPrivate
{
memset(autotiles, 0, sizeof(autotiles));
atlas.animatedATs.reserve(7);
atlas.frameIdx = 0;
atlas.aniIdx = 0;
atlas.animatedATs.reserve(autotileCount);
tiles.animated = false;
tiles.bufferCount = 1;
tiles.frameIdx = 0;
tiles.aniIdx = 0;
/* Init tile buffers */
tiles.vbo = VBO::gen();
@ -367,7 +418,7 @@ struct TilemapPrivate
VBO::del(flash.vbo);
tilesetCon.disconnect();
for (int i = 0; i < 7; ++i)
for (int i = 0; i < autotileCount; ++i)
{
autotilesCon[i].disconnect();
autotilesDispCon[i].disconnect();
@ -379,6 +430,62 @@ struct TilemapPrivate
prepareCon.disconnect();
}
void updateAtlasInfo()
{
if (!tileset || tileset->isDisposed())
{
atlas.size = Vec2i();
return;
}
atlas.size = TileAtlas::minSize(tileset->height(), glState.caps.maxTexSize);
if (atlas.size.x < 0)
throw Exception(Exception::MKXPError,
"Cannot allocate big enough texture for tileset atlas");
}
void updateAutotileInfo()
{
/* Check if and which autotiles are animated */
QVector<uint8_t> &usableATs = atlas.usableATs;
QVector<uint8_t> &animatedATs = atlas.animatedATs;
usableATs.clear();
for (int i = 0; i < autotileCount; ++i)
{
if (!autotiles[i])
continue;
if (autotiles[i]->isDisposed())
continue;
usableATs.append(i);
autotiles[i]->flush();
if (autotiles[i]->width() > autotileW)
animatedATs.append(i);
}
tiles.animated = !animatedATs.empty();
tiles.bufferCount = animatedATs.empty() ? 1 : 4;
}
void updateMapDataInfo()
{
if (!mapData)
{
mapWidth = 0;
mapHeight = 0;
return;
}
mapWidth = mapData->xSize();
mapHeight = mapData->ySize();
}
void updateSceneGeometry(const Scene::Geometry &geo)
{
elem.sceneOffset.x = geo.rect.x - geo.xOrigin;
@ -388,12 +495,12 @@ struct TilemapPrivate
void updatePosition()
{
dispPos.x = -offset.x + elem.sceneOffset.x;
dispPos.y = -offset.y + elem.sceneOffset.y;
if (mapWidth == 0 || mapHeight == 0)
return;
dispPos.x = -offset.x + elem.sceneOffset.x;
dispPos.y = -offset.y + elem.sceneOffset.y;
dispPos.x %= mapWidth * 32;
dispPos.y %= mapHeight * 32;
}
@ -403,6 +510,10 @@ struct TilemapPrivate
void updateReplicas()
{
replicas = Normal;
if (mapWidth == 0 || mapHeight == 0)
return;
const IntRect &sRect = elem.sceneGeo.rect;
if (dispPos.x > sRect.x)
@ -453,34 +564,11 @@ struct TilemapPrivate
/* Allocates correctly sized TexFBO for atlas */
void allocateAtlas()
{
/* Check if and which autotiles are animated */
QVector<uint8_t> &animatedATs = atlas.animatedATs;
for (int i = 0; i < 7; ++i)
{
Bitmap *autotile = autotiles[i];
if (!autotile)
continue;
if (autotile->isDisposed())
continue;
autotile->flush();
if (autotile->width() > autotileW)
animatedATs.append(i);
}
atlas.animated = !animatedATs.empty();
atlas.frameH = max(tileset->height(), autotileH*7);
updateAtlasInfo();
/* Aquire atlas tex */
atlas.width = animatedATs.empty() ? atlasFrameW : atlasFrameW * 4;
gState->texPool().release(atlas.gl);
atlas.gl = gState->texPool().request(atlas.width, atlas.frameH);
atlas.gl = gState->texPool().request(atlas.size.x, atlas.size.y);
atlasDirty = true;
}
@ -490,7 +578,12 @@ struct TilemapPrivate
{
tileset->flush();
QVector<uint8_t> &animatedATs = atlas.animatedATs;
updateAutotileInfo();
Q_FOREACH (uint8_t i, atlas.usableATs)
autotiles[i]->flush();
TileAtlas::BlitList blits = TileAtlas::calcBlits(tileset->height(), atlas.size);
/* Clear atlas */
FBO::bind(atlas.gl.fbo, FBO::Draw);
@ -502,52 +595,64 @@ struct TilemapPrivate
glState.scissorTest.pop();
glState.clearColor.pop();
int tsW = tilesetW;
int tsH = tileset->height();
/* Assemble first frame with static content */
FBO::bind(tileset->getGLTypes().fbo, FBO::Read);
FBO::blit(0, 0, 0, 0, tsW, tsH);
for (int i = 0; i < 7; ++i)
/* Blit autotiles */
Q_FOREACH (uint8_t i, atlas.usableATs)
{
if (!autotiles[i])
continue;
if (autotiles[i]->isDisposed())
continue;
if (animatedATs.contains(i))
continue;
int blitW = min(autotiles[i]->width(), atAreaW);
int blitH = min(autotiles[i]->height(), atAreaH);
FBO::bind(autotiles[i]->getGLTypes().fbo, FBO::Read);
FBO::blit(0, 0, tsW, i*autotileH, autotileW, autotileH);
FBO::blit(0, 0, 0, i*autotileH, blitW, blitH);
}
/* If there aren't any animated autotiles, we're done */
if (!atlas.animated)
return;
/* Copy the first frame to the remaining 3 */
FBO::bind(atlas.gl.fbo, FBO::Read);
for (int i = 1; i < 4; ++i)
FBO::blit(0, 0, i*atlasFrameW, 0, atlasFrameW, atlas.frameH);
/* Finally, patch in the animated autotiles */
for (int i = 0; i < animatedATs.count(); ++i)
/* Blit tileset */
if (tileset->megaSurface())
{
int atInd = animatedATs[i];
Bitmap *at = autotiles[atInd];
/* Mega surface tileset */
FBO::unbind(FBO::Draw);
TEX::bind(atlas.gl.tex);
if (at->isDisposed())
continue;
SDL_Surface *tsSurf = tileset->megaSurface();
FBO::bind(at->getGLTypes().fbo, FBO::Read);
int bpp;
Uint32 rMask, gMask, bMask, aMask;
SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888,
&bpp, &rMask, &gMask, &bMask, &aMask);
for (int j = 0; j < 4; ++j)
FBO::blit(j*autotileW, 0, (j*atlasFrameW)+tsW, atInd*autotileH,
autotileW, autotileH);
for (int i = 0; i < blits.count(); ++i)
{
TileAtlas::Blit &blitOp = blits[i];
SDL_Surface *blitTemp =
SDL_CreateRGBSurface(0, tsLaneW, blitOp.h, bpp, rMask, gMask, bMask, aMask);
SDL_Rect tsRect;
tsRect.x = blitOp.src.x;
tsRect.y = blitOp.src.y;
tsRect.w = tsLaneW;
tsRect.h = blitOp.h;
SDL_Rect tmpRect = tsRect;
tmpRect.x = tmpRect.y = 0;
SDL_UpperBlit(tsSurf, &tsRect, blitTemp, &tmpRect);
TEX::uploadSubImage(blitOp.dst.x, blitOp.dst.y, tsLaneW, blitOp.h, blitTemp->pixels, GL_RGBA);
SDL_FreeSurface(blitTemp);
}
}
else
{
/* Regular tileset */
FBO::bind(tileset->getGLTypes().fbo, FBO::Read);
for (int i = 0; i < blits.count(); ++i)
{
TileAtlas::Blit &blitOp = blits[i];
FBO::blit(blitOp.src.x, blitOp.src.y, blitOp.dst.x, blitOp.dst.y, tsLaneW, blitOp.h);
}
}
}
@ -582,7 +687,7 @@ struct TilemapPrivate
return FloatRect(x, y, 16, 16);
}
void handleAutotile(int x, int y, int tileInd, SVVector &array)
void handleAutotile(int x, int y, int tileInd, TileVBuffer *array)
{
/* Which autotile [0-7] */
int atInd = tileInd / 48 - 1;
@ -591,20 +696,29 @@ struct TilemapPrivate
const StaticRect *pieceRect = &autotileRects[subInd*4];
/* Iterate over the 4 tile pieces */
for (int i = 0; i < 4; ++i)
{
FloatRect posRect = getAutotilePieceRect(x*32, y*32, i);
FloatRect texRect = pieceRect[i];
/* Adjust to atlas coordinates */
texRect.x += tilesetW;
texRect.y += atInd * autotileH;
SVertex v[4];
Quad::setTexPosRect(v, texRect, posRect);
for (int k = 0; k < tiles.bufferCount; ++k)
{
FloatRect _texRect = texRect;
for (int i = 0; i < 4; ++i)
array.append(v[i]);
if (atlas.animatedATs.contains(atInd))
_texRect.x += autotileW*k;
SVertex v[4];
Quad::setTexPosRect(v, _texRect, posRect);
/* Iterate over 4 vertices */
for (int i = 0; i < 4; ++i)
array->v[k].append(v[i]);
}
}
}
@ -622,7 +736,7 @@ struct TilemapPrivate
if (prio == -1)
return;
SVVector *targetArray;
TileVBuffer *targetArray;
/* Prio 0 tiles are all part of the same ground layer */
if (prio == 0)
@ -638,7 +752,7 @@ struct TilemapPrivate
/* Check for autotile */
if (tileInd < 48*8)
{
handleAutotile(x, y, tileInd, *targetArray);
handleAutotile(x, y, tileInd, targetArray);
return;
}
@ -646,19 +760,22 @@ struct TilemapPrivate
int tileX = tsInd % 8;
int tileY = tsInd / 8;
FloatRect texRect(tileX*32+.5, tileY*32+.5, 31, 31);
Vec2i texPos = TileAtlas::tileToAtlasCoor(tileX, tileY, tileset->height(), atlas.size.y);
FloatRect texRect((float) texPos.x+.5, (float) texPos.y+.5, 31, 31);
FloatRect posRect(x*32, y*32, 32, 32);
SVertex v[4];
Quad::setTexPosRect(v, texRect, posRect);
for (int i = 0; i < 4; ++i)
targetArray->append(v[i]);
for (int k = 0; k < tiles.bufferCount; ++k)
for (int i = 0; i < 4; ++i)
targetArray->v[k].append(v[i]);
}
void clearQuadArrays()
{
groundVert.clear();
for (int i = 0; i < 4; ++i)
groundVert.v[i].clear();
scanrowVert.clear();
scanrowBases.clear();
}
@ -667,8 +784,6 @@ struct TilemapPrivate
{
clearQuadArrays();
mapWidth = mapData->xSize();
mapHeight = mapData->ySize();
int mapDepth = mapData->zSize();
scanrowVert.resize(mapHeight + 5);
@ -695,40 +810,51 @@ struct TilemapPrivate
scanrowBases.resize(scanrowCount + 1);
/* Calculate total quad count */
int groundQuadCount = groundVert.count() / 4;
int groundQuadCount = groundVert.v[0].count() / 4;
int quadCount = groundQuadCount;
for (int i = 0; i < scanrowCount; ++i)
{
scanrowBases[i] = quadCount;
quadCount += scanrowVert[i].count() / 4;
quadCount += scanrowVert[i].v[0].count() / 4;
}
scanrowBases[scanrowCount] = quadCount;
int bufferFrameQuadCount = quadCount;
tiles.bufferFrameSize = quadCount * 6 * sizeof(uint32_t);
quadCount *= tiles.bufferCount;
VBO::bind(tiles.vbo);
VBO::allocEmpty(quadDataSize(quadCount));
VBO::uploadSubData(0, quadDataSize(groundQuadCount), groundVert.constData());
for (int i = 0; i < scanrowCount; ++i)
for (int k = 0; k < tiles.bufferCount; ++k)
{
VBO::uploadSubData(quadDataSize(scanrowBases[i]),
quadDataSize(scanrowSize(i)),
scanrowVert[i].constData());
VBO::uploadSubData(k*quadDataSize(bufferFrameQuadCount),
quadDataSize(groundQuadCount), groundVert.v[k].constData());
for (int i = 0; i < scanrowCount; ++i)
{
if (scanrowVert[i].v[0].empty())
continue;
VBO::uploadSubData(k*quadDataSize(bufferFrameQuadCount) + quadDataSize(scanrowBases[i]),
quadDataSize(scanrowSize(i)),
scanrowVert[i].v[k].constData());
}
}
VBO::unbind();
/* Ensure global IBO size */
gState->ensureQuadIBO(quadCount);
gState->ensureQuadIBO(quadCount*tiles.bufferCount);
}
void bindAtlas(SimpleShader &shader)
{
TEX::bind(atlas.gl.tex);
shader.setTexSize(Vec2i(atlas.animated ? atlasFrameW * 4 : atlasFrameW, atlas.frameH));
shader.setTexOffsetX(atlas.frameIdx * atlasFrameW);
shader.setTexSize(atlas.size);
}
Vec2i getReplicaOffset(Position pos)
@ -839,7 +965,7 @@ struct TilemapPrivate
/* Only generate elements for non-emtpy scanrows */
QVector<int> scanrowInd;
for (int i = 0; i < scanrowCount; ++i)
if (scanrowVert[i].count() > 0)
if (scanrowVert[i].v[0].count() > 0)
scanrowInd.append(i);
generateElements(scanrowInd);
@ -963,7 +1089,7 @@ void GroundLayer::draw()
void GroundLayer::drawInt()
{
glDrawElements(GL_TRIANGLES, vboCount,
GL_UNSIGNED_INT, 0);
GL_UNSIGNED_INT, (GLvoid*) (p->tiles.frameIdx * p->tiles.bufferFrameSize));
}
void GroundLayer::drawFlashInt()
@ -983,7 +1109,7 @@ ScanRow::ScanRow(TilemapPrivate *p, Viewport *viewport, int index)
index(index),
p(p)
{
vboOffset = p->scanrowBases[index] * sizeof(uint) * 6;
vboOffset = p->scanrowBases[index] * sizeof(uint32_t) * 6;
vboCount = p->scanrowSize(index) * 6;
}
@ -1017,7 +1143,7 @@ void ScanRow::draw()
void ScanRow::drawInt()
{
glDrawElements(GL_TRIANGLES, vboCount,
GL_UNSIGNED_INT, (const GLvoid*) vboOffset);
GL_UNSIGNED_INT, (GLvoid*) (vboOffset + p->tiles.frameIdx * p->tiles.bufferFrameSize));
}
void ScanRow::updateZ()
@ -1028,7 +1154,7 @@ void ScanRow::updateZ()
void Tilemap::Autotiles::set(int i, Bitmap *bitmap)
{
if (i < 0 || i > 6)
if (i < 0 || i > autotileCount-1)
return;
if (p->autotiles[i] == bitmap)
@ -1045,11 +1171,13 @@ void Tilemap::Autotiles::set(int i, Bitmap *bitmap)
p->autotilesDispCon[i].disconnect();
p->autotilesDispCon[i] = bitmap->wasDisposed.connect
(sigc::mem_fun(p, &TilemapPrivate::invalidateAtlasContents));
p->updateAutotileInfo();
}
Bitmap *Tilemap::Autotiles::get(int i) const
{
if (i < 0 || i > 6)
if (i < 0 || i > autotileCount-1)
return 0;
return p->autotiles[i];
@ -1074,20 +1202,25 @@ static const uchar atAnimation[16*4] =
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
};
static elementsN(atAnimation);
void Tilemap::update()
{
if (!p->tilemapReady)
return;
/* Animate flash */
if (++p->flash.alphaIdx > flashAlphaN-1)
if (++p->flash.alphaIdx >= flashAlphaN)
p->flash.alphaIdx = 0;
/* Animate autotiles */
if (!p->atlas.animated)
if (!p->tiles.animated)
return;
p->atlas.frameIdx = atAnimation[p->atlas.aniIdx];
p->tiles.frameIdx = atAnimation[p->tiles.aniIdx];
if (++p->atlas.aniIdx >= 16*4)
p->atlas.aniIdx = 0;
if (++p->tiles.aniIdx >= atAnimationN)
p->tiles.aniIdx = 0;
}
Tilemap::Autotiles &Tilemap::getAutotiles() const
@ -1137,6 +1270,8 @@ void Tilemap::setTileset(Bitmap *value)
p->tilesetCon.disconnect();
p->tilesetCon = value->modified.connect
(sigc::mem_fun(p, &TilemapPrivate::invalidateAtlasSize));
p->updateAtlasInfo();
}
void Tilemap::setMapData(Table *value)
@ -1152,6 +1287,9 @@ void Tilemap::setMapData(Table *value)
p->mapDataCon.disconnect();
p->mapDataCon = value->modified.connect
(sigc::mem_fun(p, &TilemapPrivate::invalidateBuffers));
p->updateMapDataInfo();
}
void Tilemap::setFlashData(Table *value)

View File

@ -694,10 +694,10 @@ void Window::update()
#define DISP_CLASS_NAME "window"
DEF_ATTR_SIMPLE(Window, Windowskin, Bitmap*, p->windowskin)
DEF_ATTR_SIMPLE(Window, X, int, p->position.x)
DEF_ATTR_SIMPLE(Window, Y, int, p->position.y)
DEF_ATTR_RD_SIMPLE(Window, Windowskin, Bitmap*, p->windowskin)
DEF_ATTR_RD_SIMPLE(Window, Contents, Bitmap*, p->contents)
DEF_ATTR_RD_SIMPLE(Window, Stretch, bool, p->bgStretch)
DEF_ATTR_RD_SIMPLE(Window, CursorRect, Rect*, p->cursorRect)
@ -711,9 +711,20 @@ DEF_ATTR_RD_SIMPLE(Window, Opacity, int, p->opacity)
DEF_ATTR_RD_SIMPLE(Window, BackOpacity, int, p->backOpacity)
DEF_ATTR_RD_SIMPLE(Window, ContentsOpacity, int, p->contentsOpacity)
void Window::setWindowskin(Bitmap *value)
{
GUARD_DISPOSED;
value->ensureNonMega();
p->windowskin = value;
}
void Window::setContents(Bitmap *value)
{
GUARD_DISPOSED
GUARD_DISPOSED;
value->ensureNonMega();
p->contents = value;
p->controlsVertDirty = true;