From c6edb9c7d15843e8ab965d365099ace29e2d2049 Mon Sep 17 00:00:00 2001 From: Jere Tuliniemi Date: Mon, 1 Apr 2019 15:08:11 +0300 Subject: Add distance field rendering to OpenGL runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old text rendering remains in the background and is used to render clipped text, since that feature is not yet implemented for distance field fonts. Building the runtime with Qt version older than 5.12.2 also causes a fallback to the old text rendering. Depth pass rendering also needs to be redone in the future to avoid another full render pass. Task-number: QT3DS-3210 Change-Id: Ib7666c437d23ae25e1872682f010df3721476a14 Reviewed-by: Tomi Korpipää --- src/Authoring/Studio/Qt3DStudio.pro | 1 + src/Authoring/Studio/Render/StudioRenderer.cpp | 16 + .../Studio/Render/StudioRendererTranslation.cpp | 3 +- .../Qt3DSRuntimeStatic/Qt3DSRuntimeStatic.pro | 13 +- .../Source/engine/Qt3DSRenderRuntimeBinding.cpp | 7 +- .../Source/engine/Qt3DSRenderRuntimeBindingImpl.h | 5 + src/Runtime/Source/runtime/Qt3DSApplication.cpp | 5 + .../runtimerender/Qt3DSDistanceFieldGlyphCache.cpp | 519 ++++++++++ .../Qt3DSDistanceFieldGlyphCacheManager.cpp | 75 ++ .../Qt3DSDistanceFieldGlyphCacheManager_p.h | 75 ++ .../runtimerender/Qt3DSDistanceFieldGlyphCache_p.h | 100 ++ .../runtimerender/Qt3DSDistanceFieldRenderer.cpp | 1006 ++++++++++++++++++++ .../runtimerender/Qt3DSDistanceFieldRenderer.h | 172 ++++ .../Source/runtimerender/Qt3DSFontDatabase.cpp | 106 +++ .../Source/runtimerender/Qt3DSFontDatabase_p.h | 82 ++ .../runtimerender/Qt3DSRenderContextCore.cpp | 21 + .../Source/runtimerender/Qt3DSRenderContextCore.h | 3 + src/Runtime/Source/runtimerender/Qt3DSRenderer.h | 3 +- .../Source/runtimerender/Qt3DSTextRenderer.h | 4 + .../runtimerender/graphobjects/Qt3DSRenderText.cpp | 7 +- .../rendererimpl/Qt3DSRenderableObjects.cpp | 12 + .../rendererimpl/Qt3DSRenderableObjects.h | 45 +- .../rendererimpl/Qt3DSRendererImpl.cpp | 21 +- .../runtimerender/rendererimpl/Qt3DSRendererImpl.h | 3 +- .../Qt3DSRendererImplLayerRenderData.cpp | 28 +- .../Qt3DSRendererImplLayerRenderData.h | 2 +- ...Qt3DSRendererImplLayerRenderPreparationData.cpp | 79 +- .../Qt3DSRendererImplLayerRenderPreparationData.h | 7 +- src/Runtime/res.qrc | 8 + src/Runtime/res/effectlib/distancefieldtext.frag | 12 + src/Runtime/res/effectlib/distancefieldtext.vert | 66 ++ .../res/effectlib/distancefieldtext_core.frag | 14 + .../res/effectlib/distancefieldtext_core.vert | 44 + .../effectlib/distancefieldtext_dropshadow.frag | 29 + .../effectlib/distancefieldtext_dropshadow.vert | 78 ++ .../distancefieldtext_dropshadow_core.frag | 32 + .../distancefieldtext_dropshadow_core.vert | 54 ++ 37 files changed, 2706 insertions(+), 51 deletions(-) create mode 100644 src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp create mode 100644 src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp create mode 100644 src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h create mode 100644 src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h create mode 100644 src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp create mode 100644 src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h create mode 100644 src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp create mode 100644 src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h create mode 100644 src/Runtime/res/effectlib/distancefieldtext.frag create mode 100644 src/Runtime/res/effectlib/distancefieldtext.vert create mode 100644 src/Runtime/res/effectlib/distancefieldtext_core.frag create mode 100644 src/Runtime/res/effectlib/distancefieldtext_core.vert create mode 100644 src/Runtime/res/effectlib/distancefieldtext_dropshadow.frag create mode 100644 src/Runtime/res/effectlib/distancefieldtext_dropshadow.vert create mode 100644 src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.frag create mode 100644 src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.vert diff --git a/src/Authoring/Studio/Qt3DStudio.pro b/src/Authoring/Studio/Qt3DStudio.pro index c1272d3e..04b8b0ec 100644 --- a/src/Authoring/Studio/Qt3DStudio.pro +++ b/src/Authoring/Studio/Qt3DStudio.pro @@ -21,6 +21,7 @@ win: QMAKE_LFLAGS += /MANIFEST /ENTRY:"wWinMainCRTStartup" QT += core gui xml openglextensions QT += qml quick widgets quickwidgets network +QT += quick-private # Configuration for RT1/RT2 preview. RT2 doesn't work yet so uset RT1. QT += studio3d-private diff --git a/src/Authoring/Studio/Render/StudioRenderer.cpp b/src/Authoring/Studio/Render/StudioRenderer.cpp index c8888c13..d6b5b3b2 100644 --- a/src/Authoring/Studio/Render/StudioRenderer.cpp +++ b/src/Authoring/Studio/Render/StudioRenderer.cpp @@ -318,6 +318,13 @@ struct SRendererImpl : public IStudioRenderer, m_RenderContext->GetRenderContext().GetStringTable())); theCore->SetTextRendererCore(theTextRenderer); +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + ITextRendererCore &distanceFieldRenderer( + ITextRendererCore::createDistanceFieldRenderer( + m_RenderContext->GetRenderContext().GetFoundation())); + theCore->setDistanceFieldRenderer(distanceFieldRenderer); +#endif + m_Context = theCore->CreateRenderContext( m_RenderContext->GetRenderContext(), m_RenderContext->GetRenderContext().GetStringTable().RegisterStr( @@ -704,6 +711,8 @@ struct SRendererImpl : public IStudioRenderer, void SetupTextRenderer() { if (m_Context.mPtr && m_Context->GetTextRenderer()) { + if (m_Context->getDistanceFieldRenderer()) + m_Context->getDistanceFieldRenderer()->ClearProjectFontDirectories(); m_Context->GetTextRenderer()->ClearProjectFontDirectories(); QString projectPath = g_StudioApp.GetCore()->getProjectFile().getProjectPath(); if (!projectPath.isEmpty()) { @@ -715,6 +724,13 @@ struct SRendererImpl : public IStudioRenderer, m_Context->GetStringTable().RegisterStr(thePath.c_str())); m_Context->GetTextRenderer()->AddProjectFontDirectory( m_Context->GetStringTable().RegisterStr(projectPath.toLatin1().data())); + if (m_Context->getDistanceFieldRenderer()) { + m_Context->getDistanceFieldRenderer()->AddSystemFontDirectory( + m_Context->GetStringTable().RegisterStr(thePath.c_str())); + m_Context->getDistanceFieldRenderer()->AddProjectFontDirectory( + m_Context->GetStringTable().RegisterStr( + projectPath.toLatin1().data())); + } } } } diff --git a/src/Authoring/Studio/Render/StudioRendererTranslation.cpp b/src/Authoring/Studio/Render/StudioRendererTranslation.cpp index 8887c571..73981d85 100644 --- a/src/Authoring/Studio/Render/StudioRendererTranslation.cpp +++ b/src/Authoring/Studio/Render/StudioRendererTranslation.cpp @@ -2888,7 +2888,8 @@ void STranslation::RenderZoomRender(SZoomRender &inRender) theRenderContext.SetClearColor(QT3DSVec4(.2f, .2f, .2f, 0.0f)); theRenderContext.Clear(qt3ds::render::NVRenderClearFlags( qt3ds::render::NVRenderClearValues::Color | qt3ds::render::NVRenderClearValues::Depth)); - theRenderer.RunLayerRender(*theLayer, thePickSetup->m_ViewProjection); + theRenderer.RunLayerRender(*theLayer, thePickSetup->m_ProjectionPreMultiply, + thePickSetup->m_ViewProjection); theRenderContext.SetScissorTestEnabled(false); } } diff --git a/src/Runtime/Qt3DSRuntimeStatic/Qt3DSRuntimeStatic.pro b/src/Runtime/Qt3DSRuntimeStatic/Qt3DSRuntimeStatic.pro index 7020f79e..83579651 100644 --- a/src/Runtime/Qt3DSRuntimeStatic/Qt3DSRuntimeStatic.pro +++ b/src/Runtime/Qt3DSRuntimeStatic/Qt3DSRuntimeStatic.pro @@ -15,6 +15,7 @@ linux { DEFINES += QT3DS_BUILDING_LIBRARY QT += qml +QT += quick-private # Foundation SOURCES += \ @@ -162,7 +163,11 @@ SOURCES += \ ../Source/uipparser/Qt3DSUIPParserImpl.cpp \ ../Source/uipparser/Qt3DSUIPParserObjectRefHelper.cpp \ ../Source/runtimerender/Qt3DSRenderContextCore.cpp \ - ../Source/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.cpp + ../Source/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.cpp \ + ../Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp \ + ../Source/runtimerender/Qt3DSFontDatabase.cpp \ + ../Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp \ + ../Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp HEADERS += \ ../Source/foundation/ConvertUTF.h \ @@ -448,7 +453,11 @@ HEADERS += \ ../Source/engine/Qt3DSWindowSystem.h \ ../Source/runtimerender/Qt3DSRenderContextCore.h \ ../Source/runtimerender/Qt3DSRenderLightConstantProperties.h \ - ../Source/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.h + ../Source/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.h \ + ../Source/runtimerender/Include/Qt3DSDistanceFieldRenderer.h \ + ../Source/runtimerender/Include/Qt3DSFontDatabase_p.h \ + ../Source/runtimerender/Include/Qt3DSDistanceFieldGlyphCacheManager_p.h \ + ../Source/runtimerender/Include/Qt3DSDistanceFieldGlyphCache_p.h win32 { SOURCES += \ 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 + +#include +#include +#include + +#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 &glyphs) +{ + m_unusedGlyphs -= glyphs; +} + +void Q3DSDistanceFieldGlyphCache::releaseGlyphs(const QSet &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 &glyphs) +{ + using GlyphTextureHash = QHash >; + 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 &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 glyphPositions; + QVector glyphsToRender; + + if (m_areaAllocator == nullptr) { + m_areaAllocator = new QSGAreaAllocator(QSize(maxTextureSize(), + m_maxTextureCount * maxTextureSize())); + } + + for (QSet::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 + static inline T fetch(const char *data, Offset offset) + { + return qFromBigEndian(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 >; + + 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(qtdfTableStart, Qtdf::majorVersion); + quint8 minorVersion = Qtdf::fetch(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(qtdfTableStart, Qtdf::pixelSize)); + m_maxTextureSize = int(Qtdf::fetch(qtdfTableStart, Qtdf::textureSize)); + m_doubleGlyphResolution = Qtdf::fetch(qtdfTableStart, Qtdf::flags) == 1; + padding = Qtdf::fetch(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(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(textureRecord, Qtdf::allocatedX))); + tex->allocatedArea.setY(int(Qtdf::fetch(textureRecord, Qtdf::allocatedY))); + tex->allocatedArea.setWidth(int(Qtdf::fetch(textureRecord, + Qtdf::allocatedWidth))); + tex->allocatedArea.setHeight(int(Qtdf::fetch(textureRecord, + Qtdf::allocatedHeight))); + tex->padding = Qtdf::fetch(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(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(glyphRecord, Qtdf::textureOffsetX)); + glyphData.texCoord.y + = FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::textureOffsetY)); + glyphData.texCoord.width + = FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::textureWidth)); + glyphData.texCoord.height + = FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::textureHeight)); + glyphData.texCoord.xMargin + = FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::xMargin)); + glyphData.texCoord.yMargin + = FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::yMargin)); + glyphData.boundingRect.setX( + FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::boundingRectX))); + glyphData.boundingRect.setY( + FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, Qtdf::boundingRectY))); + glyphData.boundingRect.setWidth( + FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, + Qtdf::boundingRectWidth))); + glyphData.boundingRect.setHeight( + FROM_FIXED_POINT(Qtdf::fetch(glyphRecord, + Qtdf::boundingRectHeight))); + +#undef FROM_FIXED_POINT + + int textureIndex = Qtdf::fetch(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(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(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 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 + +#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 +#include + +#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 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 +#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 &glyphs) override; + void storeGlyphs(const QList &glyphs) override; + void referenceGlyphs(const QSet &glyphs) override; + void releaseGlyphs(const QSet &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 m_textures; + QHash m_glyphsTexture; + QSet 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::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::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 +Q3DSDistanceFieldRenderer::buildGlyphsPerTexture(const SText &textInfo) +{ + if (textInfo.m_BoundingBox.x < 0 || textInfo.m_BoundingBox.y < 0) + return QHash(); + + 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 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 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::max(), std::numeric_limits::max(), 0); + QT3DSVec3 maximum(-std::numeric_limits::max(), -std::numeric_limits::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 glyphRuns = line.glyphRuns(); + for (const QGlyphRun &glyphRun : glyphRuns) { + const QVector glyphIndexes = glyphRun.glyphIndexes(); + const QVector 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 &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 +static QVector fillIndexBuffer(uint quadCount) +{ + QVector 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( + "mvp", *m_shader.program); + m_shader.modelView = NVRenderCachedShaderProperty( + "modelView", *m_shader.program); + m_shader.textureWidth = NVRenderCachedShaderProperty( + "textureWidth", *m_shader.program); + m_shader.textureHeight = NVRenderCachedShaderProperty( + "textureHeight", *m_shader.program); + m_shader.fontScale = NVRenderCachedShaderProperty( + "fontScale", *m_shader.program); + m_shader.texture = NVRenderCachedShaderProperty( + "_qt_texture", *m_shader.program); + m_shader.color = NVRenderCachedShaderProperty( + "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( + "mvp", *m_dropShadowShader.program); + m_dropShadowShader.modelView = NVRenderCachedShaderProperty( + "modelView", *m_dropShadowShader.program); + m_dropShadowShader.textureWidth = NVRenderCachedShaderProperty( + "textureWidth", *m_dropShadowShader.program); + m_dropShadowShader.textureHeight = NVRenderCachedShaderProperty( + "textureHeight", *m_dropShadowShader.program); + m_dropShadowShader.fontScale = NVRenderCachedShaderProperty( + "fontScale", *m_dropShadowShader.program); + m_dropShadowShader.shadowOffset = NVRenderCachedShaderProperty( + "shadowOffset", *m_dropShadowShader.program); + m_dropShadowShader.texture = NVRenderCachedShaderProperty( + "_qt_texture", *m_dropShadowShader.program); + m_dropShadowShader.color = NVRenderCachedShaderProperty( + "color", *m_dropShadowShader.program); + m_dropShadowShader.shadowColor = NVRenderCachedShaderProperty( + "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 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 indexes = fillIndexBuffer(quadCount); + mesh.indexBuffer = renderContext.CreateIndexBuffer( + NVRenderBufferUsageType::Static, NVRenderComponentTypes::QT3DSU16, + size_t(indexes.size()) * sizeof(QT3DSU16), + toU8DataRef(indexes.begin(), QT3DSU32(indexes.size()))); + } else { + QVector indexes = fillIndexBuffer(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 +struct hash> +{ + size_t operator()(const QVector &s) const + { + return qHash(s); + } +}; + +template<> +struct hash +{ + size_t operator()(const TextHorizontalAlignment::Enum& s) const + { + return qHash(s); + } +}; + +template<> +struct hash +{ + size_t operator()(const TextVerticalAlignment::Enum& s) const + { + return qHash(s); + } +}; + +template<> +struct hash +{ + size_t operator()(const TextElide::Enum& s) const + { + return qHash(s); + } +}; + +template<> +struct hash +{ + size_t operator()(const TextWordWrap::Enum& s) const + { + return qHash(s); + } +}; +} + +// Copied from boost +template +inline void hashCombine(std::size_t &seed, const T &v) +{ + std::hash 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 &glyphsPerTexture + = m_glyphCache[textHashValue]; + + QT3DSVec3 minimum(std::numeric_limits::max(), std::numeric_limits::max(), 0); + QT3DSVec3 maximum(-std::numeric_limits::max(), -std::numeric_limits::max(), 0); + + QHash::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 Q3DSDistanceFieldRenderer::GetProjectFontList() +{ + Q_ASSERT(false); + return NVConstDataRef(); +} + +Option Q3DSDistanceFieldRenderer::GetFontNameForFont(CRegisteredString) +{ + Q_ASSERT(false); + return Option(); +} + +Option Q3DSDistanceFieldRenderer::GetFontNameForFont(const char8_t *) +{ + Q_ASSERT(false); + return Option(); +} + +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 vertexes; + QVector glyphIndexes; + Q3DSDistanceFieldGlyphCache *cache; + float fontScale; + float shadowOffsetX; + float shadowOffsetY; + NVBounds3 bounds; +}; + +struct Q3DSDistanceFieldShader { + NVRenderShaderProgram *program = nullptr; + NVRenderCachedShaderProperty mvp; + NVRenderCachedShaderProperty modelView; + NVRenderCachedShaderProperty textureWidth; + NVRenderCachedShaderProperty textureHeight; + NVRenderCachedShaderProperty fontScale; + NVRenderCachedShaderProperty texture; + NVRenderCachedShaderProperty color; +}; + +struct Q3DSDistanceFieldDropShadowShader { + NVRenderShaderProgram *program = nullptr; + NVRenderCachedShaderProperty mvp; + NVRenderCachedShaderProperty modelView; + NVRenderCachedShaderProperty textureWidth; + NVRenderCachedShaderProperty textureHeight; + NVRenderCachedShaderProperty fontScale; + NVRenderCachedShaderProperty shadowOffset; + NVRenderCachedShaderProperty texture; + NVRenderCachedShaderProperty color; + NVRenderCachedShaderProperty 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 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 GetProjectFontList() override; + + Option GetFontNameForFont(CRegisteredString) override; + Option 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> m_glyphCache; + QHash m_meshCache; + + Q3DSFontDatabase m_fontDatabase; + Q3DSDistanceFieldGlyphCacheManager m_glyphCacheManager; + + Q3DSDistanceFieldShader m_shader; + Q3DSDistanceFieldDropShadowShader m_dropShadowShader; + QVector 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 +#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::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 +#include + +#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 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 m_TextRenderer; NVScopedRefCounted m_OnscreenTexRenderer; NVScopedRefCounted m_PathManagerCore; + NVScopedRefCounted 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 m_OffscreenRenderManager; NVScopedRefCounted m_Renderer; NVScopedRefCounted m_TextRenderer; + NVScopedRefCounted m_distanceFieldRenderer; NVScopedRefCounted m_OnscreenTextRenderer; NVScopedRefCounted m_TextTextureCache; NVScopedRefCounted 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(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 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 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(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( + 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(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(&inRenderableObject)->m_ModelContext.m_Model; else if (inRenderableObject.m_RenderableFlags.IsText()) thePickObject = &static_cast(&inRenderableObject)->m_Text; +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + else if (inRenderableObject.m_RenderableFlags.isDistanceField()) + thePickObject = &static_cast(&inRenderableObject)->m_text; +#endif else if (inRenderableObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) thePickObject = &static_cast(&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 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(inObject).RenderDepthPass(inCameraProps); - else if (inObject.m_RenderableFlags.IsText()) + } else if (inObject.m_RenderableFlags.IsText()) { static_cast(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(inObject).RenderDepthPass(inCameraProps); +#endif + } else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) { static_cast(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(inObject).Render(inCameraProps, inFeatureSet); else if (inObject.m_RenderableFlags.IsText()) static_cast(inObject).Render(inCameraProps); +#if QT_VERSION >= QT_VERSION_CHECK(5,12,2) + else if (inObject.m_RenderableFlags.isDistanceField()) + static_cast(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(*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( + *m_TransparentObjects[idx]); + OffsetProjectionMatrix(theRenderable.m_mvp, + theVertexOffsets); +#endif } else if (m_TransparentObjects[idx]->m_RenderableFlags.IsPath()) { SPathRenderable &theRenderable = static_cast(*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( + 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 &inClipFrustum, - QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags) + const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection, + const Option &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(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 m_LayerPrepResult; // Widgets drawn at particular times during the rendering process @@ -324,14 +325,16 @@ namespace render { const Option &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 &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 &inClipFrustum, QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags); diff --git a/src/Runtime/res.qrc b/src/Runtime/res.qrc index 2fb0a4f7..c5c59b29 100644 --- a/src/Runtime/res.qrc +++ b/src/Runtime/res.qrc @@ -105,5 +105,13 @@ res/primitives/Rectangle.mesh res/primitives/Sphere.mesh res/Font/TitilliumWeb-Regular.ttf + res/effectlib/distancefieldtext.frag + res/effectlib/distancefieldtext.vert + res/effectlib/distancefieldtext_core.frag + res/effectlib/distancefieldtext_core.vert + res/effectlib/distancefieldtext_dropshadow.frag + res/effectlib/distancefieldtext_dropshadow.vert + res/effectlib/distancefieldtext_dropshadow_core.frag + res/effectlib/distancefieldtext_dropshadow_core.vert diff --git a/src/Runtime/res/effectlib/distancefieldtext.frag b/src/Runtime/res/effectlib/distancefieldtext.frag new file mode 100644 index 00000000..efcf89e4 --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext.frag @@ -0,0 +1,12 @@ +varying highp vec2 sampleCoord; +varying highp vec2 alphas; + +uniform sampler2D _qt_texture; +uniform highp vec4 color; + +void main() +{ + gl_FragColor = color * smoothstep(alphas.x, + alphas.y, + texture2D(_qt_texture, sampleCoord).a); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext.vert b/src/Runtime/res/effectlib/distancefieldtext.vert new file mode 100644 index 00000000..a3feb378 --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext.vert @@ -0,0 +1,66 @@ +uniform highp mat4 mvp; +uniform highp mat4 modelView; +uniform highp float fontScale; +uniform int textureWidth; +uniform int textureHeight; + +attribute highp vec3 vCoord; +attribute highp vec2 tCoord; + +varying highp vec2 sampleCoord; +varying highp vec2 alphas; + +highp float thresholdFunc(highp float scale) +{ + highp float base = 0.5; + highp float baseDev = 0.065; + highp float devScaleMin = 0.15; + highp float devScaleMax = 0.3; + return base - ((clamp(scale, devScaleMin, devScaleMax) - devScaleMin) + / (devScaleMax - devScaleMin) * -baseDev + baseDev); +} + +highp float spreadFunc(highp float scale) +{ + return 0.06 / scale; +} + +highp vec2 alphaRange(highp float scale) +{ + highp float base = thresholdFunc(scale); + highp float range = spreadFunc(scale); + highp float alphaMin = max(0.0, base - range); + highp float alphaMax = min(base + range, 1.0); + return vec2(alphaMin, alphaMax); +} + +highp float determinantOfSubmatrix(highp mat4 m, int col0, int col1, int row0, int row1) +{ + return m[col0][row0] * m[col1][row1] - m[col0][row1] * m[col1][row0]; +} + +highp float determinantOfSubmatrix(highp mat4 m, int col0, int col1, int col2, + int row0, int row1, int row2) +{ + highp float det = m[col0][row0] * determinantOfSubmatrix(m, col1, col2, row1, row2); + det -= m[col1][row0] * determinantOfSubmatrix(m, col0, col2, row1, row2); + det += m[col2][row0] * determinantOfSubmatrix(m, col0, col1, row1, row2); + return det; +} + +highp float determinant(highp mat4 m) +{ + highp float det = m[0][0] * determinantOfSubmatrix(m, 1, 2, 3, 1, 2, 3); + det -= m[1][0] * determinantOfSubmatrix(m, 0, 2, 3, 1, 2, 3); + det += m[2][0] * determinantOfSubmatrix(m, 0, 1, 3, 1, 2, 3); + det -= m[3][0] * determinantOfSubmatrix(m, 0, 1, 2, 1, 2, 3); + return det; +} + +void main() +{ + highp float scale = fontScale * sqrt(abs(determinant(modelView))); + alphas = alphaRange(scale); + sampleCoord = tCoord * vec2(1.0 / highp float(textureWidth), 1.0 / highp float(textureHeight)); + gl_Position = mvp * vec4(vCoord, 1.0); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext_core.frag b/src/Runtime/res/effectlib/distancefieldtext_core.frag new file mode 100644 index 00000000..0f848bc1 --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext_core.frag @@ -0,0 +1,14 @@ +in vec2 sampleCoord; + +out vec4 fragColor; + +uniform sampler2D _qt_texture; +uniform vec4 color; + +in vec2 alphas; + +void main() +{ + fragColor = color * smoothstep(alphas.x, alphas.y, + texture(_qt_texture, sampleCoord).r); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext_core.vert b/src/Runtime/res/effectlib/distancefieldtext_core.vert new file mode 100644 index 00000000..074a73ac --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext_core.vert @@ -0,0 +1,44 @@ +in vec3 vCoord; +in vec2 tCoord; + +out vec2 sampleCoord; + +out vec2 alphas; + +uniform mat4 mvp; +uniform mat4 modelView; +uniform int textureWidth; +uniform int textureHeight; +uniform float fontScale; + +float thresholdFunc(float scale) +{ + float base = 0.5; + float baseDev = 0.065; + float devScaleMin = 0.15; + float devScaleMax = 0.3; + return base - ((clamp(scale, devScaleMin, devScaleMax) - devScaleMin) + / (devScaleMax - devScaleMin) * -baseDev + baseDev); +} + +float spreadFunc(float scale) +{ + return 0.06 / scale; +} + +vec2 alphaRange(float scale) +{ + float base = thresholdFunc(scale); + float range = spreadFunc(scale); + float alphaMin = max(0.0, base - range); + float alphaMax = min(base + range, 1.0); + return vec2(alphaMin, alphaMax); +} + +void main() +{ + float scale = fontScale * sqrt(abs(determinant(modelView))); + alphas = alphaRange(scale); + sampleCoord = tCoord * vec2(1.0 / textureWidth, 1.0 / textureHeight); + gl_Position = mvp * vec4(vCoord, 1.0); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext_dropshadow.frag b/src/Runtime/res/effectlib/distancefieldtext_dropshadow.frag new file mode 100644 index 00000000..da51c5c2 --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext_dropshadow.frag @@ -0,0 +1,29 @@ +varying highp vec2 sampleCoord; +varying highp vec2 alphas; +varying highp vec2 shadowSampleCoord; +varying highp vec4 normalizedTextureBounds; + +uniform sampler2D _qt_texture; +uniform highp vec4 color; +uniform highp vec4 shadowColor; + +void main() +{ + highp float shadowAlpha = smoothstep(alphas.x, + alphas.y, + texture2D(_qt_texture, + clamp(shadowSampleCoord, + normalizedTextureBounds.xy, + normalizedTextureBounds.zw)).a); + highp vec4 shadowPixel = shadowColor * shadowAlpha; + + highp float textAlpha = smoothstep(alphas.x, + alphas.y, + texture2D(_qt_texture, + clamp(sampleCoord, + normalizedTextureBounds.xy, + normalizedTextureBounds.zw)).a); + highp vec4 textPixel = color * textAlpha; + + gl_FragColor = mix(shadowPixel, textPixel, textPixel.a); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext_dropshadow.vert b/src/Runtime/res/effectlib/distancefieldtext_dropshadow.vert new file mode 100644 index 00000000..f645eb8c --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext_dropshadow.vert @@ -0,0 +1,78 @@ +uniform highp mat4 mvp; +uniform highp mat4 modelView; +uniform highp float fontScale; +uniform int textureWidth; +uniform int textureHeight; +uniform highp vec2 shadowOffset; + +attribute highp vec3 vCoord; +attribute highp vec2 tCoord; +attribute highp vec4 textureBounds; + +varying highp vec2 sampleCoord; +varying highp vec2 shadowSampleCoord; +varying highp vec2 alphas; +varying highp vec4 normalizedTextureBounds; + +highp float thresholdFunc(highp float scale) +{ + highp float base = 0.5; + highp float baseDev = 0.065; + highp float devScaleMin = 0.15; + highp float devScaleMax = 0.3; + return base - ((clamp(scale, devScaleMin, devScaleMax) - devScaleMin) + / (devScaleMax - devScaleMin) * -baseDev + baseDev); +} + +highp float spreadFunc(highp float scale) +{ + return 0.06 / scale; +} + +highp vec2 alphaRange(highp float scale) +{ + highp float base = thresholdFunc(scale); + highp float range = spreadFunc(scale); + highp float alphaMin = max(0.0, base - range); + highp float alphaMax = min(base + range, 1.0); + return vec2(alphaMin, alphaMax); +} + +highp float determinantOfSubmatrix(highp mat4 m, int col0, int col1, int row0, int row1) +{ + return m[col0][row0] * m[col1][row1] - m[col0][row1] * m[col1][row0]; +} + +highp float determinantOfSubmatrix(highp mat4 m, int col0, int col1, int col2, + int row0, int row1, int row2) +{ + highp float det = m[col0][row0] * determinantOfSubmatrix(m, col1, col2, row1, row2); + det -= m[col1][row0] * determinantOfSubmatrix(m, col0, col2, row1, row2); + det += m[col2][row0] * determinantOfSubmatrix(m, col0, col1, row1, row2); + return det; +} + +highp float determinant(highp mat4 m) +{ + highp float det = m[0][0] * determinantOfSubmatrix(m, 1, 2, 3, 1, 2, 3); + det -= m[1][0] * determinantOfSubmatrix(m, 0, 2, 3, 1, 2, 3); + det += m[2][0] * determinantOfSubmatrix(m, 0, 1, 3, 1, 2, 3); + det -= m[3][0] * determinantOfSubmatrix(m, 0, 1, 2, 1, 2, 3); + return det; +} + +void main() +{ + highp float scale = fontScale * sqrt(abs(determinant(modelView))); + alphas = alphaRange(scale); + + highp vec2 textureSizeMultiplier = vec2(1.0 / highp float(textureWidth), + 1.0 / float(textureHeight)); + + sampleCoord = tCoord * textureSizeMultiplier; + shadowSampleCoord = (tCoord - shadowOffset) * textureSizeMultiplier; + normalizedTextureBounds = highp vec4(textureBounds.xy * textureSizeMultiplier, + textureBounds.zw * textureSizeMultiplier); + + gl_Position = mvp * vec4(vCoord, 1.0); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.frag b/src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.frag new file mode 100644 index 00000000..304b1874 --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.frag @@ -0,0 +1,32 @@ +in vec2 sampleCoord; +in vec2 shadowSampleCoord; +in vec4 normalizedTextureBounds; + +out vec4 fragColor; + +uniform sampler2D _qt_texture; +uniform vec4 color; +uniform vec4 shadowColor; + +in vec2 alphas; + +void main() +{ + float shadowAlpha = smoothstep(alphas.x, + alphas.y, + texture(_qt_texture, + clamp(shadowSampleCoord, + normalizedTextureBounds.xy, + normalizedTextureBounds.zw)).r); + vec4 shadowPixel = shadowColor * shadowAlpha; + + float textAlpha = smoothstep(alphas.x, + alphas.y, + texture(_qt_texture, + clamp(sampleCoord, + normalizedTextureBounds.xy, + normalizedTextureBounds.zw)).r); + vec4 textPixel = color * textAlpha; + + fragColor = mix(shadowPixel, textPixel, textPixel.a); +} diff --git a/src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.vert b/src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.vert new file mode 100644 index 00000000..51190577 --- /dev/null +++ b/src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.vert @@ -0,0 +1,54 @@ +in vec3 vCoord; +in vec2 tCoord; +in vec4 textureBounds; + +out vec2 sampleCoord; +out vec2 shadowSampleCoord; + +out vec2 alphas; +out vec4 normalizedTextureBounds; + +uniform mat4 mvp; +uniform mat4 modelView; +uniform int textureWidth; +uniform int textureHeight; +uniform float fontScale; +uniform vec2 shadowOffset; + +float thresholdFunc(float scale) +{ + float base = 0.5; + float baseDev = 0.065; + float devScaleMin = 0.15; + float devScaleMax = 0.3; + return base - ((clamp(scale, devScaleMin, devScaleMax) - devScaleMin) + / (devScaleMax - devScaleMin) * -baseDev + baseDev); +} + +float spreadFunc(float scale) +{ + return 0.06 / scale; +} + +vec2 alphaRange(float scale) +{ + float base = thresholdFunc(scale); + float range = spreadFunc(scale); + float alphaMin = max(0.0, base - range); + float alphaMax = min(base + range, 1.0); + return vec2(alphaMin, alphaMax); +} + +void main() +{ + float scale = fontScale * sqrt(abs(determinant(modelView))); + alphas = alphaRange(scale); + + vec2 textureSizeMultiplier = vec2(1.0 / textureWidth, 1.0 / textureHeight); + + sampleCoord = tCoord * textureSizeMultiplier; + shadowSampleCoord = (tCoord - shadowOffset) * textureSizeMultiplier; + normalizedTextureBounds = vec4(textureBounds.xy * textureSizeMultiplier, + textureBounds.zw * textureSizeMultiplier); + gl_Position = mvp * vec4(vCoord, 1.0); +} -- cgit v1.2.3