diff options
Diffstat (limited to 'src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp')
-rw-r--r-- | src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp new file mode 100644 index 00000000..c6941860 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp @@ -0,0 +1,519 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "Qt3DSDistanceFieldGlyphCache_p.h" + +#include <QtQuick/private/qsgareaallocator_p.h> + +#include <QtCore/qmath.h> +#include <QtCore/qendian.h> +#include <QtGui/qimage.h> + +#include "render/Qt3DSRenderContext.h" +#include "render/Qt3DSRenderBaseTypes.h" +#include "Qt3DSRenderResourceManager.h" +#include "foundation/Qt3DSAllocator.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +QT_BEGIN_NAMESPACE + +// Should work on most hardware. Used as stop gap until Qt 3D provides a +// way to retrieve the system value +#ifndef Q3DSDISTANCEFIELDGLYPHCACHE_MAXIMUM_TEXURE_SIZE +# define Q3DSDISTANCEFIELDGLYPHCACHE_MAXIMUM_TEXURE_SIZE 2048 +#endif + +#if !defined(Q3DSDISTANCEFIELDGLYPHCACHE_PADDING) +# define Q3DSDISTANCEFIELDGLYPHCACHE_PADDING 2 +#endif + +Q3DSDistanceFieldGlyphCache::Q3DSDistanceFieldGlyphCache( + const QRawFont &font, qt3ds::render::IQt3DSRenderContext &context) + : QSGDistanceFieldGlyphCache(font) + , m_context(context) +{ + m_maxTextureSize = Q3DSDISTANCEFIELDGLYPHCACHE_MAXIMUM_TEXURE_SIZE; + + loadPregeneratedCache(font); +} + +Q3DSDistanceFieldGlyphCache::~Q3DSDistanceFieldGlyphCache() +{ + for (auto &texture : m_textures) + qt3ds::foundation::NVDelete(m_context.GetAllocator(), texture.texture); +} + +int Q3DSDistanceFieldGlyphCache::maxTextureSize() const +{ + return m_maxTextureSize; +} + +Q3DSDistanceFieldGlyphCache::TextureInfo *Q3DSDistanceFieldGlyphCache::textureInfo(int index) const +{ + while (index >= m_textures.size()) + m_textures.append(TextureInfo()); + return &m_textures[index]; +} + +void Q3DSDistanceFieldGlyphCache::referenceGlyphs(const QSet<glyph_t> &glyphs) +{ + m_unusedGlyphs -= glyphs; +} + +void Q3DSDistanceFieldGlyphCache::releaseGlyphs(const QSet<glyph_t> &glyphs) +{ + m_unusedGlyphs += glyphs; +} + +void Q3DSDistanceFieldGlyphCache::resizeTexture(TextureInfo *info, int width, int height) +{ + QImage &image = info->copy; + if (info->texture == nullptr) { + info->texture = m_context.GetRenderContext().CreateTexture2D(); + info->texture->SetMinFilter(qt3ds::render::NVRenderTextureMinifyingOp::Enum::Linear); + info->texture->SetMagFilter(qt3ds::render::NVRenderTextureMagnifyingOp::Enum::Linear); + info->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), image.byteCount()), + 0, image.width(), image.height(), + qt3ds::render::NVRenderTextureFormats::R8); + } + + qt3ds::render::STextureDetails textureDetails = info->texture->GetTextureDetails(); + if (int(textureDetails.m_Width) != width || int(textureDetails.m_Height) != height) { + info->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), image.byteCount()), + 0, image.width(), image.height(), + qt3ds::render::NVRenderTextureFormats::R8); + } + + if (info->copy.width() != width || info->copy.height() != height) { + QImage newImage(width, height, QImage::Format_Alpha8); + + for (int y = 0; y < info->copy.height(); ++y) { + uchar *dest = newImage.scanLine(y); + const uchar *src = info->copy.scanLine(y); + ::memcpy(dest, src, size_t(info->copy.width())); + } + + info->copy = newImage; + + info->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), image.byteCount()), + 0, image.width(), image.height(), + qt3ds::render::NVRenderTextureFormats::R8); + } +} + +void Q3DSDistanceFieldGlyphCache::storeGlyphs(const QList<QDistanceField> &glyphs) +{ + using GlyphTextureHash = QHash<TextureInfo *, QVector<glyph_t> >; + using GlyphTextureHashConstIt = GlyphTextureHash::const_iterator; + + GlyphTextureHash glyphTextures; + for (int i = 0; i < glyphs.size(); ++i) { + QDistanceField glyph = glyphs.at(i); + glyph_t glyphIndex = glyph.glyph(); + TexCoord c = glyphTexCoord(glyphIndex); + TextureInfo *texInfo = m_glyphsTexture.value(glyphIndex); + + resizeTexture(texInfo, maxTextureSize(), texInfo->allocatedArea.height()); + + Q_ASSERT(!glyphTextures[texInfo].contains(glyphIndex)); + glyphTextures[texInfo].append(glyphIndex); + + int padding = texInfo->padding; + int expectedWidth = qCeil(c.width + c.xMargin * 2); + glyph = glyph.copy(-padding, -padding, + expectedWidth + padding * 2, glyph.height() + padding * 2); + + for (int y = 0; y < glyph.height(); ++y) { + const uchar *src = glyph.scanLine(y); + uchar *dest = texInfo->copy.scanLine(int(c.y) + y - padding) + (int(c.x) - padding); + ::memcpy(dest, src, size_t(glyph.width())); + } + } + + for (GlyphTextureHashConstIt i = glyphTextures.constBegin(), + cend = glyphTextures.constEnd(); i != cend; ++i) { + Texture t; + // 0 == empty texture, (i - 1) == index into m_textures + t.textureId = uint(i.key() - m_textures.constData()) + 1; + qt3ds::render::STextureDetails textureDetails = i.key()->texture->GetTextureDetails(); + t.size = QSize(textureDetails.m_Width, textureDetails.m_Height); + setGlyphsTexture(i.value(), t); + + QImage &image = i.key()->copy; + + i.key()->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), + image.byteCount()), + 0, image.width(), image.height(), + qt3ds::render::NVRenderTextureFormats::R8); + } +} + +void Q3DSDistanceFieldGlyphCache::requestGlyphs(const QSet<glyph_t> &glyphs) +{ + // Note: Most of this is copy-pasted from QSGDefaultDistanceFieldGlyphCache in Qt Quick. + // All of this can probably be shared as a default implementation, since it does not + // actually create any textures, but it might have to be either templated or based + // on void*. For now we will just live with the duplication. + + QList<GlyphPosition> glyphPositions; + QVector<glyph_t> glyphsToRender; + + if (m_areaAllocator == nullptr) { + m_areaAllocator = new QSGAreaAllocator(QSize(maxTextureSize(), + m_maxTextureCount * maxTextureSize())); + } + + for (QSet<glyph_t>::const_iterator it = glyphs.constBegin(); it != glyphs.constEnd() ; ++it) { + glyph_t glyphIndex = *it; + + int padding = Q3DSDISTANCEFIELDGLYPHCACHE_PADDING; + QRectF boundingRect = glyphData(glyphIndex).boundingRect; + int glyphWidth = qCeil(boundingRect.width()) + distanceFieldRadius() * 2; + int glyphHeight = qCeil(boundingRect.height()) + distanceFieldRadius() * 2; + QSize glyphSize(glyphWidth + padding * 2, glyphHeight + padding * 2); + QRect alloc = m_areaAllocator->allocate(glyphSize); + + if (alloc.isNull()) { + // Unallocate unused glyphs until we can allocated the new glyph + while (alloc.isNull() && !m_unusedGlyphs.isEmpty()) { + glyph_t unusedGlyph = *m_unusedGlyphs.constBegin(); + + TexCoord unusedCoord = glyphTexCoord(unusedGlyph); + QRectF unusedGlyphBoundingRect = glyphData(unusedGlyph).boundingRect; + int unusedGlyphWidth = qCeil(unusedGlyphBoundingRect.width()) + + distanceFieldRadius() * 2; + int unusedGlyphHeight = qCeil(unusedGlyphBoundingRect.height()) + + distanceFieldRadius() * 2; + m_areaAllocator->deallocate(QRect(int(unusedCoord.x) - padding, + int(unusedCoord.y) - padding, + padding * 2 + unusedGlyphWidth, + padding * 2 + unusedGlyphHeight)); + + m_unusedGlyphs.remove(unusedGlyph); + m_glyphsTexture.remove(unusedGlyph); + removeGlyph(unusedGlyph); + + alloc = m_areaAllocator->allocate(glyphSize); + } + + // Not enough space left for this glyph... skip to the next one + if (alloc.isNull()) + continue; + } + + TextureInfo *tex = textureInfo(alloc.y() / maxTextureSize()); + alloc = QRect(alloc.x(), alloc.y() % maxTextureSize(), alloc.width(), alloc.height()); + + tex->allocatedArea |= alloc; + Q_ASSERT(tex->padding == padding || tex->padding < 0); + tex->padding = padding; + + GlyphPosition p; + p.glyph = glyphIndex; + p.position = alloc.topLeft() + QPoint(padding, padding); + + glyphPositions.append(p); + glyphsToRender.append(glyphIndex); + m_glyphsTexture.insert(glyphIndex, tex); + } + + setGlyphsPosition(glyphPositions); + markGlyphsToRender(glyphsToRender); +} + +void Q3DSDistanceFieldGlyphCache::processPendingGlyphs() +{ + update(); +} + +Q3DSDistanceFieldGlyphCache::TextureInfo *Q3DSDistanceFieldGlyphCache::textureInfoById( + uint id) const +{ + Q_ASSERT(id > 0); + return textureInfo(id - 1); +} + +// This is all copy-pasted from Qt Quick, as sharing it would require some refactoring, and we +// need to work with Qt 5.12.2 at the moment. +namespace { + struct Qtdf { + // We need these structs to be tightly packed, but some compilers we use do not + // support #pragma pack(1), so we need to hardcode the offsets/sizes in the + // file format + enum TableSize { + HeaderSize = 14, + GlyphRecordSize = 46, + TextureRecordSize = 17 + }; + + enum Offset { + // Header + majorVersion = 0, + minorVersion = 1, + pixelSize = 2, + textureSize = 4, + flags = 8, + headerPadding = 9, + numGlyphs = 10, + + // Glyph record + glyphIndex = 0, + textureOffsetX = 4, + textureOffsetY = 8, + textureWidth = 12, + textureHeight = 16, + xMargin = 20, + yMargin = 24, + boundingRectX = 28, + boundingRectY = 32, + boundingRectWidth = 36, + boundingRectHeight = 40, + textureIndex = 44, + + // Texture record + allocatedX = 0, + allocatedY = 4, + allocatedWidth = 8, + allocatedHeight = 12, + texturePadding = 16 + + }; + + template <typename T> + static inline T fetch(const char *data, Offset offset) + { + return qFromBigEndian<T>(data + int(offset)); + } + }; +} + +qreal Q3DSDistanceFieldGlyphCache::fontSize() const +{ + return QT_DISTANCEFIELD_BASEFONTSIZE(m_doubleGlyphResolution); +} + +bool Q3DSDistanceFieldGlyphCache::loadPregeneratedCache(const QRawFont &font) +{ + // The pregenerated data must be loaded first, otherwise the area allocator + // will be wrong + if (m_areaAllocator != nullptr) { + qWarning("Font cache must be loaded before cache is used"); + return false; + } + + QByteArray qtdfTable = font.fontTable("qtdf"); + if (qtdfTable.isEmpty()) + return false; + + using GlyphTextureHash = QHash<TextureInfo *, QVector<glyph_t> >; + + GlyphTextureHash glyphTextures; + + if (uint(qtdfTable.size()) < Qtdf::HeaderSize) { + qWarning("Invalid qtdf table in font '%s'", + qPrintable(font.familyName())); + return false; + } + + const char *qtdfTableStart = qtdfTable.constData(); + const char *qtdfTableEnd = qtdfTableStart + qtdfTable.size(); + + int padding = 0; + int textureCount = 0; + { + quint8 majorVersion = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::majorVersion); + quint8 minorVersion = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::minorVersion); + if (majorVersion != 5 || minorVersion != 12) { + qWarning("Invalid version of qtdf table %d.%d in font '%s'", + majorVersion, + minorVersion, + qPrintable(font.familyName())); + return false; + } + + qreal pixelSize = qreal(Qtdf::fetch<quint16>(qtdfTableStart, Qtdf::pixelSize)); + m_maxTextureSize = int(Qtdf::fetch<quint32>(qtdfTableStart, Qtdf::textureSize)); + m_doubleGlyphResolution = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::flags) == 1; + padding = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::headerPadding); + + if (pixelSize <= 0.0) { + qWarning("Invalid pixel size in '%s'", qPrintable(font.familyName())); + return false; + } + + if (m_maxTextureSize <= 0) { + qWarning("Invalid texture size in '%s'", qPrintable(font.familyName())); + return false; + } + + if (padding != Q3DSDISTANCEFIELDGLYPHCACHE_PADDING) { + qWarning("Padding mismatch in '%s'. Font requires %d, but Qt is compiled with %d.", + qPrintable(font.familyName()), + padding, + Q3DSDISTANCEFIELDGLYPHCACHE_PADDING); + } + + m_referenceFont.setPixelSize(pixelSize); + + quint32 glyphCount = Qtdf::fetch<quint32>(qtdfTableStart, Qtdf::numGlyphs); + m_unusedGlyphs.reserve(int(glyphCount)); + + const char *allocatorData = qtdfTableStart + Qtdf::HeaderSize; + { + m_areaAllocator = new QSGAreaAllocator(QSize(0, 0)); + allocatorData = m_areaAllocator->deserialize(allocatorData, + qtdfTableEnd - allocatorData); + if (allocatorData == nullptr) + return false; + } + + if (m_areaAllocator->size().height() % m_maxTextureSize != 0) { + qWarning("Area allocator size mismatch in '%s'", qPrintable(font.familyName())); + return false; + } + + textureCount = m_areaAllocator->size().height() / m_maxTextureSize; + m_maxTextureCount = qMax(m_maxTextureCount, textureCount); + + const char *textureRecord = allocatorData; + for (int i = 0; i < textureCount; ++i, textureRecord += Qtdf::TextureRecordSize) { + if (textureRecord + Qtdf::TextureRecordSize > qtdfTableEnd) { + qWarning("qtdf table too small in font '%s'.", + qPrintable(font.familyName())); + return false; + } + + TextureInfo *tex = textureInfo(i); + tex->allocatedArea.setX(int(Qtdf::fetch<quint32>(textureRecord, Qtdf::allocatedX))); + tex->allocatedArea.setY(int(Qtdf::fetch<quint32>(textureRecord, Qtdf::allocatedY))); + tex->allocatedArea.setWidth(int(Qtdf::fetch<quint32>(textureRecord, + Qtdf::allocatedWidth))); + tex->allocatedArea.setHeight(int(Qtdf::fetch<quint32>(textureRecord, + Qtdf::allocatedHeight))); + tex->padding = Qtdf::fetch<quint8>(textureRecord, Qtdf::texturePadding); + } + + const char *glyphRecord = textureRecord; + for (quint32 i = 0; i < glyphCount; ++i, glyphRecord += Qtdf::GlyphRecordSize) { + if (glyphRecord + Qtdf::GlyphRecordSize > qtdfTableEnd) { + qWarning("qtdf table too small in font '%s'.", + qPrintable(font.familyName())); + return false; + } + + glyph_t glyph = Qtdf::fetch<quint32>(glyphRecord, Qtdf::glyphIndex); + m_unusedGlyphs.insert(glyph); + + GlyphData &glyphData = emptyData(glyph); + +#define FROM_FIXED_POINT(value) (qreal(value)/qreal(65536)) + + glyphData.texCoord.x + = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetX)); + glyphData.texCoord.y + = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetY)); + glyphData.texCoord.width + = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureWidth)); + glyphData.texCoord.height + = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureHeight)); + glyphData.texCoord.xMargin + = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::xMargin)); + glyphData.texCoord.yMargin + = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::yMargin)); + glyphData.boundingRect.setX( + FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectX))); + glyphData.boundingRect.setY( + FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectY))); + glyphData.boundingRect.setWidth( + FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, + Qtdf::boundingRectWidth))); + glyphData.boundingRect.setHeight( + FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, + Qtdf::boundingRectHeight))); + +#undef FROM_FIXED_POINT + + int textureIndex = Qtdf::fetch<quint16>(glyphRecord, Qtdf::textureIndex); + if (textureIndex < 0 || textureIndex >= textureCount) { + qWarning("Invalid texture index %d (texture count == %d) in '%s'", + textureIndex, + textureCount, + qPrintable(font.familyName())); + return false; + } + + + TextureInfo *texInfo = textureInfo(textureIndex); + m_glyphsTexture.insert(glyph, texInfo); + + glyphTextures[texInfo].append(glyph); + } + + const uchar *textureData = reinterpret_cast<const uchar *>(glyphRecord); + for (int i = 0; i < textureCount; ++i) { + + TextureInfo *texInfo = textureInfo(i); + + int width = texInfo->allocatedArea.width(); + int height = texInfo->allocatedArea.height(); + qint64 size = width * height; + if (reinterpret_cast<const char *>(textureData + size) > qtdfTableEnd) { + qWarning("qtdf table too small in font '%s'.", + qPrintable(font.familyName())); + return false; + } + + resizeTexture(texInfo, width, height); + + memcpy(texInfo->copy.bits(), textureData, size); + textureData += size; + + QImage &image = texInfo->copy; + texInfo->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), + image.byteCount()), + 0, image.width(), image.height(), + qt3ds::render::NVRenderTextureFormats::R8); + + QVector<glyph_t> glyphs = glyphTextures.value(texInfo); + + Texture t; + t.textureId = uint(i + 1); + t.size = texInfo->copy.size(); + + setGlyphsTexture(glyphs, t); + } + } + + return true; +} + +QT_END_NAMESPACE + +#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2) |