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;