diff options
Diffstat (limited to 'src/Runtime/Source')
24 files changed, 2339 insertions, 48 deletions
diff --git a/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp b/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp index 8fa75792..27f326eb 100644 --- a/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp +++ b/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp @@ -894,9 +894,14 @@ struct Qt3DSRenderSceneManager : public Q3DStudio::ISceneManager, inPresentation.SetScene(&inScene); if (m_ProjectInitialized == false) { m_ProjectInitialized = true; - if (m_Context->m_Context->GetTextRenderer()) + if (m_Context->m_Context->GetTextRenderer()) { m_Context->m_Context->GetTextRenderer()->AddProjectFontDirectory( inScene.m_Presentation->m_PresentationDirectory); + } + if (m_Context->m_Context->getDistanceFieldRenderer()) { + m_Context->m_Context->getDistanceFieldRenderer()->AddProjectFontDirectory( + inScene.m_Presentation->m_PresentationDirectory); + } eastl::string theBinaryPath(inPresentation.GetFilePath().toLatin1().constData()); qt3ds::foundation::CFileTools::AppendDirectoryInPathToFile(theBinaryPath, "binary"); eastl::string theBinaryDir(theBinaryPath); diff --git a/src/Runtime/Source/engine/Qt3DSRenderRuntimeBindingImpl.h b/src/Runtime/Source/engine/Qt3DSRenderRuntimeBindingImpl.h index 3ffe15ce..1d27f293 100644 --- a/src/Runtime/Source/engine/Qt3DSRenderRuntimeBindingImpl.h +++ b/src/Runtime/Source/engine/Qt3DSRenderRuntimeBindingImpl.h @@ -118,6 +118,11 @@ namespace render { m_CoreContext->SetTextRendererCore( ITextRendererCore::CreateQtTextRenderer(*m_Foundation, *m_StringTable)); +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + m_CoreContext->setDistanceFieldRenderer( + ITextRendererCore::createDistanceFieldRenderer(*m_Foundation)); +#endif + m_CoreContext->SetOnscreenTextRendererCore( ITextRendererCore::CreateOnscreenTextRenderer(*m_Foundation)); } diff --git a/src/Runtime/Source/runtime/Qt3DSApplication.cpp b/src/Runtime/Source/runtime/Qt3DSApplication.cpp index 249070c2..0538666d 100644 --- a/src/Runtime/Source/runtime/Qt3DSApplication.cpp +++ b/src/Runtime/Source/runtime/Qt3DSApplication.cpp @@ -1314,6 +1314,11 @@ struct SApp : public IApplication NVFoundationBase &fnd(m_CoreFactory->GetFoundation()); + if (m_CoreFactory->GetRenderContextCore().getDistanceFieldRenderer()) { + m_CoreFactory->GetRenderContextCore().getDistanceFieldRenderer() + ->AddProjectFontDirectory(projectDirectory.c_str()); + } + if (m_CoreFactory->GetRenderContextCore().GetTextRendererCore()) { m_CoreFactory->GetRenderContextCore().GetTextRendererCore()->AddProjectFontDirectory( projectDirectory.c_str()); 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) diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp new file mode 100644 index 00000000..750301dc --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 "Qt3DSDistanceFieldGlyphCacheManager_p.h" +#include "Qt3DSDistanceFieldGlyphCache_p.h" + +#include <QtQuick/private/qsgdefaultrendercontext_p.h> + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +QT_BEGIN_NAMESPACE + +namespace { + class FontKeyAccessor : public QSGDefaultRenderContext + { + public: + static QString fontKey(const QRawFont &font) + { + return QSGDefaultRenderContext::fontKey(font); + } + }; +} + +Q3DSDistanceFieldGlyphCacheManager::~Q3DSDistanceFieldGlyphCacheManager() +{ + for (auto &cache : qAsConst(m_glyphCaches)) + delete cache; +} + +Q3DSDistanceFieldGlyphCache *Q3DSDistanceFieldGlyphCacheManager::glyphCache(const QRawFont &font) +{ + QString key = FontKeyAccessor::fontKey(font); + Q3DSDistanceFieldGlyphCache *cache = m_glyphCaches.value(key); + if (cache == nullptr) { + cache = new Q3DSDistanceFieldGlyphCache(font, *m_context); + m_glyphCaches.insert(key, cache); + } + + return cache; +} + +void Q3DSDistanceFieldGlyphCacheManager::setContext(qt3ds::render::IQt3DSRenderContext &context) +{ + m_context = &context; +} + +QT_END_NAMESPACE + +#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2) diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h new file mode 100644 index 00000000..19d3c088 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef Q3DSDISTANCEFIELDGLYPHCACHEMANAGER_P_H +#define Q3DSDISTANCEFIELDGLYPHCACHEMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qhash.h> +#include <QtGui/qrawfont.h> + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +namespace qt3ds { +namespace render { +class IQt3DSRenderContext; +} +} + +QT_BEGIN_NAMESPACE + +class Q3DSDistanceFieldGlyphCache; +class Q3DSDistanceFieldGlyphCacheManager +{ +public: + ~Q3DSDistanceFieldGlyphCacheManager(); + Q3DSDistanceFieldGlyphCache *glyphCache(const QRawFont &font); + void setContext(qt3ds::render::IQt3DSRenderContext &context); + +private: + QHash<QString, Q3DSDistanceFieldGlyphCache *> m_glyphCaches; + qt3ds::render::IQt3DSRenderContext *m_context; + +}; + +QT_END_NAMESPACE + +#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +#endif // Q3DSDISTANCEFIELDGLYPHCACHEMANAGER_P_H diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h new file mode 100644 index 00000000..e3ec204a --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef Q3DSDISTANCEFIELDGLYPHCACHE_P_H +#define Q3DSDISTANCEFIELDGLYPHCACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qsgadaptationlayer_p.h> +#include "render/Qt3DSRenderTexture2D.h" +#include "Qt3DSRenderContextCore.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +QT_BEGIN_NAMESPACE + +class QSGAreaAllocator; +class Q3DSDistanceFieldGlyphCache : public QSGDistanceFieldGlyphCache +{ +public: + struct TextureInfo { + qt3ds::render::NVRenderTexture2D *texture; + int padding = -1; + + QRect allocatedArea; + QImage copy; + }; + + Q3DSDistanceFieldGlyphCache(const QRawFont &font, + qt3ds::render::IQt3DSRenderContext &context); + ~Q3DSDistanceFieldGlyphCache() override; + + void requestGlyphs(const QSet<glyph_t> &glyphs) override; + void storeGlyphs(const QList<QDistanceField> &glyphs) override; + void referenceGlyphs(const QSet<glyph_t> &glyphs) override; + void releaseGlyphs(const QSet<glyph_t> &glyphs) override; + + void processPendingGlyphs() override; + + TextureInfo *textureInfoById(uint textureId) const; + + qreal fontSize() const; + +private: + bool loadPregeneratedCache(const QRawFont &font); + TextureInfo *textureInfo(int index) const; + + int maxTextureSize() const; + void resizeTexture(TextureInfo *info, int width, int height); + + QSGAreaAllocator *m_areaAllocator = nullptr; + int m_maxTextureSize = 0; + int m_maxTextureCount = 3; + + mutable QVector<TextureInfo> m_textures; + QHash<glyph_t, TextureInfo *> m_glyphsTexture; + QSet<glyph_t> m_unusedGlyphs; + qt3ds::render::IQt3DSRenderContext &m_context; +}; + +QT_END_NAMESPACE + +#endif // Q3DSDISTANCEFIELDGLYPHCACHE_P_H + +#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2) diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp new file mode 100644 index 00000000..64e5a737 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp @@ -0,0 +1,1006 @@ +/**************************************************************************** +** +** 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 "Qt3DSDistanceFieldRenderer.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +#include "Qt3DSRenderContextCore.h" +#include "Qt3DSRenderShaderCodeGeneratorV2.h" +#include "render/Qt3DSRenderBaseTypes.h" +#include "render/Qt3DSRenderContext.h" +#include "qmath.h" +#include "foundation/Qt3DSAllocator.h" + +using namespace qt3ds::render; + +Q3DSDistanceFieldRenderer::Q3DSDistanceFieldRenderer(NVFoundationBase &foundation) + : m_foundation(foundation) +{ + const QWindowList list = QGuiApplication::topLevelWindows(); + if (list.size() > 0) + m_pixelRatio = list[0]->devicePixelRatio(); +} + +Q3DSDistanceFieldRenderer::~Q3DSDistanceFieldRenderer() +{ + NVAllocatorCallback &alloc = m_context->GetAllocator(); + + QHash<size_t, Q3DSDistanceFieldMesh>::const_iterator it; + for (it = m_meshCache.constBegin(); it != m_meshCache.constEnd(); ++it) { + const Q3DSDistanceFieldMesh &mesh = it.value(); + NVDelete(alloc, mesh.vertexBuffer); + NVDelete(alloc, mesh.indexBuffer); + NVDelete(alloc, mesh.inputAssembler); + } +} + +void Q3DSDistanceFieldRenderer::AddSystemFontDirectory(const char8_t *dir) +{ + QString systemDir(dir); + m_systemDirs += systemDir; + m_fontDatabase.registerFonts({ systemDir }); +} + +void Q3DSDistanceFieldRenderer::AddProjectFontDirectory(const char8_t *dir) +{ + QString projectDir(dir); + projectDir += QLatin1String("/fonts"); + m_projectDirs += projectDir; + m_fontDatabase.registerFonts({ projectDir }); +} + +void Q3DSDistanceFieldRenderer::ClearProjectFontDirectories() +{ + m_fontDatabase.unregisterFonts(m_projectDirs); + m_projectDirs.clear(); +} + +ITextRenderer &Q3DSDistanceFieldRenderer::GetTextRenderer(NVRenderContext &) +{ + return *this; +} + +void Q3DSDistanceFieldRenderer::EndFrame() +{ + // Remove meshes for glyphs that weren't rendered last frame + NVAllocatorCallback &alloc = m_context->GetAllocator(); + + QHash<size_t, Q3DSDistanceFieldMesh>::const_iterator it = m_meshCache.constBegin(); + while (it != m_meshCache.constEnd()) { + const size_t glyphHash = it.key(); + const Q3DSDistanceFieldMesh &mesh = it.value(); + if (!m_renderedGlyphs.contains(glyphHash)) { + NVDelete(alloc, mesh.vertexBuffer); + NVDelete(alloc, mesh.indexBuffer); + NVDelete(alloc, mesh.inputAssembler); + m_meshCache.erase(it++); + } else { + it++; + } + } + m_renderedGlyphs.clear(); +} + +QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> +Q3DSDistanceFieldRenderer::buildGlyphsPerTexture(const SText &textInfo) +{ + if (textInfo.m_BoundingBox.x < 0 || textInfo.m_BoundingBox.y < 0) + return QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>(); + + QVector2D boundingBox = QVector2D(textInfo.m_BoundingBox.x, textInfo.m_BoundingBox.y); + + QRawFont font = m_fontDatabase.findFont(textInfo.m_Font.c_str()); + font.setPixelSize(qreal(textInfo.m_FontSize)); + + const qreal maximumWidth = boundingBox.isNull() ? qreal(0x01000000) : qreal(boundingBox.x()); + const qreal maximumHeight = boundingBox.isNull() ? qreal(0x01000000) : qreal(boundingBox.y()); + + QTextLayout layout; + QTextOption option = layout.textOption(); + + QTextOption::WrapMode wrapMode; + switch (textInfo.m_WordWrap) { + case TextWordWrap::Clip: + wrapMode = QTextOption::ManualWrap; + break; + case TextWordWrap::WrapWord: + wrapMode = QTextOption::WrapAtWordBoundaryOrAnywhere; + break; + case TextWordWrap::WrapAnywhere: + wrapMode = QTextOption::WrapAnywhere; + break; + case TextWordWrap::Unknown: + wrapMode = QTextOption::ManualWrap; + Q_ASSERT(0); + }; + option.setWrapMode(wrapMode); + option.setUseDesignMetrics(true); + + layout.setTextOption(option); + layout.setRawFont(font); + + QString text = textInfo.m_Text.c_str(); + text.replace(QLatin1Char('\n'), QChar::LineSeparator); + + qreal width; + qreal height; + bool needsElide; + do { + needsElide = false; + + layout.clearLayout(); + + float leading = textInfo.m_Leading; + width = 0.0; + height = qreal(-leading); + + QVector<QTextLayout::FormatRange> formatRanges; + + QTextLayout::FormatRange formatRange; + formatRange.start = 0; + formatRange.length = text.length(); + formatRange.format.setFontLetterSpacingType(QFont::AbsoluteSpacing); + formatRange.format.setFontLetterSpacing(qreal(textInfo.m_Tracking)); + formatRanges.append(formatRange); + layout.setFormats(formatRanges); + + layout.setText(text); + layout.beginLayout(); + + QTextLine previousLine; + forever { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + + line.setLineWidth(maximumWidth); + height += qreal(leading); + height = qCeil(height); + + qreal textWidth = line.naturalTextWidth(); + line.setPosition(QPointF(0.0, height)); + + width = qMin(maximumWidth, qMax(width, textWidth)); + height += layout.engine()->lines[line.lineNumber()].height().toReal(); + + // Fast path for right elide + if (textInfo.m_Elide == TextElide::ElideRight + && previousLine.isValid() + && height > maximumHeight) { + break; + } + + previousLine = line; + } + layout.endLayout(); + + if (textInfo.m_Elide != TextElide::ElideNone && height > maximumHeight) { + needsElide = true; + + QString elidedText; + switch (textInfo.m_Elide) { + case TextElide::ElideRight: + if (previousLine.textStart() > 0) + elidedText = text.left(previousLine.textStart()); + + elidedText += layout.engine()->elidedText( + Qt::ElideRight, QFixed::fromReal(width), 0, previousLine.textStart(), + text.length() - previousLine.textStart()); + break; + case TextElide::ElideLeft: + { + height = 0.0; + previousLine = QTextLine(); + for (int i = layout.lineCount() - 1; i >= 0; --i) { + qreal lineHeight = layout.lineAt(i).height(); + if (i < layout.lineCount() - 1 && height + lineHeight > maximumHeight) + break; + height += lineHeight; + previousLine = layout.lineAt(i); + } + + Q_ASSERT(previousLine.isValid()); + elidedText += layout.engine()->elidedText( + Qt::ElideLeft, QFixed::fromReal(width), 0, 0, + previousLine.textStart() + previousLine.textLength()); + + int nextPosition = (previousLine.textStart() + previousLine.textLength()); + if (nextPosition < text.length()) + elidedText += text.mid(nextPosition); + break; + } + case TextElide::ElideMiddle: + { + height = 0.0; + QTextLine lastLineBefore; + QTextLine firstLineAfter; + for (int i = 0; i < (layout.lineCount() / 2) + (layout.lineCount() % 2); ++i) { + qreal lineHeight = 3 * layout.lineAt(i).height(); + if (height + lineHeight > maximumHeight) + break; + height += lineHeight; + + lastLineBefore = layout.lineAt(i); + firstLineAfter = layout.lineAt(layout.lineCount() - i - 1); + } + + int nextPosition = 0; + if (lastLineBefore.isValid()) { + elidedText += text.left(lastLineBefore.textStart() + + lastLineBefore.textLength()); + nextPosition = lastLineBefore.textStart() + lastLineBefore.textLength(); + } + + QString suffix; + int length = text.length() - nextPosition; + if (firstLineAfter.isValid()) { + length = firstLineAfter.textStart() - nextPosition; + suffix = text.mid(firstLineAfter.textStart()); + } + + elidedText += layout.engine()->elidedText( + Qt::ElideMiddle, QFixed::fromReal(width), 0, nextPosition, length); + + elidedText += suffix; + break; + } + case TextElide::ElideNone: + Q_UNREACHABLE(); + }; + + // Failsafe + if (elidedText.isEmpty() || elidedText == text) + needsElide = false; + else + text = elidedText; + } + } while (needsElide); + + QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> glyphsPerTexture; + + float originY = float(height) / 2.0f; + if (textInfo.m_VerticalAlignment == TextVerticalAlignment::Bottom) + originY = float(height); + else if (textInfo.m_VerticalAlignment == TextVerticalAlignment::Top) + originY = 0.0; + + float originX = float(width) / 2.0f; + if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Right) + originX = float(width); + else if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Left) + originX = 0.0; + + float offsetY = -originY; + + QT3DSVec3 minimum(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), 0); + QT3DSVec3 maximum(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), 0); + + // To match the original behavior of the sources, we don't actually align to + // the bounding box. This is only used for word wrapping and elide. Keeping the + // code here in case this was a mistake or we for some other reason want to change + // it. +#if 0 + // If there is no bounding box, then alignmentHeight == height, so we skip it + if (!boundingBox.isNull() && text3DS->verticalAlignment() == Q3DSTextNode::Bottom) + offsetY += float(maximumHeight - height); + else if (!boundingBox.isNull() && text3DS->verticalAlignment() == Q3DSTextNode::Middle) + offsetY += float(maximumHeight / 2.0 - height / 2.0); + float alignmentWidth = boundingBox.isNull() ? float(width) : float(maximumWidth); +#else + float alignmentWidth = float(width); +#endif + + for (int j = 0; j < layout.lineCount(); ++j) { + QTextLine line = layout.lineAt(j); + + float offsetX = -originX; + if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Right) + offsetX += alignmentWidth - float(line.naturalTextWidth()); + else if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Center) + offsetX += alignmentWidth / 2.0f - float(line.naturalTextWidth()) / 2.0f; + + const QList<QGlyphRun> glyphRuns = line.glyphRuns(); + for (const QGlyphRun &glyphRun : glyphRuns) { + const QVector<quint32> glyphIndexes = glyphRun.glyphIndexes(); + const QVector<QPointF> glyphPositions = glyphRun.positions(); + + Q3DSDistanceFieldGlyphCache *cache = m_glyphCacheManager.glyphCache( + glyphRun.rawFont()); + cache->populate(glyphRun.glyphIndexes()); + cache->processPendingGlyphs(); + + qreal fontPixelSize = glyphRun.rawFont().pixelSize(); + + qreal shadowOffsetX = qreal(cache->fontSize()) + * qreal(textInfo.m_DropShadowOffsetX) / 1000.0; + qreal shadowOffsetY = qreal(cache->fontSize()) + * qreal(textInfo.m_DropShadowOffsetY) / 1000.0; + + qreal maxTexMargin = cache->distanceFieldRadius(); + qreal fontScale = cache->fontScale(fontPixelSize); + qreal margin = 2; + qreal texMargin = margin / fontScale; + if (texMargin > maxTexMargin) { + texMargin = maxTexMargin; + margin = maxTexMargin * fontScale; + } + + for (int i = 0; i < glyphIndexes.size(); ++i) { + quint32 glyphIndex = glyphIndexes.at(i); + QPointF position = glyphPositions.at(i); + + QSGDistanceFieldGlyphCache::TexCoord c = cache->glyphTexCoord(glyphIndex); + if (c.isNull()) + continue; + + QSGDistanceFieldGlyphCache::Metrics metrics = cache->glyphMetrics(glyphIndex, + fontPixelSize); + if (metrics.isNull()) + continue; + + metrics.width += margin * 2; + metrics.height += margin * 2; + metrics.baselineX -= margin; + metrics.baselineY += margin; + c.xMargin -= texMargin; + c.yMargin -= texMargin; + c.width += texMargin * 2; + c.height += texMargin * 2; + + float cx1 = float(position.x() + metrics.baselineX) + offsetX; + float cx2 = cx1 + float(metrics.width); + float cy1 = float(position.y() - metrics.baselineY) + offsetY; + float cy2 = cy1 + float(metrics.height); + + if (textInfo.m_DropShadow) { + if (shadowOffsetX < 0.0) + cx1 += float(shadowOffsetX * fontScale); + else + cx2 += float(shadowOffsetX * fontScale); + + if (shadowOffsetY < 0.0) + cy1 += float(shadowOffsetY * fontScale); + else + cy2 += float(shadowOffsetY * fontScale); + } + + cy1 = -cy1; + cy2 = -cy2; + + if (cx1 < minimum.x) + minimum.x = cx1; + else if (cx1 > maximum.x) + maximum.x = cx1; + + if (cx2 < minimum.x) + minimum.x = cx2; + else if (cx2 > maximum.x) + maximum.x = cx2; + + if (cy1 < minimum.y) + minimum.y = cy1; + else if (cy1 > maximum.y) + maximum.y = cy1; + + if (cy2 < minimum.y) + minimum.y = cy2; + else if (cy2 > maximum.y) + maximum.y = cy2; + + if (boundingBox.x() > 0 || boundingBox.y() > 0) { + const float halfWidth = boundingBox.x() / 2.0f; + const float halfHeight = boundingBox.y() / 2.0f; + if (maximum.x < halfWidth) + maximum.x = halfWidth; + if (minimum.x > -halfWidth) + minimum.x = -halfWidth; + if (maximum.y < halfHeight) + maximum.y = halfHeight; + if (minimum.y > -halfHeight) + minimum.y = -halfHeight; + } + + float tx1 = float(c.x + c.xMargin); + float tx2 = tx1 + float(c.width); + float ty1 = float(c.y + c.yMargin); + float ty2 = ty1 + float(c.height); + + // Preserve original bounds of glyphs + float ttx1 = tx1; + float tty1 = ty1; + float ttx2 = tx2; + float tty2 = ty2; + + if (textInfo.m_DropShadow) { + if (shadowOffsetX < 0.0) + tx1 += float(c.width * shadowOffsetX * fontScale) / float(metrics.width); + else + tx2 += float(c.width * shadowOffsetX * fontScale) / float(metrics.width); + + if (shadowOffsetY < 0.0) + ty1 += float(c.height * shadowOffsetY * fontScale) / float(metrics.height); + else + ty2 += float(c.height * shadowOffsetY * fontScale) / float(metrics.height); + } + + const QSGDistanceFieldGlyphCache::Texture *texture + = cache->glyphTexture(glyphIndex); + if (texture->textureId == 0) { + qWarning() << "Empty texture for glyph" << glyphIndex; + continue; + } + + Q3DSDistanceFieldGlyphCache::TextureInfo *textureInfo = cache->textureInfoById( + texture->textureId); + + GlyphInfo &glyphInfo = glyphsPerTexture[textureInfo]; + glyphInfo.fontScale = float(fontScale); + glyphInfo.shadowOffsetX = float(shadowOffsetX); + glyphInfo.shadowOffsetY = float(shadowOffsetY); + glyphInfo.bounds = NVBounds3(minimum, maximum); + + QVector<float> &vertexes = glyphInfo.vertexes; + vertexes.reserve(vertexes.size() + 20 + (textInfo.m_DropShadow ? 16 : 0)); + + vertexes.append(cx1); + vertexes.append(cy1); + vertexes.append(0.0); + vertexes.append(tx1); + vertexes.append(ty1); + + if (textInfo.m_DropShadow) { + vertexes.append(ttx1); + vertexes.append(tty1); + vertexes.append(ttx2); + vertexes.append(tty2); + } + + vertexes.append(cx2); + vertexes.append(cy1); + vertexes.append(0.0); + vertexes.append(tx2); + vertexes.append(ty1); + + if (textInfo.m_DropShadow) { + vertexes.append(ttx1); + vertexes.append(tty1); + vertexes.append(ttx2); + vertexes.append(tty2); + } + + vertexes.append(cx2); + vertexes.append(cy2); + vertexes.append(0.0); + vertexes.append(tx2); + vertexes.append(ty2); + + if (textInfo.m_DropShadow) { + vertexes.append(ttx1); + vertexes.append(tty1); + vertexes.append(ttx2); + vertexes.append(tty2); + } + + vertexes.append(cx1); + vertexes.append(cy2); + vertexes.append(0.0); + vertexes.append(tx1); + vertexes.append(ty2); + + if (textInfo.m_DropShadow) { + vertexes.append(ttx1); + vertexes.append(tty1); + vertexes.append(ttx2); + vertexes.append(tty2); + } + } + } + } + + return glyphsPerTexture; +} + +template <typename T> +static QVector<T> fillIndexBuffer(uint quadCount) +{ + QVector<T> indexes; + + const uint triangleCount = 2 * quadCount; + indexes.resize(3 * int(triangleCount)); + + Q_ASSERT(indexes.size() % 6 == 0); + + for (uint i = 0; i < quadCount; i ++) { + indexes[int(i * 6 + 0)] = T(i * 4 + 0); + indexes[int(i * 6 + 1)] = T(i * 4 + 3); + indexes[int(i * 6 + 2)] = T(i * 4 + 1); + + indexes[int(i * 6 + 3)] = T(i * 4 + 1); + indexes[int(i * 6 + 4)] = T(i * 4 + 3); + indexes[int(i * 6 + 5)] = T(i * 4 + 2); + } + + return indexes; +} + +void Q3DSDistanceFieldRenderer::buildShaders() +{ + IShaderProgramGenerator &gen = m_context->GetShaderProgramGenerator(); + gen.BeginProgram(); + IShaderStageGenerator &vertexGenerator(*gen.GetStage(ShaderGeneratorStages::Vertex)); + IShaderStageGenerator &fragmentGenerator(*gen.GetStage(ShaderGeneratorStages::Fragment)); + + if (m_context->GetRenderContext().GetRenderContextType() == NVRenderContextValues::GLES2) { + vertexGenerator.AddInclude("distancefieldtext.vert"); + fragmentGenerator.AddInclude("distancefieldtext.frag"); + } else { + vertexGenerator.AddInclude("distancefieldtext_core.vert"); + fragmentGenerator.AddInclude("distancefieldtext_core.frag"); + } + + m_shader.program = gen.CompileGeneratedShader("distancefieldtext", + SShaderCacheProgramFlags(), + TShaderFeatureSet(), false); + + if (m_shader.program) { + m_shader.mvp = NVRenderCachedShaderProperty<QT3DSMat44>( + "mvp", *m_shader.program); + m_shader.modelView = NVRenderCachedShaderProperty<QT3DSMat44>( + "modelView", *m_shader.program); + m_shader.textureWidth = NVRenderCachedShaderProperty<QT3DSI32>( + "textureWidth", *m_shader.program); + m_shader.textureHeight = NVRenderCachedShaderProperty<QT3DSI32>( + "textureHeight", *m_shader.program); + m_shader.fontScale = NVRenderCachedShaderProperty<QT3DSF32>( + "fontScale", *m_shader.program); + m_shader.texture = NVRenderCachedShaderProperty<NVRenderTexture2D *>( + "_qt_texture", *m_shader.program); + m_shader.color = NVRenderCachedShaderProperty<QT3DSVec4>( + "color", *m_shader.program); + } + + gen.BeginProgram(); + vertexGenerator = *gen.GetStage(ShaderGeneratorStages::Vertex); + fragmentGenerator = *gen.GetStage(ShaderGeneratorStages::Fragment); + + if (m_context->GetRenderContext().GetRenderContextType() == NVRenderContextValues::GLES2) { + vertexGenerator.AddInclude("distancefieldtext_dropshadow.vert"); + fragmentGenerator.AddInclude("distancefieldtext_dropshadow.frag"); + } else { + vertexGenerator.AddInclude("distancefieldtext_dropshadow_core.vert"); + fragmentGenerator.AddInclude("distancefieldtext_dropshadow_core.frag"); + } + + m_dropShadowShader.program = gen.CompileGeneratedShader("distancefieldtext_dropshadow", + SShaderCacheProgramFlags(), + TShaderFeatureSet(), false); + + if (m_dropShadowShader.program) { + m_dropShadowShader.mvp = NVRenderCachedShaderProperty<QT3DSMat44>( + "mvp", *m_dropShadowShader.program); + m_dropShadowShader.modelView = NVRenderCachedShaderProperty<QT3DSMat44>( + "modelView", *m_dropShadowShader.program); + m_dropShadowShader.textureWidth = NVRenderCachedShaderProperty<QT3DSI32>( + "textureWidth", *m_dropShadowShader.program); + m_dropShadowShader.textureHeight = NVRenderCachedShaderProperty<QT3DSI32>( + "textureHeight", *m_dropShadowShader.program); + m_dropShadowShader.fontScale = NVRenderCachedShaderProperty<QT3DSF32>( + "fontScale", *m_dropShadowShader.program); + m_dropShadowShader.shadowOffset = NVRenderCachedShaderProperty<QT3DSVec2>( + "shadowOffset", *m_dropShadowShader.program); + m_dropShadowShader.texture = NVRenderCachedShaderProperty<NVRenderTexture2D *>( + "_qt_texture", *m_dropShadowShader.program); + m_dropShadowShader.color = NVRenderCachedShaderProperty<QT3DSVec4>( + "color", *m_dropShadowShader.program); + m_dropShadowShader.shadowColor = NVRenderCachedShaderProperty<QT3DSVec4>( + "shadowColor", *m_dropShadowShader.program); + } +} + +Q3DSDistanceFieldMesh Q3DSDistanceFieldRenderer::buildMesh(const GlyphInfo &glyphInfo, + bool shadow) +{ + static NVRenderVertexBufferEntry entries[] = { + NVRenderVertexBufferEntry("vCoord", NVRenderComponentTypes::QT3DSF32, 3), + NVRenderVertexBufferEntry("tCoord", NVRenderComponentTypes::QT3DSF32, 2, 3 * sizeof(float)) + }; + + static NVRenderVertexBufferEntry shadowEntries[] = { + NVRenderVertexBufferEntry("vCoord", NVRenderComponentTypes::QT3DSF32, 3), + NVRenderVertexBufferEntry("tCoord", NVRenderComponentTypes::QT3DSF32, 2, + 3 * sizeof(float)), + NVRenderVertexBufferEntry("textureBounds", NVRenderComponentTypes::QT3DSF32, 4, + 5 * sizeof(float)) + }; + + const uint floatsPerVertex = 3 + 2 + (shadow ? 4 : 0); + const uint stride = floatsPerVertex * sizeof(float); + const uint offset = 0; + + NVRenderContext &renderContext = m_context->GetRenderContext(); + QVector<float> vertexes = glyphInfo.vertexes; + + Q_ASSERT(uint(vertexes.size()) % floatsPerVertex == 0); + const uint vertexCount = uint(vertexes.size()) / floatsPerVertex; + + Q_ASSERT(vertexCount % 4 == 0); + const uint quadCount = vertexCount / 4; + + Q3DSDistanceFieldMesh mesh; + + mesh.attribLayout = renderContext.CreateAttributeLayout( + toConstDataRef(shadow ? shadowEntries : entries, shadow ? 3 : 2)); + mesh.vertexBuffer = renderContext.CreateVertexBuffer( + NVRenderBufferUsageType::Static, size_t(vertexes.size()) * sizeof(float), stride, + toU8DataRef(vertexes.begin(), QT3DSU32(vertexes.size()))); + + if (vertexCount <= 0xffff) { + QVector<QT3DSU16> indexes = fillIndexBuffer<QT3DSU16>(quadCount); + mesh.indexBuffer = renderContext.CreateIndexBuffer( + NVRenderBufferUsageType::Static, NVRenderComponentTypes::QT3DSU16, + size_t(indexes.size()) * sizeof(QT3DSU16), + toU8DataRef(indexes.begin(), QT3DSU32(indexes.size()))); + } else { + QVector<QT3DSU32> indexes = fillIndexBuffer<QT3DSU32>(quadCount); + mesh.indexBuffer = renderContext.CreateIndexBuffer( + NVRenderBufferUsageType::Static, NVRenderComponentTypes::QT3DSU32, + size_t(indexes.size()) * sizeof(QT3DSU32), + toU8DataRef(indexes.begin(), QT3DSU32(indexes.size()))); + } + + mesh.inputAssembler = renderContext.CreateInputAssembler( + mesh.attribLayout, toConstDataRef(&mesh.vertexBuffer, 1), mesh.indexBuffer, + toConstDataRef(&stride, 1), toConstDataRef(&offset, 1)); + + return mesh; +} + +void Q3DSDistanceFieldRenderer::renderMesh( + NVRenderInputAssembler *inputAssembler, NVRenderTexture2D *texture, const QT3DSMat44 &mvp, + const QT3DSMat44 &modelView, QT3DSI32 textureWidth, QT3DSI32 textureHeight, + QT3DSF32 fontScale, QT3DSVec4 color) +{ + NVRenderContext &renderContext = m_context->GetRenderContext(); + renderContext.SetCullingEnabled(false); + + renderContext.SetBlendFunction(NVRenderBlendFunctionArgument( + NVRenderSrcBlendFunc::One, + NVRenderDstBlendFunc::OneMinusSrcAlpha, + NVRenderSrcBlendFunc::One, + NVRenderDstBlendFunc::OneMinusSrcAlpha)); + renderContext.SetBlendEquation(NVRenderBlendEquationArgument( + NVRenderBlendEquation::Add, NVRenderBlendEquation::Add)); + + renderContext.SetActiveShader(m_shader.program); + m_shader.mvp.Set(mvp); + m_shader.modelView.Set(modelView); + m_shader.textureWidth.Set(textureWidth); + m_shader.textureHeight.Set(textureHeight); + m_shader.fontScale.Set(fontScale); + m_shader.texture.Set(texture); + m_shader.color.Set(color); + + renderContext.SetInputAssembler(inputAssembler); + renderContext.Draw(NVRenderDrawMode::Triangles, inputAssembler->GetIndexCount(), 0); +} + +void Q3DSDistanceFieldRenderer::renderMeshWithDropShadow( + NVRenderInputAssembler *inputAssembler, NVRenderTexture2D *texture, const QT3DSMat44 &mvp, + const QT3DSMat44 &modelView, QT3DSI32 textureWidth, QT3DSI32 textureHeight, + QT3DSF32 fontScale, QT3DSVec2 shadowOffset, QT3DSVec4 color, QT3DSVec4 shadowColor) +{ + NVRenderContext &renderContext = m_context->GetRenderContext(); + renderContext.SetCullingEnabled(false); + + renderContext.SetBlendFunction(NVRenderBlendFunctionArgument( + NVRenderSrcBlendFunc::One, + NVRenderDstBlendFunc::OneMinusSrcAlpha, + NVRenderSrcBlendFunc::One, + NVRenderDstBlendFunc::OneMinusSrcAlpha)); + renderContext.SetBlendEquation(NVRenderBlendEquationArgument( + NVRenderBlendEquation::Add, NVRenderBlendEquation::Add)); + + renderContext.SetActiveShader(m_dropShadowShader.program); + m_dropShadowShader.mvp.Set(mvp); + m_dropShadowShader.modelView.Set(modelView); + m_dropShadowShader.textureWidth.Set(textureWidth); + m_dropShadowShader.textureHeight.Set(textureHeight); + m_dropShadowShader.fontScale.Set(fontScale); + m_dropShadowShader.shadowOffset.Set(shadowOffset); + m_dropShadowShader.texture.Set(texture); + m_dropShadowShader.color.Set(color); + m_dropShadowShader.shadowColor.Set(shadowColor); + + renderContext.SetInputAssembler(inputAssembler); + renderContext.Draw(NVRenderDrawMode::Triangles, inputAssembler->GetIndexCount(), 0); +} + +namespace std { +template<class T> +struct hash<QVector<T>> +{ + size_t operator()(const QVector<T> &s) const + { + return qHash(s); + } +}; + +template<> +struct hash<TextHorizontalAlignment::Enum> +{ + size_t operator()(const TextHorizontalAlignment::Enum& s) const + { + return qHash(s); + } +}; + +template<> +struct hash<TextVerticalAlignment::Enum> +{ + size_t operator()(const TextVerticalAlignment::Enum& s) const + { + return qHash(s); + } +}; + +template<> +struct hash<TextElide::Enum> +{ + size_t operator()(const TextElide::Enum& s) const + { + return qHash(s); + } +}; + +template<> +struct hash<TextWordWrap::Enum> +{ + size_t operator()(const TextWordWrap::Enum& s) const + { + return qHash(s); + } +}; +} + +// Copied from boost +template <class T> +inline void hashCombine(std::size_t &seed, const T &v) +{ + std::hash<T> hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +size_t getTextHashValue(const SText &text) +{ + size_t hashValue = 0; + hashCombine(hashValue, text.m_TextColor.x); + hashCombine(hashValue, text.m_TextColor.y); + hashCombine(hashValue, text.m_TextColor.z); + hashCombine(hashValue, std::string(text.m_Font.c_str())); + hashCombine(hashValue, std::string(text.m_Text.c_str())); + hashCombine(hashValue, text.m_Elide); + hashCombine(hashValue, text.m_Leading); + hashCombine(hashValue, text.m_FontSize); + hashCombine(hashValue, text.m_Tracking); + hashCombine(hashValue, text.m_WordWrap); + hashCombine(hashValue, text.m_DropShadow); + hashCombine(hashValue, text.m_BoundingBox.x); + hashCombine(hashValue, text.m_BoundingBox.y); + hashCombine(hashValue, text.m_DropShadowOffsetX); + hashCombine(hashValue, text.m_DropShadowOffsetY); + hashCombine(hashValue, text.m_DropShadowStrength); + hashCombine(hashValue, text.m_VerticalAlignment); + hashCombine(hashValue, text.m_HorizontalAlignment); + hashCombine(hashValue, text.m_DropShadowVerticalAlignment); + hashCombine(hashValue, text.m_DropShadowHorizontalAlignment); + return hashValue; +} + +size_t getGlyphHashValue(const GlyphInfo &glyph) +{ + size_t hashValue = 0; + hashCombine(hashValue, glyph.vertexes); + hashCombine(hashValue, glyph.glyphIndexes); + hashCombine(hashValue, glyph.fontScale); + hashCombine(hashValue, glyph.shadowOffsetX); + hashCombine(hashValue, glyph.shadowOffsetY); + return hashValue; +} + +void Q3DSDistanceFieldRenderer::renderText(SText &text, const QT3DSMat44 &mvp, + const QT3DSMat44 &modelView) +{ + if (!m_shader.program) + buildShaders(); + + int shadowRgb = int(100 - int(text.m_DropShadowStrength)); + QT3DSVec4 shadowColor(shadowRgb * 0.01f, shadowRgb * 0.01f, shadowRgb * 0.01f, 1); + + size_t textHashValue = getTextHashValue(text); + if (!m_glyphCache.contains(textHashValue)) + m_glyphCache[textHashValue] = buildGlyphsPerTexture(text); + + QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> &glyphsPerTexture + = m_glyphCache[textHashValue]; + + QT3DSVec3 minimum(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), 0); + QT3DSVec3 maximum(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), 0); + + QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>::const_iterator it; + for (it = glyphsPerTexture.constBegin(); it != glyphsPerTexture.constEnd(); ++it) { + const GlyphInfo &glyphInfo = it.value(); + + if (glyphInfo.bounds.minimum.x < minimum.x) + minimum.x = glyphInfo.bounds.minimum.x; + if (glyphInfo.bounds.minimum.y < minimum.y) + minimum.y = glyphInfo.bounds.minimum.y; + if (glyphInfo.bounds.minimum.z < minimum.z) + minimum.z = glyphInfo.bounds.minimum.z; + + if (glyphInfo.bounds.maximum.x > maximum.x) + maximum.x = glyphInfo.bounds.maximum.x; + if (glyphInfo.bounds.maximum.y > maximum.y) + maximum.y = glyphInfo.bounds.maximum.y; + if (glyphInfo.bounds.maximum.z > maximum.z) + maximum.z = glyphInfo.bounds.maximum.z; + + size_t glyphHashValue = getGlyphHashValue(glyphInfo); + + if (!m_meshCache.contains(glyphHashValue)) + m_meshCache[glyphHashValue] = buildMesh(glyphInfo, text.m_DropShadow); + + Q3DSDistanceFieldMesh &mesh = m_meshCache[glyphHashValue]; + + STextureDetails textureDetails = it.key()->texture->GetTextureDetails(); + + if (text.m_DropShadow) { + renderMeshWithDropShadow(mesh.inputAssembler, it.key()->texture, mvp, modelView, + int(textureDetails.m_Width), int(textureDetails.m_Height), + glyphInfo.fontScale * float(m_pixelRatio), + QT3DSVec2(glyphInfo.shadowOffsetX, glyphInfo.shadowOffsetY), + QT3DSVec4(text.m_TextColor, 1), + shadowColor); + } else { + renderMesh(mesh.inputAssembler, it.key()->texture, mvp, modelView, + int(textureDetails.m_Width), int(textureDetails.m_Height), + glyphInfo.fontScale * float(m_pixelRatio), + QT3DSVec4(text.m_TextColor, 1)); + } + + m_renderedGlyphs += glyphHashValue; + } + + text.m_Bounds = NVBounds3(minimum, maximum); +} + +void Q3DSDistanceFieldRenderer::renderTextDepth(SText &text, const QT3DSMat44 &mvp, + const QT3DSMat44 &modelView) +{ + // TODO: Create a depth pass shader for distance field text + renderText(text, mvp, modelView); +} + +void Q3DSDistanceFieldRenderer::setContext(IQt3DSRenderContext &context) +{ + m_context = &context; + m_glyphCacheManager.setContext(context); +} + +ITextRendererCore &ITextRendererCore::createDistanceFieldRenderer(NVFoundationBase &fnd) +{ + return *QT3DS_NEW(fnd.getAllocator(), Q3DSDistanceFieldRenderer)(fnd); +} + +// Unused methods: + +void Q3DSDistanceFieldRenderer::PreloadFonts() +{ + Q_ASSERT(false); +} + +void Q3DSDistanceFieldRenderer::BeginPreloadFonts(IThreadPool &, IPerfTimer &) +{ + Q_ASSERT(false); +} + +void Q3DSDistanceFieldRenderer::EndPreloadFonts() +{ + Q_ASSERT(false); +} + +void Q3DSDistanceFieldRenderer::ReloadFonts() +{ + Q_ASSERT(false); +} + +NVConstDataRef<SRendererFontEntry> Q3DSDistanceFieldRenderer::GetProjectFontList() +{ + Q_ASSERT(false); + return NVConstDataRef<SRendererFontEntry>(); +} + +Option<CRegisteredString> Q3DSDistanceFieldRenderer::GetFontNameForFont(CRegisteredString) +{ + Q_ASSERT(false); + return Option<CRegisteredString>(); +} + +Option<CRegisteredString> Q3DSDistanceFieldRenderer::GetFontNameForFont(const char8_t *) +{ + Q_ASSERT(false); + return Option<CRegisteredString>(); +} + +STextDimensions Q3DSDistanceFieldRenderer::MeasureText(const STextRenderInfo &, QT3DSF32, + const char8_t *) +{ + Q_ASSERT(false); + return STextDimensions(); +} + +STextTextureDetails Q3DSDistanceFieldRenderer::RenderText(const STextRenderInfo &, + NVRenderTexture2D &) +{ + Q_ASSERT(false); + return STextTextureDetails(); +} + +STextTextureDetails Q3DSDistanceFieldRenderer::RenderText( + const STextRenderInfo &, NVRenderPathFontItem &, NVRenderPathFontSpecification &) +{ + Q_ASSERT(false); + return STextTextureDetails(); +} + +SRenderTextureAtlasDetails Q3DSDistanceFieldRenderer::RenderText(const STextRenderInfo &) +{ + Q_ASSERT(false); + return SRenderTextureAtlasDetails(); +} + +void Q3DSDistanceFieldRenderer::BeginFrame() +{ + Q_ASSERT(false); +} + +QT3DSI32 Q3DSDistanceFieldRenderer::CreateTextureAtlas() +{ + Q_ASSERT(false); + return 0; +} + +STextTextureAtlasEntryDetails Q3DSDistanceFieldRenderer::RenderAtlasEntry(QT3DSU32, + NVRenderTexture2D &) +{ + Q_ASSERT(false); + return STextTextureAtlasEntryDetails(); +} + +#endif diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h new file mode 100644 index 00000000..0e037fd8 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QT3DSDISTANCEFIELDRENDERER_H +#define QT3DSDISTANCEFIELDRENDERER_H + +#include "Qt3DSFontDatabase_p.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +#include "Qt3DSTextRenderer.h" +#include "Qt3DSRenderText.h" +#include "render/Qt3DSRenderShaderProgram.h" + +#include "Qt3DSDistanceFieldGlyphCacheManager_p.h" +#include "Qt3DSDistanceFieldGlyphCache_p.h" + +namespace qt3ds { +namespace render { + +struct GlyphInfo { + QVector<float> vertexes; + QVector<quint32> glyphIndexes; + Q3DSDistanceFieldGlyphCache *cache; + float fontScale; + float shadowOffsetX; + float shadowOffsetY; + NVBounds3 bounds; +}; + +struct Q3DSDistanceFieldShader { + NVRenderShaderProgram *program = nullptr; + NVRenderCachedShaderProperty<QT3DSMat44> mvp; + NVRenderCachedShaderProperty<QT3DSMat44> modelView; + NVRenderCachedShaderProperty<QT3DSI32> textureWidth; + NVRenderCachedShaderProperty<QT3DSI32> textureHeight; + NVRenderCachedShaderProperty<QT3DSF32> fontScale; + NVRenderCachedShaderProperty<NVRenderTexture2D *> texture; + NVRenderCachedShaderProperty<QT3DSVec4> color; +}; + +struct Q3DSDistanceFieldDropShadowShader { + NVRenderShaderProgram *program = nullptr; + NVRenderCachedShaderProperty<QT3DSMat44> mvp; + NVRenderCachedShaderProperty<QT3DSMat44> modelView; + NVRenderCachedShaderProperty<QT3DSI32> textureWidth; + NVRenderCachedShaderProperty<QT3DSI32> textureHeight; + NVRenderCachedShaderProperty<QT3DSF32> fontScale; + NVRenderCachedShaderProperty<QT3DSVec2> shadowOffset; + NVRenderCachedShaderProperty<NVRenderTexture2D *> texture; + NVRenderCachedShaderProperty<QT3DSVec4> color; + NVRenderCachedShaderProperty<QT3DSVec4> shadowColor; +}; + +struct Q3DSDistanceFieldMesh +{ + NVRenderAttribLayout *attribLayout = nullptr; + NVRenderVertexBuffer *vertexBuffer = nullptr; + NVRenderIndexBuffer *indexBuffer = nullptr; + NVRenderInputAssembler *inputAssembler = nullptr; +}; + +class Q3DSDistanceFieldRenderer : public ITextRenderer +{ +public: + Q3DSDistanceFieldRenderer(NVFoundationBase &foundation); + ~Q3DSDistanceFieldRenderer() override; + QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> buildGlyphsPerTexture( + const SText &textInfo); + void buildShaders(); + Q3DSDistanceFieldMesh buildMesh(const GlyphInfo &glyphInfo, bool shadow); + void renderMesh(NVRenderInputAssembler *inputAssembler, + NVRenderTexture2D *texture, const QT3DSMat44 &mvp, + const QT3DSMat44 &modelView, QT3DSI32 textureWidth, + QT3DSI32 textureHeight, QT3DSF32 fontScale, QT3DSVec4 color); + void renderMeshWithDropShadow(NVRenderInputAssembler *inputAssembler, + NVRenderTexture2D *texture, const QT3DSMat44 &mvp, + const QT3DSMat44 &modelView, QT3DSI32 textureWidth, + QT3DSI32 textureHeight, QT3DSF32 fontScale, + QT3DSVec2 shadowOffset, QT3DSVec4 color, QT3DSVec4 shadowColor); + void renderText(SText &text, const QT3DSMat44 &mvp, const QT3DSMat44 &modelView); + void renderTextDepth(SText &text, const QT3DSMat44 &mvp, const QT3DSMat44 &modelView); + void setContext(IQt3DSRenderContext &context); + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_foundation.getAllocator()) + + void AddSystemFontDirectory(const char8_t *dir) override; + void AddProjectFontDirectory(const char8_t *dir) override; + void ClearProjectFontDirectories() override; + ITextRenderer &GetTextRenderer(NVRenderContext &) override; + void EndFrame() override; + + // Unused methods: + void PreloadFonts() override; + void BeginPreloadFonts(IThreadPool &, IPerfTimer &) override; + void EndPreloadFonts() override; + void ReloadFonts() override; + + NVConstDataRef<SRendererFontEntry> GetProjectFontList() override; + + Option<CRegisteredString> GetFontNameForFont(CRegisteredString) override; + Option<CRegisteredString> GetFontNameForFont(const char8_t *) override; + + STextDimensions MeasureText(const STextRenderInfo &, QT3DSF32, + const char8_t *) override; + + STextTextureDetails RenderText(const STextRenderInfo &, + NVRenderTexture2D &) override; + + STextTextureDetails RenderText(const STextRenderInfo &, NVRenderPathFontItem &, + NVRenderPathFontSpecification &) override; + + SRenderTextureAtlasDetails RenderText(const STextRenderInfo &) override; + + void BeginFrame() override; + + QT3DSI32 CreateTextureAtlas() override; + STextTextureAtlasEntryDetails RenderAtlasEntry(QT3DSU32, NVRenderTexture2D &) override; + +private: + IQt3DSRenderContext *m_context = nullptr; + QHash<size_t, QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>> m_glyphCache; + QHash<size_t, Q3DSDistanceFieldMesh> m_meshCache; + + Q3DSFontDatabase m_fontDatabase; + Q3DSDistanceFieldGlyphCacheManager m_glyphCacheManager; + + Q3DSDistanceFieldShader m_shader; + Q3DSDistanceFieldDropShadowShader m_dropShadowShader; + QVector<size_t> m_renderedGlyphs; + + QStringList m_systemDirs; + QStringList m_projectDirs; + + qreal m_pixelRatio = 0.0; + + NVFoundationBase &m_foundation; + volatile QT3DSI32 mRefCount = 0; +}; + +} +} + +#endif // Qt version check + +#endif // QT3DSDISTANCEFIELDRENDERER_H diff --git a/src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp new file mode 100644 index 00000000..b44cdbd1 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 "Qt3DSFontDatabase_p.h" + +#include <QtCore/qdir.h> +#include "qloggingcategory.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +QT_BEGIN_NAMESPACE + +Q3DSFontDatabase::Q3DSFontDatabase() +{ +} + +void Q3DSFontDatabase::registerFonts(const QStringList &directories) +{ + const QStringList nameFilters = { QStringLiteral("*.ttf"), QStringLiteral("*.otf") }; + for (const QString &directory : directories) { + QDir fontDir(directory); + if (!fontDir.exists()) { + qWarning("Attempted to register invalid font directory: %s", + qPrintable(directory)); + continue; + } + + const QFileInfoList entryInfoList = fontDir.entryInfoList(nameFilters); + for (const QFileInfo &entryInfo : entryInfoList) { + QRawFont font(entryInfo.absoluteFilePath(), 16); + if (!font.isValid()) { + qWarning("Invalid font file: %s", qPrintable(entryInfo.absoluteFilePath())); + continue; + } + + // ### Only support scalable fonts + + QString fontId = entryInfo.baseName(); + if (std::find_if(m_fonts.constBegin(), m_fonts.constEnd(), + [fontId](const Font &f) { return f.fontId == fontId; }) + != m_fonts.constEnd()) { + // already registered + continue; + } + + m_fonts.append(Font(fontId, entryInfo.absoluteFilePath())); + } + } +} + +void Q3DSFontDatabase::unregisterFonts(const QStringList &directories) +{ + for (const QString &directory : directories) { + QDir dir(directory); + QVector<Font>::iterator it = m_fonts.begin(); + while (it != m_fonts.end()) { + if (dir == QDir(QFileInfo(it->filePath).absolutePath())) + it = m_fonts.erase(it); + else + ++it; + } + } +} + +QRawFont Q3DSFontDatabase::findFont(const QString &fontId) +{ + for (Font &font : m_fonts) { + if (font.fontId == fontId) { + if (!font.rawFont.isValid()) + font.rawFont = QRawFont(font.filePath, 16); + return font.rawFont; + } + } + + return QRawFont::fromFont(QFont()); +} + +QT_END_NAMESPACE + +#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2) diff --git a/src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h new file mode 100644 index 00000000..73e03624 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef Q3DSFONTDATABASE_P_H +#define Q3DSFONTDATABASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvector.h> +#include <QtGui/qrawfont.h> + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +QT_BEGIN_NAMESPACE + +class Q3DSSceneManager; +class Q3DSFontDatabase +{ +public: + Q3DSFontDatabase(); + + void registerFonts(const QStringList &directories); + void unregisterFonts(const QStringList &directories); + + QRawFont findFont(const QString &fontId); + +private: + struct Font { + Font() = default; + Font(const QString &id, const QString &path) + : fontId(id) + , filePath(path) + {} + + QString fontId; + QString filePath; + QRawFont rawFont; + }; + + QVector<Font> m_fonts; +}; + +QT_END_NAMESPACE + +#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2) + +#endif // Q3DSFONTDATABASE_P_H diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp index e0eedfd3..3c940805 100644 --- a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp +++ b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp @@ -65,6 +65,7 @@ #include "Qt3DSRenderShaderCodeGeneratorV2.h" #include "Qt3DSRenderDefaultMaterialShaderGenerator.h" #include "Qt3DSRenderCustomMaterialShaderGenerator.h" +#include "Qt3DSDistanceFieldRenderer.h" using namespace qt3ds::render; @@ -85,6 +86,7 @@ struct SRenderContextCore : public IQt3DSRenderContextCore NVScopedRefCounted<ITextRendererCore> m_TextRenderer; NVScopedRefCounted<ITextRendererCore> m_OnscreenTexRenderer; NVScopedRefCounted<IPathManagerCore> m_PathManagerCore; + NVScopedRefCounted<ITextRendererCore> m_distanceFieldRenderer; QT3DSI32 mRefCount; SRenderContextCore(NVFoundationBase &fnd, IStringTable &strTable) @@ -127,6 +129,11 @@ struct SRenderContextCore : public IQt3DSRenderContextCore const char8_t *inPrimitivesDirectory) override; void SetTextRendererCore(ITextRendererCore &inRenderer) override { m_TextRenderer = inRenderer; } ITextRendererCore *GetTextRendererCore() override { return m_TextRenderer.mPtr; } + void setDistanceFieldRenderer(ITextRendererCore &inRenderer) override + { + m_distanceFieldRenderer = inRenderer; + } + ITextRendererCore *getDistanceFieldRenderer() override { return m_distanceFieldRenderer.mPtr; } void SetOnscreenTextRendererCore(ITextRendererCore &inRenderer) override { m_OnscreenTexRenderer = inRenderer; @@ -213,6 +220,7 @@ struct SRenderContext : public IQt3DSRenderContext NVScopedRefCounted<IOffscreenRenderManager> m_OffscreenRenderManager; NVScopedRefCounted<IQt3DSRenderer> m_Renderer; NVScopedRefCounted<ITextRenderer> m_TextRenderer; + NVScopedRefCounted<ITextRenderer> m_distanceFieldRenderer; NVScopedRefCounted<ITextRenderer> m_OnscreenTextRenderer; NVScopedRefCounted<ITextTextureCache> m_TextTextureCache; NVScopedRefCounted<ITextTextureAtlas> m_TextTextureAtlas; @@ -307,6 +315,15 @@ struct SRenderContext : public IQt3DSRenderContext m_RenderContext->GetFoundation(), *m_TextRenderer, *m_RenderContext); } +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + ITextRendererCore *distanceFieldRenderer = inCore.getDistanceFieldRenderer(); + if (distanceFieldRenderer) { + m_distanceFieldRenderer = distanceFieldRenderer->GetTextRenderer(ctx); + static_cast<Q3DSDistanceFieldRenderer *>(m_distanceFieldRenderer.mPtr) + ->setContext(*this); + } +#endif + ITextRendererCore *theOnscreenTextCore = inCore.GetOnscreenTextRendererCore(); if (theOnscreenTextCore) { m_OnscreenTextRenderer = theOnscreenTextCore->GetTextRenderer(ctx); @@ -381,6 +398,8 @@ struct SRenderContext : public IQt3DSRenderContext ITextRenderer *GetTextRenderer() override { return m_TextRenderer; } + ITextRenderer *getDistanceFieldRenderer() override { return m_distanceFieldRenderer; } + ITextRenderer *GetOnscreenTextRenderer() override { return m_OnscreenTextRenderer; } void SetSceneColor(Option<QT3DSVec4> inSceneColor) override { m_SceneColor = inSceneColor; } @@ -797,6 +816,8 @@ struct SRenderContext : public IQt3DSRenderContext m_TextTextureCache->EndFrame(); if (m_TextRenderer) m_TextRenderer->EndFrame(); + if (m_distanceFieldRenderer) + m_distanceFieldRenderer->EndFrame(); m_OffscreenRenderManager->EndFrame(); m_Renderer->EndFrame(); m_CustomMaterialSystem->EndFrame(); diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h index be1498b8..012864ba 100644 --- a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h +++ b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h @@ -70,6 +70,8 @@ namespace render { // Text renderers may be provided by clients at runtime. virtual void SetTextRendererCore(ITextRendererCore &inRenderer) = 0; virtual ITextRendererCore *GetTextRendererCore() = 0; + virtual void setDistanceFieldRenderer(ITextRendererCore &inRenderer) = 0; + virtual ITextRendererCore *getDistanceFieldRenderer() = 0; // this is our default 2D text onscreen renderer virtual void SetOnscreenTextRendererCore(ITextRendererCore &inRenderer) = 0; virtual ITextRendererCore *GetOnscreenTextRendererCore() = 0; @@ -106,6 +108,7 @@ namespace render { virtual IPerfTimer &GetPerfTimer() = 0; virtual ITextTextureCache *GetTextureCache() = 0; virtual ITextRenderer *GetTextRenderer() = 0; + virtual ITextRenderer *getDistanceFieldRenderer() = 0; virtual IRenderList &GetRenderList() = 0; virtual IPathManager &GetPathManager() = 0; virtual IShaderProgramGenerator &GetShaderProgramGenerator() = 0; diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderer.h b/src/Runtime/Source/runtimerender/Qt3DSRenderer.h index 360ebf48..69a49942 100644 --- a/src/Runtime/Source/runtimerender/Qt3DSRenderer.h +++ b/src/Runtime/Source/runtimerender/Qt3DSRenderer.h @@ -182,7 +182,8 @@ namespace render { // Testing function to allow clients to render a layer using a custom view project instead // of the one that would be setup // using the layer's camera in conjunction with the layer's position,scale. - virtual void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inViewProjection) = 0; + virtual void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inProjection, + const QT3DSMat44 &inViewProjection) = 0; // This allocator is cleared every frame on BeginFrame. Objects constructed using this // allocator diff --git a/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h b/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h index 756c3a90..65b7cb00 100644 --- a/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h +++ b/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h @@ -79,6 +79,10 @@ namespace render { static ITextRendererCore &CreateQtTextRenderer(NVFoundationBase &inFoundation, IStringTable &inStrTable); +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + static ITextRendererCore &createDistanceFieldRenderer(NVFoundationBase &inFnd); +#endif + // call this to create onscreen text renderer // it needs true type fonts static ITextRendererCore &CreateOnscreenTextRenderer(NVFoundationBase &inFoundation); diff --git a/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp b/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp index 8b44e00f..e4694c56 100644 --- a/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp +++ b/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp @@ -67,10 +67,5 @@ SText::SText() NVBounds3 SText::GetTextBounds() const { - NVBounds3 retval; - retval.setEmpty(); - if (m_TextTexture != NULL) { - retval.include(m_Bounds); - } - return retval; + return m_Bounds; } diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp index 7d8054f7..24684861 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp @@ -430,6 +430,18 @@ namespace render { } } +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + void SDistanceFieldRenderable::Render(const QT3DSVec2 &inCameraVec) + { + m_distanceFieldText.renderText(m_text, m_mvp, m_modelView); + } + + void SDistanceFieldRenderable::RenderDepthPass(const QT3DSVec2 &inCameraVec) + { + m_distanceFieldText.renderTextDepth(m_text, m_mvp, m_modelView); + } +#endif + void SCustomMaterialRenderable::Render(const QT3DSVec2 & /*inCameraVec*/, const SLayerRenderData &inLayerData, const SLayer &inLayer, NVDataRef<SLight *> inLights, diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h index d380b850..deca9c47 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h @@ -42,6 +42,7 @@ #include "Qt3DSRenderShaderCache.h" #include "foundation/Qt3DSInvasiveLinkedList.h" #include "Qt3DSRenderableImage.h" +#include "Qt3DSDistanceFieldRenderer.h" namespace qt3ds { namespace render { @@ -59,7 +60,8 @@ namespace render { CustomMaterialMeshSubset = 1 << 7, HasRefraction = 1 << 8, Path = 1 << 9, - ShadowCaster = 1 << 10 + ShadowCaster = 1 << 10, + DistanceField = 1 << 11, }; }; @@ -129,6 +131,16 @@ namespace render { void SetText(bool inText) { ClearOrSet(inText, RenderPreparationResultFlagValues::Text); } bool IsText() const { return this->operator&(RenderPreparationResultFlagValues::Text); } + void setDistanceField(bool inText) + { + ClearOrSet(inText, RenderPreparationResultFlagValues::DistanceField); + } + + bool isDistanceField() const + { + return this->operator&(RenderPreparationResultFlagValues::DistanceField); + } + void SetCustom(bool inCustom) { ClearOrSet(inCustom, RenderPreparationResultFlagValues::Custom); @@ -296,6 +308,7 @@ namespace render { m_RenderableFlags.SetDefaultMaterialMeshSubset(true); m_RenderableFlags.SetCustom(false); m_RenderableFlags.SetText(false); + m_RenderableFlags.setDistanceField(false); } void Render(const QT3DSVec2 &inCameraVec, TShaderFeatureSet inFeatureSet); @@ -374,11 +387,41 @@ namespace render { m_RenderableFlags.SetDefaultMaterialMeshSubset(false); m_RenderableFlags.SetCustom(false); m_RenderableFlags.SetText(true); + m_RenderableFlags.setDistanceField(false); + } + + void Render(const QT3DSVec2 &inCameraVec); + void RenderDepthPass(const QT3DSVec2 &inCameraVec); + }; + +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + struct SDistanceFieldRenderable : public SRenderableObject + { + Q3DSDistanceFieldRenderer &m_distanceFieldText; + QT3DSMat44 m_mvp; + QT3DSMat44 m_modelView; + SText &m_text; + + SDistanceFieldRenderable(SRenderableObjectFlags flags, QT3DSVec3 worldCenterPt, + SText &text, const NVBounds3 &bounds, + const QT3DSMat44 &mvp, const QT3DSMat44 &modelView, + Q3DSDistanceFieldRenderer &distanceFieldText) + : SRenderableObject(flags, worldCenterPt, text.m_GlobalTransform, bounds) + , m_distanceFieldText(distanceFieldText) + , m_mvp(mvp) + , m_modelView(modelView) + , m_text(text) + { + m_RenderableFlags.SetDefaultMaterialMeshSubset(false); + m_RenderableFlags.SetCustom(false); + m_RenderableFlags.SetText(false); + m_RenderableFlags.setDistanceField(true); } void Render(const QT3DSVec2 &inCameraVec); void RenderDepthPass(const QT3DSVec2 &inCameraVec); }; +#endif struct SPathRenderable : public SRenderableObject { diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp index d6fb3b1b..7c2603f6 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp @@ -696,9 +696,19 @@ namespace render { { if (inRenderableObject.m_RenderableFlags.IsText()) { STextRenderable &theRenderable = static_cast<STextRenderable &>(inRenderableObject); - if (&theRenderable.m_Text == &inNode) + if (&theRenderable.m_Text == &inNode) { return inPickRay.GetRelativeXY(inRenderableObject.m_GlobalTransform, inRenderableObject.m_Bounds); + } +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + } else if (inRenderableObject.m_RenderableFlags.isDistanceField()) { + SDistanceFieldRenderable &theRenderable = static_cast<SDistanceFieldRenderable &>( + inRenderableObject); + if (&theRenderable.m_text == &inNode) { + return inPickRay.GetRelativeXY(inRenderableObject.m_GlobalTransform, + inRenderableObject.m_Bounds); + } +#endif } else if (inRenderableObject.m_RenderableFlags.IsDefaultMaterialMeshSubset()) { SSubsetRenderable &theRenderable = static_cast<SSubsetRenderable &>(inRenderableObject); if (&theRenderable.m_ModelContext.m_Model == &inNode) @@ -964,14 +974,15 @@ namespace render { } // This doesn't have to be cheap. - void Qt3DSRendererImpl::RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inViewProjection) + void Qt3DSRendererImpl::RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inProjection, + const QT3DSMat44 &inViewProjection) { SLayerRenderData *theData = GetOrCreateLayerRenderDataForNode(inLayer); if (theData == NULL || theData->m_Camera == NULL) { QT3DS_ASSERT(false); return; } - theData->PrepareAndRender(inViewProjection); + theData->PrepareAndRender(inProjection, inViewProjection); } void Qt3DSRendererImpl::AddRenderWidget(IRenderWidget &inWidget) @@ -1356,6 +1367,10 @@ namespace render { &static_cast<SSubsetRenderable *>(&inRenderableObject)->m_ModelContext.m_Model; else if (inRenderableObject.m_RenderableFlags.IsText()) thePickObject = &static_cast<STextRenderable *>(&inRenderableObject)->m_Text; +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + else if (inRenderableObject.m_RenderableFlags.isDistanceField()) + thePickObject = &static_cast<SDistanceFieldRenderable *>(&inRenderableObject)->m_text; +#endif else if (inRenderableObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) thePickObject = &static_cast<SCustomMaterialRenderable *>(&inRenderableObject) ->m_ModelContext.m_Model; diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h index 686e7d3b..e5b39b84 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h @@ -365,7 +365,8 @@ namespace render { Option<NVRenderRectF> GetLayerRect(SLayer &inLayer) override; - void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inViewProjection) override; + void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inProjection, + const QT3DSMat44 &inViewProjection) override; // Note that this allocator is completely reset on BeginFrame. NVAllocatorCallback &GetPerFrameAllocator() override diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp index af3cb613..3fb3bc68 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp @@ -787,11 +787,15 @@ namespace render { const QT3DSVec2 &inCameraProps, TShaderFeatureSet, QT3DSU32, const SCamera &inCamera) { - if (inObject.m_RenderableFlags.IsDefaultMaterialMeshSubset()) + if (inObject.m_RenderableFlags.IsDefaultMaterialMeshSubset()) { static_cast<SSubsetRenderable &>(inObject).RenderDepthPass(inCameraProps); - else if (inObject.m_RenderableFlags.IsText()) + } else if (inObject.m_RenderableFlags.IsText()) { static_cast<STextRenderable &>(inObject).RenderDepthPass(inCameraProps); - else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) { +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + } else if (inObject.m_RenderableFlags.isDistanceField()) { + static_cast<SDistanceFieldRenderable &>(inObject).RenderDepthPass(inCameraProps); +#endif + } else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) { static_cast<SCustomMaterialRenderable &>(inObject).RenderDepthPass( inCameraProps, inData.m_Layer, inData.m_Lights, inCamera, NULL); } else if (inObject.m_RenderableFlags.IsPath()) { @@ -845,6 +849,10 @@ namespace render { static_cast<SSubsetRenderable &>(inObject).Render(inCameraProps, inFeatureSet); else if (inObject.m_RenderableFlags.IsText()) static_cast<STextRenderable &>(inObject).Render(inCameraProps); +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + else if (inObject.m_RenderableFlags.isDistanceField()) + static_cast<SDistanceFieldRenderable &>(inObject).Render(inCameraProps); +#endif else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) { // PKC : Need a better place to do this. SCustomMaterialRenderable &theObject = @@ -1419,6 +1427,15 @@ namespace render { static_cast<STextRenderable &>(*m_TransparentObjects[idx]); OffsetProjectionMatrix(theRenderable.m_ModelViewProjection, theVertexOffsets); +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + } else if (m_TransparentObjects[idx]->m_RenderableFlags + .isDistanceField()) { + SDistanceFieldRenderable &theRenderable + = static_cast<SDistanceFieldRenderable &>( + *m_TransparentObjects[idx]); + OffsetProjectionMatrix(theRenderable.m_mvp, + theVertexOffsets); +#endif } else if (m_TransparentObjects[idx]->m_RenderableFlags.IsPath()) { SPathRenderable &theRenderable = static_cast<SPathRenderable &>(*m_TransparentObjects[idx]); @@ -2140,7 +2157,8 @@ namespace render { m_BoundingRectColor.setEmpty(); } - void SLayerRenderData::PrepareAndRender(const QT3DSMat44 &inViewProjection) + void SLayerRenderData::PrepareAndRender(const QT3DSMat44 &inProjection, + const QT3DSMat44 &inViewProjection) { TRenderableObjectList theTransparentObjects(m_TransparentObjects); TRenderableObjectList theOpaqueObjects(m_OpaqueObjects); @@ -2148,7 +2166,7 @@ namespace render { theOpaqueObjects.clear(); m_ModelContexts.clear(); SLayerRenderPreparationResultFlags theFlags; - PrepareRenderablesForRender(inViewProjection, Empty(), 1.0, theFlags); + PrepareRenderablesForRender(inProjection, inViewProjection, Empty(), 1.0, theFlags); RenderDepthPass(false); Render(); } diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h index 4e237b0b..6716eada 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h @@ -153,7 +153,7 @@ struct AdvancedBlendModes // layer setup system. This assumes the client has setup the viewport, scissor, and render // target // the way they want them. - void PrepareAndRender(const QT3DSMat44 &inViewProjection); + void PrepareAndRender(const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection); SOffscreenRendererEnvironment CreateOffscreenRenderEnvironment() override; IRenderTask &CreateRenderToTextureRunnable() override; diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp index 68ed9907..2940f088 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp @@ -362,8 +362,8 @@ namespace render { } bool SLayerRenderPreparationData::PrepareTextForRender( - SText &inText, const QT3DSMat44 &inViewProjection, QT3DSF32 inTextScaleFactor, - SLayerRenderPreparationResultFlags &ioFlags) + SText &inText, const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection, + QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags) { ITextTextureCache *theTextRenderer = m_Renderer.GetQt3DSContext().GetTextureCache(); if (theTextRenderer == NULL) @@ -378,31 +378,49 @@ namespace render { if (theFlags.IsCompletelyTransparent() == false) { retval = inText.m_Flags.IsDirty() || inText.m_Flags.IsTextDirty(); inText.m_Flags.SetTextDirty(false); - TTPathObjectAndTexture theResult = - theTextRenderer->RenderText(inText, inTextScaleFactor); - inText.m_TextTexture = theResult.second.second.mPtr; - inText.m_TextTextureDetails = theResult.second.first; - inText.m_PathFontItem = theResult.first.second; - inText.m_PathFontDetails = theResult.first.first; - STextScaleAndOffset theScaleAndOffset(*inText.m_TextTexture, - inText.m_TextTextureDetails, inText); - QT3DSVec2 theTextScale(theScaleAndOffset.m_TextScale); - QT3DSVec2 theTextOffset(theScaleAndOffset.m_TextOffset); - QT3DSVec3 minimum(theTextOffset[0] - theTextScale[0], theTextOffset[1] - theTextScale[1], - 0); - QT3DSVec3 maximum(theTextOffset[0] + theTextScale[0], theTextOffset[1] + theTextScale[1], - 0); - inText.m_Bounds = NVBounds3(minimum, maximum); QT3DSMat44 theMVP; QT3DSMat33 theNormalMatrix; inText.CalculateMVPAndNormalMatrix(inViewProjection, theMVP, theNormalMatrix); - if (inText.m_PathFontDetails) - ioFlags.SetRequiresStencilBuffer(true); + SRenderableObject *theRenderable = nullptr; +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + // TODO: Implement clipping for the distance field renderer + if (inText.m_WordWrap != TextWordWrap::Clip || (inText.m_BoundingBox.x == 0.0f + && inText.m_BoundingBox.y == 0.0f)) { + QT3DSMat44 modelView = (inProjection.getInverse() * inViewProjection) + * inText.m_GlobalTransform; + Q3DSDistanceFieldRenderer *distanceFieldText + = static_cast<Q3DSDistanceFieldRenderer *>( + m_Renderer.GetQt3DSContext().getDistanceFieldRenderer()); + theRenderable = RENDER_FRAME_NEW(SDistanceFieldRenderable)( + theFlags, inText.GetGlobalPos(), inText, inText.m_Bounds, theMVP, + modelView, *distanceFieldText); + } else +#endif + { + TTPathObjectAndTexture theResult + = theTextRenderer->RenderText(inText, inTextScaleFactor); + inText.m_TextTexture = theResult.second.second.mPtr; + inText.m_TextTextureDetails = theResult.second.first; + inText.m_PathFontItem = theResult.first.second; + inText.m_PathFontDetails = theResult.first.first; + STextScaleAndOffset theScaleAndOffset(*inText.m_TextTexture, + inText.m_TextTextureDetails, inText); + QT3DSVec2 theTextScale(theScaleAndOffset.m_TextScale); + QT3DSVec2 theTextOffset(theScaleAndOffset.m_TextOffset); + QT3DSVec3 minimum(theTextOffset[0] - theTextScale[0], + theTextOffset[1] - theTextScale[1], 0); + QT3DSVec3 maximum(theTextOffset[0] + theTextScale[0], + theTextOffset[1] + theTextScale[1], 0); + inText.m_Bounds = NVBounds3(minimum, maximum); + + if (inText.m_PathFontDetails) + ioFlags.SetRequiresStencilBuffer(true); - STextRenderable *theRenderable = RENDER_FRAME_NEW(STextRenderable)( - theFlags, inText.GetGlobalPos(), m_Renderer, inText, inText.m_Bounds, theMVP, - inViewProjection, *inText.m_TextTexture, theTextOffset, theTextScale); + theRenderable = RENDER_FRAME_NEW(STextRenderable)( + theFlags, inText.GetGlobalPos(), m_Renderer, inText, inText.m_Bounds, theMVP, + inViewProjection, *inText.m_TextTexture, theTextOffset, theTextScale); + } m_TransparentObjects.push_back(theRenderable); } return retval; @@ -997,11 +1015,13 @@ namespace render { } bool SLayerRenderPreparationData::PrepareRenderablesForRender( - const QT3DSMat44 &inViewProjection, const Option<SClippingFrustum> &inClipFrustum, - QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags) + const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection, + const Option<SClippingFrustum> &inClipFrustum, + QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags) { SStackPerfTimer __timer(m_Renderer.GetQt3DSContext().GetPerfTimer(), "SLayerRenderData::PrepareRenderablesForRender"); + m_projection = inProjection; m_ViewProjection = inViewProjection; QT3DSF32 theTextScaleFactor = inTextScaleFactor; bool wasDataDirty = false; @@ -1025,7 +1045,8 @@ namespace render { SText *theText = static_cast<SText *>(theNode); theText->CalculateGlobalVariables(); if (theText->m_Flags.IsGloballyActive()) { - bool wasTextDirty = PrepareTextForRender(*theText, inViewProjection, + bool wasTextDirty = PrepareTextForRender(*theText, inProjection, + inViewProjection, theTextScaleFactor, ioFlags); wasDataDirty = wasDataDirty || wasTextDirty; } @@ -1344,6 +1365,7 @@ namespace render { QT3DSF32 theTextScaleFactor = 1.0f; if (m_Camera) { + m_projection = m_Camera->m_Projection; m_Camera->CalculateViewProjectionMatrix(m_ViewProjection); theTextScaleFactor = m_Camera->GetTextScaleFactor( thePrepResult.GetLayerToPresentationViewport(), @@ -1359,8 +1381,10 @@ namespace render { // the near plane's bbox edges are calculated in the clipping frustum's // constructor. m_ClippingFrustum = SClippingFrustum(m_ViewProjection, nearPlane); - } else + } else { + m_projection = QT3DSMat44::createIdentity(); m_ViewProjection = QT3DSMat44::createIdentity(); + } // Setup the light directions here. @@ -1372,7 +1396,8 @@ namespace render { m_ModelContexts.clear(); if (GetOffscreenRenderer() == false) { bool renderablesDirty = - PrepareRenderablesForRender(m_ViewProjection, m_ClippingFrustum, + PrepareRenderablesForRender(m_projection, m_ViewProjection, + m_ClippingFrustum, theTextScaleFactor, thePrepResult.m_Flags); wasDataDirty = wasDataDirty || renderablesDirty; if (thePrepResult.m_Flags.RequiresStencilBuffer()) diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h index 5b8d6e10..05923e7f 100644 --- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h +++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h @@ -270,6 +270,7 @@ namespace render { TRenderableObjectList m_RenderedOpaqueObjects; TRenderableObjectList m_RenderedTransparentObjects; QT3DSMat44 m_ViewProjection; + QT3DSMat44 m_projection; SClippingFrustum m_ClippingFrustum; Option<SLayerRenderPreparationResult> m_LayerPrepResult; // Widgets drawn at particular times during the rendering process @@ -324,14 +325,16 @@ namespace render { const Option<SClippingFrustum> &inClipFrustum, TNodeLightEntryList &inScopedLights); - bool PrepareTextForRender(SText &inText, const QT3DSMat44 &inViewProjection, + bool PrepareTextForRender(SText &inText, const QT3DSMat44 &inProjection, + const QT3DSMat44 &inViewProjection, QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags); bool PreparePathForRender(SPath &inPath, const QT3DSMat44 &inViewProjection, const Option<SClippingFrustum> &inClipFrustum, SLayerRenderPreparationResultFlags &ioFlags); // Helper function used during PRepareForRender and PrepareAndRender - bool PrepareRenderablesForRender(const QT3DSMat44 &inViewProjection, + bool PrepareRenderablesForRender(const QT3DSMat44 &inProjection, + const QT3DSMat44 &inViewProjection, const Option<SClippingFrustum> &inClipFrustum, QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags); |