diff --git a/doomsday/sdk/libcore/include/de/data/archive.h b/doomsday/sdk/libcore/include/de/data/archive.h index 2a5f124798..3ba0e034cb 100644 --- a/doomsday/sdk/libcore/include/de/data/archive.h +++ b/doomsday/sdk/libcore/include/de/data/archive.h @@ -14,7 +14,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #ifndef LIBDENG2_ARCHIVE_H @@ -248,7 +248,7 @@ class DENG2_PUBLIC Archive : public IWritable * Interface for derived classes: */ /// Base class for archive entries. - struct Entry : public PathTree::Node + struct DENG2_PUBLIC Entry : public PathTree::Node { dsize offset; ///< Offset from the start of the source array. dsize size; ///< Deserialized size. @@ -262,21 +262,11 @@ class DENG2_PUBLIC Archive : public IWritable /// Cached copy of the serialized data. Can be @c NULL. Entry has ownership. Block mutable *dataInArchive; - Entry(PathTree::NodeArgs const &args) : Node(args), - offset(0), - size(0), - sizeInArchive(0), - maybeChanged(false), - data(0), - dataInArchive(0) - {} - - virtual ~Entry() - { - // Entry has ownership of the cached data. - delete data; - delete dataInArchive; - } + Entry(PathTree::NodeArgs const &args); + virtual ~Entry(); + + // Must be constructed with args. + Entry() = delete; }; /** diff --git a/doomsday/sdk/libcore/include/de/data/path.h b/doomsday/sdk/libcore/include/de/data/path.h index 2c3bfeec45..4147ad36aa 100644 --- a/doomsday/sdk/libcore/include/de/data/path.h +++ b/doomsday/sdk/libcore/include/de/data/path.h @@ -106,6 +106,8 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base */ hash_type hash() const; + bool hasWildCard() const; + /** * Case insensitive equality test. * @@ -136,8 +138,11 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base friend class Path; friend struct Path::Instance; + enum Flag { GotHashKey = 0x1, WildCardChecked = 0x2, IncludesWildCard = 0x4 }; + Q_DECLARE_FLAGS(Flags, Flag) + private: - mutable bool gotHashKey; + mutable Flags flags; mutable hash_type hashKey; QStringRef range; }; @@ -457,6 +462,8 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base Instance *d; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Path::Segment::Flags) + /** * Utility class for specifying paths that use a dot (.) as the path separator. * @ingroup data diff --git a/doomsday/sdk/libcore/include/de/data/pathtree.h b/doomsday/sdk/libcore/include/de/data/pathtree.h index 68ba561cfe..dc43cc0cae 100644 --- a/doomsday/sdk/libcore/include/de/data/pathtree.h +++ b/doomsday/sdk/libcore/include/de/data/pathtree.h @@ -14,7 +14,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #ifndef LIBDENG2_PATHTREE_H diff --git a/doomsday/sdk/libcore/include/de/filesys/file.h b/doomsday/sdk/libcore/include/de/filesys/file.h index cbcce4b5e5..ab0bbec376 100644 --- a/doomsday/sdk/libcore/include/de/filesys/file.h +++ b/doomsday/sdk/libcore/include/de/filesys/file.h @@ -111,10 +111,10 @@ class DENG2_PUBLIC File : public filesys::Node, public IIOStream, public IObject }; public: - Status(dsize s = 0, Time const &modTime = Time()) + Status(dsize s = 0, Time const &modTime = Time::invalidTime()) : size(s), modifiedAt(modTime), _type(FILE) {} - Status(Type t, dsize s = 0, Time const &modTime = Time()) + Status(Type t, dsize s = 0, Time const &modTime = Time::invalidTime()) : size(s), modifiedAt(modTime), _type(t) {} Type type() const { return _type; } diff --git a/doomsday/sdk/libcore/src/core/log.cpp b/doomsday/sdk/libcore/src/core/log.cpp index 94e30bcdd2..5e5f50b52f 100644 --- a/doomsday/sdk/libcore/src/core/log.cpp +++ b/doomsday/sdk/libcore/src/core/log.cpp @@ -588,7 +588,7 @@ Log::Section::~Section() DENG2_PIMPL_NOREF(Log) { - typedef QList SectionStack; + typedef QVector SectionStack; SectionStack sectionStack; LogEntry *throwawayEntry; duint32 currentEntryMedata; ///< Applies to the current entry being staged in the thread. @@ -630,13 +630,13 @@ bool Log::isStaging() const void Log::beginSection(char const *name) { - d->sectionStack.append(name); + d->sectionStack.push_back(name); } void Log::endSection(char const *DENG2_DEBUG_ONLY(name)) { DENG2_ASSERT(d->sectionStack.back() == name); - d->sectionStack.takeLast(); + d->sectionStack.pop_back(); } void Log::beginInteractive() diff --git a/doomsday/sdk/libcore/src/data/archive.cpp b/doomsday/sdk/libcore/src/data/archive.cpp index 9771b245ba..dab994e002 100644 --- a/doomsday/sdk/libcore/src/data/archive.cpp +++ b/doomsday/sdk/libcore/src/data/archive.cpp @@ -192,7 +192,7 @@ Block &Archive::entryBlock(Path const &path) // Mark for recompression. Entry &entry = static_cast(d->index->find(path, PathTree::MatchFull | PathTree::NoBranch)); entry.maybeChanged = true; - entry.modifiedAt = Time(); + entry.modifiedAt = Time::currentHighPerformanceTime(); d->modified = true; @@ -215,7 +215,7 @@ void Archive::add(Path const &path, IByteArray const &data) Entry &entry = static_cast(d->index->insert(path)); entry.data = new Block(data); - entry.modifiedAt = Time(); + entry.modifiedAt = Time::currentHighPerformanceTime(); entry.maybeChanged = true; // The rest of the data gets updated when the archive is written. @@ -269,4 +269,22 @@ PathTree const &Archive::index() const return *d->index; } +Archive::Entry::Entry(PathTree::NodeArgs const &args) + : Node(args) + , offset(0) + , size(0) + , sizeInArchive(0) + , modifiedAt(Time::invalidTime()) + , maybeChanged(false) + , data(0) + , dataInArchive(0) +{} + +Archive::Entry::~Entry() +{ + // Entry has ownership of the cached data. + delete data; + delete dataInArchive; +} + } // namespace de diff --git a/doomsday/sdk/libcore/src/data/block.cpp b/doomsday/sdk/libcore/src/data/block.cpp index 049111f1a5..2b66913ff7 100644 --- a/doomsday/sdk/libcore/src/data/block.cpp +++ b/doomsday/sdk/libcore/src/data/block.cpp @@ -20,6 +20,8 @@ #include "de/Block" #include "de/File" +#include + using namespace de; Block::Block(Size initialSize) @@ -79,10 +81,7 @@ void Block::get(Offset atPos, Byte *values, Size count) const String("(%1[+%2] > %3)").arg(atPos).arg(count).arg(size())); } - for (Offset i = atPos; count > 0; ++i, --count) - { - *values++ = Byte(at(i)); - } + std::memcpy(values, constData() + atPos, count); } void Block::set(Offset at, Byte const *values, Size count) @@ -92,7 +91,7 @@ void Block::set(Offset at, Byte const *values, Size count) /// @throw OffsetError The accessed region of the block was out of range. throw OffsetError("Block::set", "Out of range"); } - replace(at, count, QByteArray((char const *) values, count)); + replace(at, count, QByteArray(reinterpret_cast(values), count)); } void Block::copyFrom(IByteArray const &array, Offset at, Size count) diff --git a/doomsday/sdk/libcore/src/data/path.cpp b/doomsday/sdk/libcore/src/data/path.cpp index a19e23095c..5afdec0e24 100644 --- a/doomsday/sdk/libcore/src/data/path.cpp +++ b/doomsday/sdk/libcore/src/data/path.cpp @@ -36,7 +36,7 @@ Path::hash_type const Path::hash_range = 512; ushort Path::Segment::hash() const { // Is it time to compute the hash? - if (!gotHashKey) + if (!(flags & GotHashKey)) { hashKey = 0; int op = 0; @@ -51,11 +51,23 @@ ushort Path::Segment::hash() const } } hashKey %= hash_range; - gotHashKey = true; + flags |= GotHashKey; } return hashKey; } +bool Path::Segment::hasWildCard() const +{ + if (flags & WildCardChecked) + { + return flags.testFlag(IncludesWildCard); + } + bool isWild = toStringRef().contains(QChar('*')); + applyFlagOperation(flags, IncludesWildCard, isWild? SetFlags : UnsetFlags); + flags |= WildCardChecked; + return isWild; +} + bool Path::Segment::operator == (Path::Segment const &other) const { return !range.compare(other.range, Qt::CaseInsensitive); diff --git a/doomsday/sdk/libcore/src/data/pathtreenode.cpp b/doomsday/sdk/libcore/src/data/pathtreenode.cpp index e13e51b9e3..1309acfc77 100644 --- a/doomsday/sdk/libcore/src/data/pathtreenode.cpp +++ b/doomsday/sdk/libcore/src/data/pathtreenode.cpp @@ -50,6 +50,11 @@ DENG2_PIMPL_NOREF(PathTree::Node) { delete children; } + + void cacheSegmentText() + { + segmentText = &tree.segmentName(segmentId); + } }; PathTree::Node::Node(PathTree::NodeArgs const &args) : d(nullptr) @@ -127,7 +132,7 @@ String const &PathTree::Node::name() const // Cache the string, because PathTree::segmentName() locks the tree and that has // performance implications. The segment text string will not change while the // node exists. - d->segmentText = &tree().segmentName(d->segmentId); + d->cacheSegmentText(); } return *d->segmentText; } @@ -143,91 +148,95 @@ static int matchName(QChar const *string, dsize stringSize, { QChar const *in = string; QChar const *inEnd = string + stringSize; - QChar const *st = pattern; + QChar const *pat = pattern; while (in < inEnd) { - if (*st == QChar('*')) + if (*pat == QChar('*')) { - st++; + pat++; continue; } - if (*st != QChar('?') && (st->toLower() != in->toLower())) + if (/**st != QChar('?') && */ pat->toLower() != in->toLower()) { // A mismatch. Hmm. Go back to a previous '*'. - while (st >= pattern && *st != QChar('*')) { st--; } + while (pat >= pattern && *pat != QChar('*')) { --pat; } // No match? - if (st < pattern) return false; + if (pat < pattern) return false; // The asterisk lets us continue. } // This character of the pattern is OK. - st++; + pat++; in++; } // Skip remaining asterisks. - while (*st == QChar('*')) { st++; } + while (*pat == QChar('*')) { pat ++; } // Match is good if the end of the pattern was reached. - return st == (pattern + patternSize); + return pat == (pattern + patternSize); } int PathTree::Node::comparePath(de::Path const &searchPattern, ComparisonFlags flags) const { if (((flags & PathTree::NoLeaf) && isLeaf()) || - ((flags & PathTree::NoBranch) && isBranch())) + ((flags & PathTree::NoBranch) && isBranch())) + { return 1; + } - try - { - de::Path::Segment const *snode = &searchPattern.lastSegment(); + de::Path::Segment const *snode = &searchPattern.lastSegment(); - // In reverse order, compare each path node in the search term. - int pathNodeCount = searchPattern.segmentCount(); + // In reverse order, compare each path node in the search term. + int pathNodeCount = searchPattern.segmentCount(); - PathTree::Node const *node = this; - for (int i = 0; i < pathNodeCount; ++i) + PathTree::Node const *node = this; + for (int i = 0; i < pathNodeCount; ++i) + { + // If the hashes don't match it can't possibly be this. + if (snode->hash() != node->hash()) { - bool const snameIsWild = !snode->toStringRef().compare(QStringLiteral("*")); - if (!snameIsWild) - { - // If the hashes don't match it can't possibly be this. - if (snode->hash() != node->hash()) - { - return 1; - } - - // Compare the names. - if (!matchName(node->name().constData(), node->name().size(), - snode->toStringRef().constData(), snode->toStringRef().size())) - { - return 1; - } - } + return 1; + } - // Have we arrived at the search target? - if (i == pathNodeCount - 1) + if (!snode->hasWildCard()) + { + if (node->name().compare(snode->toStringRef(), Qt::CaseInsensitive)) { - return !(!(flags & MatchFull) || node->isAtRootLevel()); + return 1; } - - // Is the hierarchy too shallow? - if (node->isAtRootLevel()) + } + else + { + // Compare the names using wildcard pattern matching. + // Note: This has relatively slow performance. + if (!matchName(node->name().constData(), node->name().size(), + snode->toStringRef().constData(), snode->toStringRef().size())) { return 1; } + } - // So far so good. Move one level up the hierarchy. - node = &node->parent(); - snode = &searchPattern.reverseSegment(i + 1); + // Have we arrived at the search target? + if (i == pathNodeCount - 1) + { + return !(!(flags & MatchFull) || node->isAtRootLevel()); } + + // Is the hierarchy too shallow? + if (node->isAtRootLevel()) + { + return 1; + } + + // So far so good. Move one level up the hierarchy. + node = &node->parent(); + snode = &searchPattern.reverseSegment(i + 1); } - catch (de::Path::OutOfBoundsError const &) - {} // Ignore this error. return 1; } diff --git a/doomsday/sdk/libcore/src/filesys/nativefile.cpp b/doomsday/sdk/libcore/src/filesys/nativefile.cpp index ea31fb904c..b51f4ae55b 100644 --- a/doomsday/sdk/libcore/src/filesys/nativefile.cpp +++ b/doomsday/sdk/libcore/src/filesys/nativefile.cpp @@ -203,7 +203,7 @@ void NativeFile::get(Offset at, Byte *values, Size count) const throw OffsetError("NativeFile::get", description() + ": cannot read past end of file " + String("(%1[+%2] > %3)").arg(at).arg(count).arg(size())); } - in.seek(at); + if (in.pos() != qint64(at)) in.seek(qint64(at)); in.read(reinterpret_cast(values), count); } diff --git a/doomsday/sdk/libgui/include/de/graphics/atlas.h b/doomsday/sdk/libgui/include/de/graphics/atlas.h index 9696db897e..932a7f1c6e 100644 --- a/doomsday/sdk/libgui/include/de/graphics/atlas.h +++ b/doomsday/sdk/libgui/include/de/graphics/atlas.h @@ -291,13 +291,21 @@ class LIBGUI_PUBLIC Atlas : public IAtlas, public Lockable, public Deletable virtual void commitFull(Image const &fullImage) const = 0; /** - * Commits a image to the actual physical atlas storage. + * Commits an an image to the actual physical atlas storage. * * @param image Image to commit. - * @param topLeft Top left corner of where to place the image. + * @param topleft Top left corner of where to place the image. */ virtual void commit(Image const &image, Vector2i const &topLeft) const = 0; + /** + * Commits a subregion of an image to the actual physical atlas storage. + * + * @param fullImage Image to commit. + * @param subregion Section of the image to commit. + */ + virtual void commit(Image const &fullImage, Rectanglei const &subregion) const = 0; + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/sdk/libgui/include/de/graphics/atlastexture.h b/doomsday/sdk/libgui/include/de/graphics/atlastexture.h index d4edb46dfc..42852fb6de 100644 --- a/doomsday/sdk/libgui/include/de/graphics/atlastexture.h +++ b/doomsday/sdk/libgui/include/de/graphics/atlastexture.h @@ -13,7 +13,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #ifndef LIBGUI_ATLASTEXTURE_H @@ -66,6 +66,7 @@ class LIBGUI_PUBLIC AtlasTexture : public Atlas, public GLTexture void commitFull(Image const &fullImage) const; void commit(Image const &image, Vector2i const &topLeft) const; + void commit(Image const &fullImage, Rectanglei const &subregion) const; }; } // namespace de diff --git a/doomsday/sdk/libgui/include/de/graphics/gltexture.h b/doomsday/sdk/libgui/include/de/graphics/gltexture.h index 476c7bf0f8..40e335588a 100644 --- a/doomsday/sdk/libgui/include/de/graphics/gltexture.h +++ b/doomsday/sdk/libgui/include/de/graphics/gltexture.h @@ -13,7 +13,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #ifndef LIBGUI_GLTEXTURE_H @@ -183,7 +183,7 @@ class LIBGUI_PUBLIC GLTexture : public Asset void setImage(gl::CubeFace face, Image const &image, int level = 0); /** - * Replaces a portion of existing content. The image should be provided in + * Replaces a portion of existing content. The image must be provided in * the same format as the previous full content. * * @param image Image to copy. @@ -192,7 +192,20 @@ class LIBGUI_PUBLIC GLTexture : public Asset */ void setSubImage(Image const &image, Vector2i const &pos, int level = 0); + /** + * Replaces a portion of existing content. The image must be provided in the same + * format as the previous full content. + * + * The image data is copied directly from the specified subregion of the image. + * + * @param image Image to copy. + * @param rect Region of the image to copy. + * @param level Mipmap level. + */ + void setSubImage(Image const &image, Rectanglei const &rect, int level = 0); + void setSubImage(gl::CubeFace face, Image const &image, Vector2i const &pos, int level = 0); + void setSubImage(gl::CubeFace face, Image const &image, Rectanglei const &rect, int level = 0); /** * Generate a full set of mipmap levels based on the content on level 0. diff --git a/doomsday/sdk/libgui/src/graphics/atlas.cpp b/doomsday/sdk/libgui/src/graphics/atlas.cpp index e26ea9e0fe..74fcbc587a 100644 --- a/doomsday/sdk/libgui/src/graphics/atlas.cpp +++ b/doomsday/sdk/libgui/src/graphics/atlas.cpp @@ -97,11 +97,10 @@ DENG2_PIMPL(Atlas) bool mustCommitFull() const { /* - * Simple heuristic: if more than 70% of the pixels are included in the - * changed area, simply copy the whole thing rather than doing a large - * extra copy. + * Simple heuristic: if more than 95% of the pixels are included in the + * changed area, simply copy the whole thing. */ - return (needFullCommit || changedPercentage() > .7f); + return (needFullCommit || changedPercentage() > .95f); } float changedPercentage() const @@ -530,9 +529,7 @@ void Atlas::commit() const { LOGDEV_GL_XVERBOSE("Partial commit ") << d->changedArea.asText(); } - - // An extra copy is done to crop to the changed area. - commit(d->backing.subImage(d->changedArea), d->changedArea.topLeft); + commit(d->backing, d->changedArea); } d->needCommit = false; diff --git a/doomsday/sdk/libgui/src/graphics/atlastexture.cpp b/doomsday/sdk/libgui/src/graphics/atlastexture.cpp index efc3501be0..12bf448b81 100644 --- a/doomsday/sdk/libgui/src/graphics/atlastexture.cpp +++ b/doomsday/sdk/libgui/src/graphics/atlastexture.cpp @@ -13,7 +13,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #include "de/AtlasTexture" @@ -77,4 +77,17 @@ void AtlasTexture::commit(Image const &image, Vector2i const &topLeft) const tex->setSubImage(image, topLeft); } +void AtlasTexture::commit(Image const &fullImage, Rectanglei const &subregion) const +{ + GLTexture *tex = const_cast(this); + + if (size() == GLTexture::Size(0, 0)) + { + // Hasn't been full-committed yet. + tex->setUndefinedImage(totalSize(), Image::RGBA_8888); + } + + tex->setSubImage(fullImage, subregion); +} + } // namespace de diff --git a/doomsday/sdk/libgui/src/graphics/gltexture.cpp b/doomsday/sdk/libgui/src/graphics/gltexture.cpp index 562d76c133..0a68760b54 100644 --- a/doomsday/sdk/libgui/src/graphics/gltexture.cpp +++ b/doomsday/sdk/libgui/src/graphics/gltexture.cpp @@ -192,7 +192,7 @@ DENG2_PIMPL(GLTexture) << level << internalFormat << size.x << size.y << 0 << glFormat.format << glFormat.type << data;*/ - if (data) glPixelStorei(GL_UNPACK_ALIGNMENT, glFormat.rowAlignment); + if (data) glPixelStorei(GL_UNPACK_ALIGNMENT, GLint(glFormat.rowAlignment)); glTexImage2D(isCube()? glFace(face) : texTarget, level, internalFormat, size.x, size.y, 0, glFormat.format, glFormat.type, data); @@ -203,13 +203,34 @@ DENG2_PIMPL(GLTexture) void glSubImage(int level, Vector2i const &pos, Size const &size, GLPixelFormat const &glFormat, void const *data, CubeFace face = PositiveX) { - if (data) glPixelStorei(GL_UNPACK_ALIGNMENT, glFormat.rowAlignment); + if (data) glPixelStorei(GL_UNPACK_ALIGNMENT, GLint(glFormat.rowAlignment)); glTexSubImage2D(isCube()? glFace(face) : texTarget, level, pos.x, pos.y, size.x, size.y, glFormat.format, glFormat.type, data); LIBGUI_ASSERT_GL_OK(); } + + void glSubImage(int level, Rectanglei const &rect, Image const &image, + CubeFace face = PositiveX) + { + auto const &glFormat = image.glFormat(); + + glPixelStorei(GL_UNPACK_ALIGNMENT, GLint(glFormat.rowAlignment)); + glPixelStorei(GL_UNPACK_ROW_LENGTH, GLint(image.width())); + + int const bytesPerPixel = image.depth() / 8; + + glTexSubImage2D(isCube()? glFace(face) : texTarget, + level, rect.left(), rect.top(), rect.width(), rect.height(), + glFormat.format, glFormat.type, + static_cast(image.bits()) + + bytesPerPixel * rect.left() + image.stride() * rect.top()); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + LIBGUI_ASSERT_GL_OK(); + } }; GLTexture::GLTexture() : d(new Instance(this)) @@ -437,6 +458,21 @@ void GLTexture::setSubImage(Image const &image, Vector2i const &pos, int level) } } +void GLTexture::setSubImage(Image const &image, Rectanglei const &rect, int level) +{ + d->texTarget = GL_TEXTURE_2D; + + d->alloc(); + d->glBind(); + d->glSubImage(level, rect, image); + d->glUnbind(); + + if (!level && d->flags.testFlag(AutoMips)) + { + generateMipmap(); + } +} + void GLTexture::setSubImage(CubeFace face, Image const &image, Vector2i const &pos, int level) { d->texTarget = GL_TEXTURE_CUBE_MAP; @@ -452,6 +488,21 @@ void GLTexture::setSubImage(CubeFace face, Image const &image, Vector2i const &p } } +void GLTexture::setSubImage(CubeFace face, Image const &image, Rectanglei const &rect, int level) +{ + d->texTarget = GL_TEXTURE_CUBE_MAP; + + d->alloc(); + d->glBind(); + d->glSubImage(level, rect, image, face); + d->glUnbind(); + + if (!level && d->flags.testFlag(AutoMips)) + { + generateMipmap(); + } +} + void GLTexture::generateMipmap() { if (d->name)