From 10ec55e39b1a7bf22f2ce8bb9b47e7292700c689 Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Mon, 23 Sep 2013 22:21:58 +0200 Subject: [PATCH] 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. --- mkxp.pro | 6 +- src/bitmap.cpp | 117 ++++++++++--- src/bitmap.h | 3 + src/plane.cpp | 23 ++- src/sprite.cpp | 2 + src/tileatlas.cpp | 181 ++++++++++++++++++++ src/tileatlas.h | 45 +++++ src/tilemap.cpp | 422 ++++++++++++++++++++++++++++++---------------- src/window.cpp | 15 +- 9 files changed, 637 insertions(+), 177 deletions(-) create mode 100644 src/tileatlas.cpp create mode 100644 src/tileatlas.h diff --git a/mkxp.pro b/mkxp.pro index 03a0085..dc3a469 100644 --- a/mkxp.pro +++ b/mkxp.pro @@ -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 \ diff --git a/src/bitmap.cpp b/src/bitmap.cpp index dcaa302..ff7c4eb 100644 --- a/src/bitmap.cpp +++ b/src/bitmap.cpp @@ -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; } diff --git a/src/bitmap.h b/src/bitmap.h index 0fa8cef..198d6b9 100644 --- a/src/bitmap.h +++ b/src/bitmap.h @@ -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: /* */ void flush() const; TEXFBO &getGLTypes(); + SDL_Surface *megaSurface(); + void ensureNonMega(); /* Binds the backing texture and sets the correct * texture size uniform in shader */ diff --git a/src/plane.cpp b/src/plane.cpp index 1ebc267..862e52a 100644 --- a/src/plane.cpp +++ b/src/plane.cpp @@ -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) diff --git a/src/sprite.cpp b/src/sprite.cpp index 5b52f74..a5a48aa 100644 --- a/src/sprite.cpp +++ b/src/sprite.cpp @@ -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(); diff --git a/src/tileatlas.cpp b/src/tileatlas.cpp new file mode 100644 index 0000000..d4c7c34 --- /dev/null +++ b/src/tileatlas.cpp @@ -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 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); +} + +} diff --git a/src/tileatlas.h b/src/tileatlas.h new file mode 100644 index 0000000..3dcb7d4 --- /dev/null +++ b/src/tileatlas.h @@ -0,0 +1,45 @@ +#ifndef TILEATLAS_H +#define TILEATLAS_H + +#include "etc-internal.h" + +#include + +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 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 diff --git a/src/tilemap.cpp b/src/tilemap.cpp index 2a58fd3..7197439 100644 --- a/src/tilemap.cpp +++ b/src/tilemap.cpp @@ -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 +#include + extern const StaticRect autotileRects[]; typedef QVector 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 usableATs; + /* Indices of animated autotiles */ QVector 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 scanrowVert; + QVector 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 &usableATs = atlas.usableATs; + QVector &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 &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 &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 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) diff --git a/src/window.cpp b/src/window.cpp index 51d1e66..01d5586 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -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;