summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJere Tuliniemi <jere.tuliniemi@qt.io>2019-04-01 15:08:11 +0300
committerJere Tuliniemi <jere.tuliniemi@qt.io>2019-04-16 09:16:37 +0000
commitc6edb9c7d15843e8ab965d365099ace29e2d2049 (patch)
tree0a24f87676d342c69254e9baeedadaaf4a70e5e5
parent156910d1df8d3f11b72664cf0bf6b7fe800b53cf (diff)
Add distance field rendering to OpenGL runtime
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ää <tomi.korpipaa@qt.io>
-rw-r--r--src/Authoring/Studio/Qt3DStudio.pro1
-rw-r--r--src/Authoring/Studio/Render/StudioRenderer.cpp16
-rw-r--r--src/Authoring/Studio/Render/StudioRendererTranslation.cpp3
-rw-r--r--src/Runtime/Qt3DSRuntimeStatic/Qt3DSRuntimeStatic.pro13
-rw-r--r--src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp7
-rw-r--r--src/Runtime/Source/engine/Qt3DSRenderRuntimeBindingImpl.h5
-rw-r--r--src/Runtime/Source/runtime/Qt3DSApplication.cpp5
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache.cpp519
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp75
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h75
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h100
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp1006
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h172
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp106
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h82
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp21
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h3
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSRenderer.h3
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h4
-rw-r--r--src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp7
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp12
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h45
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp21
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h3
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp28
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h2
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp79
-rw-r--r--src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h7
-rw-r--r--src/Runtime/res.qrc8
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext.frag12
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext.vert66
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext_core.frag14
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext_core.vert44
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext_dropshadow.frag29
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext_dropshadow.vert78
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.frag32
-rw-r--r--src/Runtime/res/effectlib/distancefieldtext_dropshadow_core.vert54
37 files changed, 2706 insertions, 51 deletions
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 <QtQuick/private/qsgareaallocator_p.h>
+
+#include <QtCore/qmath.h>
+#include <QtCore/qendian.h>
+#include <QtGui/qimage.h>
+
+#include "render/Qt3DSRenderContext.h"
+#include "render/Qt3DSRenderBaseTypes.h"
+#include "Qt3DSRenderResourceManager.h"
+#include "foundation/Qt3DSAllocator.h"
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+QT_BEGIN_NAMESPACE
+
+// Should work on most hardware. Used as stop gap until Qt 3D provides a
+// way to retrieve the system value
+#ifndef Q3DSDISTANCEFIELDGLYPHCACHE_MAXIMUM_TEXURE_SIZE
+# define Q3DSDISTANCEFIELDGLYPHCACHE_MAXIMUM_TEXURE_SIZE 2048
+#endif
+
+#if !defined(Q3DSDISTANCEFIELDGLYPHCACHE_PADDING)
+# define Q3DSDISTANCEFIELDGLYPHCACHE_PADDING 2
+#endif
+
+Q3DSDistanceFieldGlyphCache::Q3DSDistanceFieldGlyphCache(
+ const QRawFont &font, qt3ds::render::IQt3DSRenderContext &context)
+ : QSGDistanceFieldGlyphCache(font)
+ , m_context(context)
+{
+ m_maxTextureSize = Q3DSDISTANCEFIELDGLYPHCACHE_MAXIMUM_TEXURE_SIZE;
+
+ loadPregeneratedCache(font);
+}
+
+Q3DSDistanceFieldGlyphCache::~Q3DSDistanceFieldGlyphCache()
+{
+ for (auto &texture : m_textures)
+ qt3ds::foundation::NVDelete(m_context.GetAllocator(), texture.texture);
+}
+
+int Q3DSDistanceFieldGlyphCache::maxTextureSize() const
+{
+ return m_maxTextureSize;
+}
+
+Q3DSDistanceFieldGlyphCache::TextureInfo *Q3DSDistanceFieldGlyphCache::textureInfo(int index) const
+{
+ while (index >= m_textures.size())
+ m_textures.append(TextureInfo());
+ return &m_textures[index];
+}
+
+void Q3DSDistanceFieldGlyphCache::referenceGlyphs(const QSet<glyph_t> &glyphs)
+{
+ m_unusedGlyphs -= glyphs;
+}
+
+void Q3DSDistanceFieldGlyphCache::releaseGlyphs(const QSet<glyph_t> &glyphs)
+{
+ m_unusedGlyphs += glyphs;
+}
+
+void Q3DSDistanceFieldGlyphCache::resizeTexture(TextureInfo *info, int width, int height)
+{
+ QImage &image = info->copy;
+ if (info->texture == nullptr) {
+ info->texture = m_context.GetRenderContext().CreateTexture2D();
+ info->texture->SetMinFilter(qt3ds::render::NVRenderTextureMinifyingOp::Enum::Linear);
+ info->texture->SetMagFilter(qt3ds::render::NVRenderTextureMagnifyingOp::Enum::Linear);
+ info->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), image.byteCount()),
+ 0, image.width(), image.height(),
+ qt3ds::render::NVRenderTextureFormats::R8);
+ }
+
+ qt3ds::render::STextureDetails textureDetails = info->texture->GetTextureDetails();
+ if (int(textureDetails.m_Width) != width || int(textureDetails.m_Height) != height) {
+ info->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), image.byteCount()),
+ 0, image.width(), image.height(),
+ qt3ds::render::NVRenderTextureFormats::R8);
+ }
+
+ if (info->copy.width() != width || info->copy.height() != height) {
+ QImage newImage(width, height, QImage::Format_Alpha8);
+
+ for (int y = 0; y < info->copy.height(); ++y) {
+ uchar *dest = newImage.scanLine(y);
+ const uchar *src = info->copy.scanLine(y);
+ ::memcpy(dest, src, size_t(info->copy.width()));
+ }
+
+ info->copy = newImage;
+
+ info->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(), image.byteCount()),
+ 0, image.width(), image.height(),
+ qt3ds::render::NVRenderTextureFormats::R8);
+ }
+}
+
+void Q3DSDistanceFieldGlyphCache::storeGlyphs(const QList<QDistanceField> &glyphs)
+{
+ using GlyphTextureHash = QHash<TextureInfo *, QVector<glyph_t> >;
+ using GlyphTextureHashConstIt = GlyphTextureHash::const_iterator;
+
+ GlyphTextureHash glyphTextures;
+ for (int i = 0; i < glyphs.size(); ++i) {
+ QDistanceField glyph = glyphs.at(i);
+ glyph_t glyphIndex = glyph.glyph();
+ TexCoord c = glyphTexCoord(glyphIndex);
+ TextureInfo *texInfo = m_glyphsTexture.value(glyphIndex);
+
+ resizeTexture(texInfo, maxTextureSize(), texInfo->allocatedArea.height());
+
+ Q_ASSERT(!glyphTextures[texInfo].contains(glyphIndex));
+ glyphTextures[texInfo].append(glyphIndex);
+
+ int padding = texInfo->padding;
+ int expectedWidth = qCeil(c.width + c.xMargin * 2);
+ glyph = glyph.copy(-padding, -padding,
+ expectedWidth + padding * 2, glyph.height() + padding * 2);
+
+ for (int y = 0; y < glyph.height(); ++y) {
+ const uchar *src = glyph.scanLine(y);
+ uchar *dest = texInfo->copy.scanLine(int(c.y) + y - padding) + (int(c.x) - padding);
+ ::memcpy(dest, src, size_t(glyph.width()));
+ }
+ }
+
+ for (GlyphTextureHashConstIt i = glyphTextures.constBegin(),
+ cend = glyphTextures.constEnd(); i != cend; ++i) {
+ Texture t;
+ // 0 == empty texture, (i - 1) == index into m_textures
+ t.textureId = uint(i.key() - m_textures.constData()) + 1;
+ qt3ds::render::STextureDetails textureDetails = i.key()->texture->GetTextureDetails();
+ t.size = QSize(textureDetails.m_Width, textureDetails.m_Height);
+ setGlyphsTexture(i.value(), t);
+
+ QImage &image = i.key()->copy;
+
+ i.key()->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(),
+ image.byteCount()),
+ 0, image.width(), image.height(),
+ qt3ds::render::NVRenderTextureFormats::R8);
+ }
+}
+
+void Q3DSDistanceFieldGlyphCache::requestGlyphs(const QSet<glyph_t> &glyphs)
+{
+ // Note: Most of this is copy-pasted from QSGDefaultDistanceFieldGlyphCache in Qt Quick.
+ // All of this can probably be shared as a default implementation, since it does not
+ // actually create any textures, but it might have to be either templated or based
+ // on void*. For now we will just live with the duplication.
+
+ QList<GlyphPosition> glyphPositions;
+ QVector<glyph_t> glyphsToRender;
+
+ if (m_areaAllocator == nullptr) {
+ m_areaAllocator = new QSGAreaAllocator(QSize(maxTextureSize(),
+ m_maxTextureCount * maxTextureSize()));
+ }
+
+ for (QSet<glyph_t>::const_iterator it = glyphs.constBegin(); it != glyphs.constEnd() ; ++it) {
+ glyph_t glyphIndex = *it;
+
+ int padding = Q3DSDISTANCEFIELDGLYPHCACHE_PADDING;
+ QRectF boundingRect = glyphData(glyphIndex).boundingRect;
+ int glyphWidth = qCeil(boundingRect.width()) + distanceFieldRadius() * 2;
+ int glyphHeight = qCeil(boundingRect.height()) + distanceFieldRadius() * 2;
+ QSize glyphSize(glyphWidth + padding * 2, glyphHeight + padding * 2);
+ QRect alloc = m_areaAllocator->allocate(glyphSize);
+
+ if (alloc.isNull()) {
+ // Unallocate unused glyphs until we can allocated the new glyph
+ while (alloc.isNull() && !m_unusedGlyphs.isEmpty()) {
+ glyph_t unusedGlyph = *m_unusedGlyphs.constBegin();
+
+ TexCoord unusedCoord = glyphTexCoord(unusedGlyph);
+ QRectF unusedGlyphBoundingRect = glyphData(unusedGlyph).boundingRect;
+ int unusedGlyphWidth = qCeil(unusedGlyphBoundingRect.width())
+ + distanceFieldRadius() * 2;
+ int unusedGlyphHeight = qCeil(unusedGlyphBoundingRect.height())
+ + distanceFieldRadius() * 2;
+ m_areaAllocator->deallocate(QRect(int(unusedCoord.x) - padding,
+ int(unusedCoord.y) - padding,
+ padding * 2 + unusedGlyphWidth,
+ padding * 2 + unusedGlyphHeight));
+
+ m_unusedGlyphs.remove(unusedGlyph);
+ m_glyphsTexture.remove(unusedGlyph);
+ removeGlyph(unusedGlyph);
+
+ alloc = m_areaAllocator->allocate(glyphSize);
+ }
+
+ // Not enough space left for this glyph... skip to the next one
+ if (alloc.isNull())
+ continue;
+ }
+
+ TextureInfo *tex = textureInfo(alloc.y() / maxTextureSize());
+ alloc = QRect(alloc.x(), alloc.y() % maxTextureSize(), alloc.width(), alloc.height());
+
+ tex->allocatedArea |= alloc;
+ Q_ASSERT(tex->padding == padding || tex->padding < 0);
+ tex->padding = padding;
+
+ GlyphPosition p;
+ p.glyph = glyphIndex;
+ p.position = alloc.topLeft() + QPoint(padding, padding);
+
+ glyphPositions.append(p);
+ glyphsToRender.append(glyphIndex);
+ m_glyphsTexture.insert(glyphIndex, tex);
+ }
+
+ setGlyphsPosition(glyphPositions);
+ markGlyphsToRender(glyphsToRender);
+}
+
+void Q3DSDistanceFieldGlyphCache::processPendingGlyphs()
+{
+ update();
+}
+
+Q3DSDistanceFieldGlyphCache::TextureInfo *Q3DSDistanceFieldGlyphCache::textureInfoById(
+ uint id) const
+{
+ Q_ASSERT(id > 0);
+ return textureInfo(id - 1);
+}
+
+// This is all copy-pasted from Qt Quick, as sharing it would require some refactoring, and we
+// need to work with Qt 5.12.2 at the moment.
+namespace {
+ struct Qtdf {
+ // We need these structs to be tightly packed, but some compilers we use do not
+ // support #pragma pack(1), so we need to hardcode the offsets/sizes in the
+ // file format
+ enum TableSize {
+ HeaderSize = 14,
+ GlyphRecordSize = 46,
+ TextureRecordSize = 17
+ };
+
+ enum Offset {
+ // Header
+ majorVersion = 0,
+ minorVersion = 1,
+ pixelSize = 2,
+ textureSize = 4,
+ flags = 8,
+ headerPadding = 9,
+ numGlyphs = 10,
+
+ // Glyph record
+ glyphIndex = 0,
+ textureOffsetX = 4,
+ textureOffsetY = 8,
+ textureWidth = 12,
+ textureHeight = 16,
+ xMargin = 20,
+ yMargin = 24,
+ boundingRectX = 28,
+ boundingRectY = 32,
+ boundingRectWidth = 36,
+ boundingRectHeight = 40,
+ textureIndex = 44,
+
+ // Texture record
+ allocatedX = 0,
+ allocatedY = 4,
+ allocatedWidth = 8,
+ allocatedHeight = 12,
+ texturePadding = 16
+
+ };
+
+ template <typename T>
+ static inline T fetch(const char *data, Offset offset)
+ {
+ return qFromBigEndian<T>(data + int(offset));
+ }
+ };
+}
+
+qreal Q3DSDistanceFieldGlyphCache::fontSize() const
+{
+ return QT_DISTANCEFIELD_BASEFONTSIZE(m_doubleGlyphResolution);
+}
+
+bool Q3DSDistanceFieldGlyphCache::loadPregeneratedCache(const QRawFont &font)
+{
+ // The pregenerated data must be loaded first, otherwise the area allocator
+ // will be wrong
+ if (m_areaAllocator != nullptr) {
+ qWarning("Font cache must be loaded before cache is used");
+ return false;
+ }
+
+ QByteArray qtdfTable = font.fontTable("qtdf");
+ if (qtdfTable.isEmpty())
+ return false;
+
+ using GlyphTextureHash = QHash<TextureInfo *, QVector<glyph_t> >;
+
+ GlyphTextureHash glyphTextures;
+
+ if (uint(qtdfTable.size()) < Qtdf::HeaderSize) {
+ qWarning("Invalid qtdf table in font '%s'",
+ qPrintable(font.familyName()));
+ return false;
+ }
+
+ const char *qtdfTableStart = qtdfTable.constData();
+ const char *qtdfTableEnd = qtdfTableStart + qtdfTable.size();
+
+ int padding = 0;
+ int textureCount = 0;
+ {
+ quint8 majorVersion = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::majorVersion);
+ quint8 minorVersion = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::minorVersion);
+ if (majorVersion != 5 || minorVersion != 12) {
+ qWarning("Invalid version of qtdf table %d.%d in font '%s'",
+ majorVersion,
+ minorVersion,
+ qPrintable(font.familyName()));
+ return false;
+ }
+
+ qreal pixelSize = qreal(Qtdf::fetch<quint16>(qtdfTableStart, Qtdf::pixelSize));
+ m_maxTextureSize = int(Qtdf::fetch<quint32>(qtdfTableStart, Qtdf::textureSize));
+ m_doubleGlyphResolution = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::flags) == 1;
+ padding = Qtdf::fetch<quint8>(qtdfTableStart, Qtdf::headerPadding);
+
+ if (pixelSize <= 0.0) {
+ qWarning("Invalid pixel size in '%s'", qPrintable(font.familyName()));
+ return false;
+ }
+
+ if (m_maxTextureSize <= 0) {
+ qWarning("Invalid texture size in '%s'", qPrintable(font.familyName()));
+ return false;
+ }
+
+ if (padding != Q3DSDISTANCEFIELDGLYPHCACHE_PADDING) {
+ qWarning("Padding mismatch in '%s'. Font requires %d, but Qt is compiled with %d.",
+ qPrintable(font.familyName()),
+ padding,
+ Q3DSDISTANCEFIELDGLYPHCACHE_PADDING);
+ }
+
+ m_referenceFont.setPixelSize(pixelSize);
+
+ quint32 glyphCount = Qtdf::fetch<quint32>(qtdfTableStart, Qtdf::numGlyphs);
+ m_unusedGlyphs.reserve(int(glyphCount));
+
+ const char *allocatorData = qtdfTableStart + Qtdf::HeaderSize;
+ {
+ m_areaAllocator = new QSGAreaAllocator(QSize(0, 0));
+ allocatorData = m_areaAllocator->deserialize(allocatorData,
+ qtdfTableEnd - allocatorData);
+ if (allocatorData == nullptr)
+ return false;
+ }
+
+ if (m_areaAllocator->size().height() % m_maxTextureSize != 0) {
+ qWarning("Area allocator size mismatch in '%s'", qPrintable(font.familyName()));
+ return false;
+ }
+
+ textureCount = m_areaAllocator->size().height() / m_maxTextureSize;
+ m_maxTextureCount = qMax(m_maxTextureCount, textureCount);
+
+ const char *textureRecord = allocatorData;
+ for (int i = 0; i < textureCount; ++i, textureRecord += Qtdf::TextureRecordSize) {
+ if (textureRecord + Qtdf::TextureRecordSize > qtdfTableEnd) {
+ qWarning("qtdf table too small in font '%s'.",
+ qPrintable(font.familyName()));
+ return false;
+ }
+
+ TextureInfo *tex = textureInfo(i);
+ tex->allocatedArea.setX(int(Qtdf::fetch<quint32>(textureRecord, Qtdf::allocatedX)));
+ tex->allocatedArea.setY(int(Qtdf::fetch<quint32>(textureRecord, Qtdf::allocatedY)));
+ tex->allocatedArea.setWidth(int(Qtdf::fetch<quint32>(textureRecord,
+ Qtdf::allocatedWidth)));
+ tex->allocatedArea.setHeight(int(Qtdf::fetch<quint32>(textureRecord,
+ Qtdf::allocatedHeight)));
+ tex->padding = Qtdf::fetch<quint8>(textureRecord, Qtdf::texturePadding);
+ }
+
+ const char *glyphRecord = textureRecord;
+ for (quint32 i = 0; i < glyphCount; ++i, glyphRecord += Qtdf::GlyphRecordSize) {
+ if (glyphRecord + Qtdf::GlyphRecordSize > qtdfTableEnd) {
+ qWarning("qtdf table too small in font '%s'.",
+ qPrintable(font.familyName()));
+ return false;
+ }
+
+ glyph_t glyph = Qtdf::fetch<quint32>(glyphRecord, Qtdf::glyphIndex);
+ m_unusedGlyphs.insert(glyph);
+
+ GlyphData &glyphData = emptyData(glyph);
+
+#define FROM_FIXED_POINT(value) (qreal(value)/qreal(65536))
+
+ glyphData.texCoord.x
+ = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetX));
+ glyphData.texCoord.y
+ = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetY));
+ glyphData.texCoord.width
+ = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureWidth));
+ glyphData.texCoord.height
+ = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureHeight));
+ glyphData.texCoord.xMargin
+ = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::xMargin));
+ glyphData.texCoord.yMargin
+ = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::yMargin));
+ glyphData.boundingRect.setX(
+ FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectX)));
+ glyphData.boundingRect.setY(
+ FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectY)));
+ glyphData.boundingRect.setWidth(
+ FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord,
+ Qtdf::boundingRectWidth)));
+ glyphData.boundingRect.setHeight(
+ FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord,
+ Qtdf::boundingRectHeight)));
+
+#undef FROM_FIXED_POINT
+
+ int textureIndex = Qtdf::fetch<quint16>(glyphRecord, Qtdf::textureIndex);
+ if (textureIndex < 0 || textureIndex >= textureCount) {
+ qWarning("Invalid texture index %d (texture count == %d) in '%s'",
+ textureIndex,
+ textureCount,
+ qPrintable(font.familyName()));
+ return false;
+ }
+
+
+ TextureInfo *texInfo = textureInfo(textureIndex);
+ m_glyphsTexture.insert(glyph, texInfo);
+
+ glyphTextures[texInfo].append(glyph);
+ }
+
+ const uchar *textureData = reinterpret_cast<const uchar *>(glyphRecord);
+ for (int i = 0; i < textureCount; ++i) {
+
+ TextureInfo *texInfo = textureInfo(i);
+
+ int width = texInfo->allocatedArea.width();
+ int height = texInfo->allocatedArea.height();
+ qint64 size = width * height;
+ if (reinterpret_cast<const char *>(textureData + size) > qtdfTableEnd) {
+ qWarning("qtdf table too small in font '%s'.",
+ qPrintable(font.familyName()));
+ return false;
+ }
+
+ resizeTexture(texInfo, width, height);
+
+ memcpy(texInfo->copy.bits(), textureData, size);
+ textureData += size;
+
+ QImage &image = texInfo->copy;
+ texInfo->texture->SetTextureData(qt3ds::render::toU8DataRef(image.bits(),
+ image.byteCount()),
+ 0, image.width(), image.height(),
+ qt3ds::render::NVRenderTextureFormats::R8);
+
+ QVector<glyph_t> glyphs = glyphTextures.value(texInfo);
+
+ Texture t;
+ t.textureId = uint(i + 1);
+ t.size = texInfo->copy.size();
+
+ setGlyphsTexture(glyphs, t);
+ }
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2)
diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp
new file mode 100644
index 00000000..750301dc
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager.cpp
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSDistanceFieldGlyphCacheManager_p.h"
+#include "Qt3DSDistanceFieldGlyphCache_p.h"
+
+#include <QtQuick/private/qsgdefaultrendercontext_p.h>
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+ class FontKeyAccessor : public QSGDefaultRenderContext
+ {
+ public:
+ static QString fontKey(const QRawFont &font)
+ {
+ return QSGDefaultRenderContext::fontKey(font);
+ }
+ };
+}
+
+Q3DSDistanceFieldGlyphCacheManager::~Q3DSDistanceFieldGlyphCacheManager()
+{
+ for (auto &cache : qAsConst(m_glyphCaches))
+ delete cache;
+}
+
+Q3DSDistanceFieldGlyphCache *Q3DSDistanceFieldGlyphCacheManager::glyphCache(const QRawFont &font)
+{
+ QString key = FontKeyAccessor::fontKey(font);
+ Q3DSDistanceFieldGlyphCache *cache = m_glyphCaches.value(key);
+ if (cache == nullptr) {
+ cache = new Q3DSDistanceFieldGlyphCache(font, *m_context);
+ m_glyphCaches.insert(key, cache);
+ }
+
+ return cache;
+}
+
+void Q3DSDistanceFieldGlyphCacheManager::setContext(qt3ds::render::IQt3DSRenderContext &context)
+{
+ m_context = &context;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2)
diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h
new file mode 100644
index 00000000..19d3c088
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCacheManager_p.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef Q3DSDISTANCEFIELDGLYPHCACHEMANAGER_P_H
+#define Q3DSDISTANCEFIELDGLYPHCACHEMANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qhash.h>
+#include <QtGui/qrawfont.h>
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+namespace qt3ds {
+namespace render {
+class IQt3DSRenderContext;
+}
+}
+
+QT_BEGIN_NAMESPACE
+
+class Q3DSDistanceFieldGlyphCache;
+class Q3DSDistanceFieldGlyphCacheManager
+{
+public:
+ ~Q3DSDistanceFieldGlyphCacheManager();
+ Q3DSDistanceFieldGlyphCache *glyphCache(const QRawFont &font);
+ void setContext(qt3ds::render::IQt3DSRenderContext &context);
+
+private:
+ QHash<QString, Q3DSDistanceFieldGlyphCache *> m_glyphCaches;
+ qt3ds::render::IQt3DSRenderContext *m_context;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+#endif // Q3DSDISTANCEFIELDGLYPHCACHEMANAGER_P_H
diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h
new file mode 100644
index 00000000..e3ec204a
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldGlyphCache_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef Q3DSDISTANCEFIELDGLYPHCACHE_P_H
+#define Q3DSDISTANCEFIELDGLYPHCACHE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/private/qsgadaptationlayer_p.h>
+#include "render/Qt3DSRenderTexture2D.h"
+#include "Qt3DSRenderContextCore.h"
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+QT_BEGIN_NAMESPACE
+
+class QSGAreaAllocator;
+class Q3DSDistanceFieldGlyphCache : public QSGDistanceFieldGlyphCache
+{
+public:
+ struct TextureInfo {
+ qt3ds::render::NVRenderTexture2D *texture;
+ int padding = -1;
+
+ QRect allocatedArea;
+ QImage copy;
+ };
+
+ Q3DSDistanceFieldGlyphCache(const QRawFont &font,
+ qt3ds::render::IQt3DSRenderContext &context);
+ ~Q3DSDistanceFieldGlyphCache() override;
+
+ void requestGlyphs(const QSet<glyph_t> &glyphs) override;
+ void storeGlyphs(const QList<QDistanceField> &glyphs) override;
+ void referenceGlyphs(const QSet<glyph_t> &glyphs) override;
+ void releaseGlyphs(const QSet<glyph_t> &glyphs) override;
+
+ void processPendingGlyphs() override;
+
+ TextureInfo *textureInfoById(uint textureId) const;
+
+ qreal fontSize() const;
+
+private:
+ bool loadPregeneratedCache(const QRawFont &font);
+ TextureInfo *textureInfo(int index) const;
+
+ int maxTextureSize() const;
+ void resizeTexture(TextureInfo *info, int width, int height);
+
+ QSGAreaAllocator *m_areaAllocator = nullptr;
+ int m_maxTextureSize = 0;
+ int m_maxTextureCount = 3;
+
+ mutable QVector<TextureInfo> m_textures;
+ QHash<glyph_t, TextureInfo *> m_glyphsTexture;
+ QSet<glyph_t> m_unusedGlyphs;
+ qt3ds::render::IQt3DSRenderContext &m_context;
+};
+
+QT_END_NAMESPACE
+
+#endif // Q3DSDISTANCEFIELDGLYPHCACHE_P_H
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2)
diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp
new file mode 100644
index 00000000..64e5a737
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp
@@ -0,0 +1,1006 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSDistanceFieldRenderer.h"
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+#include "Qt3DSRenderContextCore.h"
+#include "Qt3DSRenderShaderCodeGeneratorV2.h"
+#include "render/Qt3DSRenderBaseTypes.h"
+#include "render/Qt3DSRenderContext.h"
+#include "qmath.h"
+#include "foundation/Qt3DSAllocator.h"
+
+using namespace qt3ds::render;
+
+Q3DSDistanceFieldRenderer::Q3DSDistanceFieldRenderer(NVFoundationBase &foundation)
+ : m_foundation(foundation)
+{
+ const QWindowList list = QGuiApplication::topLevelWindows();
+ if (list.size() > 0)
+ m_pixelRatio = list[0]->devicePixelRatio();
+}
+
+Q3DSDistanceFieldRenderer::~Q3DSDistanceFieldRenderer()
+{
+ NVAllocatorCallback &alloc = m_context->GetAllocator();
+
+ QHash<size_t, Q3DSDistanceFieldMesh>::const_iterator it;
+ for (it = m_meshCache.constBegin(); it != m_meshCache.constEnd(); ++it) {
+ const Q3DSDistanceFieldMesh &mesh = it.value();
+ NVDelete(alloc, mesh.vertexBuffer);
+ NVDelete(alloc, mesh.indexBuffer);
+ NVDelete(alloc, mesh.inputAssembler);
+ }
+}
+
+void Q3DSDistanceFieldRenderer::AddSystemFontDirectory(const char8_t *dir)
+{
+ QString systemDir(dir);
+ m_systemDirs += systemDir;
+ m_fontDatabase.registerFonts({ systemDir });
+}
+
+void Q3DSDistanceFieldRenderer::AddProjectFontDirectory(const char8_t *dir)
+{
+ QString projectDir(dir);
+ projectDir += QLatin1String("/fonts");
+ m_projectDirs += projectDir;
+ m_fontDatabase.registerFonts({ projectDir });
+}
+
+void Q3DSDistanceFieldRenderer::ClearProjectFontDirectories()
+{
+ m_fontDatabase.unregisterFonts(m_projectDirs);
+ m_projectDirs.clear();
+}
+
+ITextRenderer &Q3DSDistanceFieldRenderer::GetTextRenderer(NVRenderContext &)
+{
+ return *this;
+}
+
+void Q3DSDistanceFieldRenderer::EndFrame()
+{
+ // Remove meshes for glyphs that weren't rendered last frame
+ NVAllocatorCallback &alloc = m_context->GetAllocator();
+
+ QHash<size_t, Q3DSDistanceFieldMesh>::const_iterator it = m_meshCache.constBegin();
+ while (it != m_meshCache.constEnd()) {
+ const size_t glyphHash = it.key();
+ const Q3DSDistanceFieldMesh &mesh = it.value();
+ if (!m_renderedGlyphs.contains(glyphHash)) {
+ NVDelete(alloc, mesh.vertexBuffer);
+ NVDelete(alloc, mesh.indexBuffer);
+ NVDelete(alloc, mesh.inputAssembler);
+ m_meshCache.erase(it++);
+ } else {
+ it++;
+ }
+ }
+ m_renderedGlyphs.clear();
+}
+
+QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>
+Q3DSDistanceFieldRenderer::buildGlyphsPerTexture(const SText &textInfo)
+{
+ if (textInfo.m_BoundingBox.x < 0 || textInfo.m_BoundingBox.y < 0)
+ return QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>();
+
+ QVector2D boundingBox = QVector2D(textInfo.m_BoundingBox.x, textInfo.m_BoundingBox.y);
+
+ QRawFont font = m_fontDatabase.findFont(textInfo.m_Font.c_str());
+ font.setPixelSize(qreal(textInfo.m_FontSize));
+
+ const qreal maximumWidth = boundingBox.isNull() ? qreal(0x01000000) : qreal(boundingBox.x());
+ const qreal maximumHeight = boundingBox.isNull() ? qreal(0x01000000) : qreal(boundingBox.y());
+
+ QTextLayout layout;
+ QTextOption option = layout.textOption();
+
+ QTextOption::WrapMode wrapMode;
+ switch (textInfo.m_WordWrap) {
+ case TextWordWrap::Clip:
+ wrapMode = QTextOption::ManualWrap;
+ break;
+ case TextWordWrap::WrapWord:
+ wrapMode = QTextOption::WrapAtWordBoundaryOrAnywhere;
+ break;
+ case TextWordWrap::WrapAnywhere:
+ wrapMode = QTextOption::WrapAnywhere;
+ break;
+ case TextWordWrap::Unknown:
+ wrapMode = QTextOption::ManualWrap;
+ Q_ASSERT(0);
+ };
+ option.setWrapMode(wrapMode);
+ option.setUseDesignMetrics(true);
+
+ layout.setTextOption(option);
+ layout.setRawFont(font);
+
+ QString text = textInfo.m_Text.c_str();
+ text.replace(QLatin1Char('\n'), QChar::LineSeparator);
+
+ qreal width;
+ qreal height;
+ bool needsElide;
+ do {
+ needsElide = false;
+
+ layout.clearLayout();
+
+ float leading = textInfo.m_Leading;
+ width = 0.0;
+ height = qreal(-leading);
+
+ QVector<QTextLayout::FormatRange> formatRanges;
+
+ QTextLayout::FormatRange formatRange;
+ formatRange.start = 0;
+ formatRange.length = text.length();
+ formatRange.format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
+ formatRange.format.setFontLetterSpacing(qreal(textInfo.m_Tracking));
+ formatRanges.append(formatRange);
+ layout.setFormats(formatRanges);
+
+ layout.setText(text);
+ layout.beginLayout();
+
+ QTextLine previousLine;
+ forever {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+
+ line.setLineWidth(maximumWidth);
+ height += qreal(leading);
+ height = qCeil(height);
+
+ qreal textWidth = line.naturalTextWidth();
+ line.setPosition(QPointF(0.0, height));
+
+ width = qMin(maximumWidth, qMax(width, textWidth));
+ height += layout.engine()->lines[line.lineNumber()].height().toReal();
+
+ // Fast path for right elide
+ if (textInfo.m_Elide == TextElide::ElideRight
+ && previousLine.isValid()
+ && height > maximumHeight) {
+ break;
+ }
+
+ previousLine = line;
+ }
+ layout.endLayout();
+
+ if (textInfo.m_Elide != TextElide::ElideNone && height > maximumHeight) {
+ needsElide = true;
+
+ QString elidedText;
+ switch (textInfo.m_Elide) {
+ case TextElide::ElideRight:
+ if (previousLine.textStart() > 0)
+ elidedText = text.left(previousLine.textStart());
+
+ elidedText += layout.engine()->elidedText(
+ Qt::ElideRight, QFixed::fromReal(width), 0, previousLine.textStart(),
+ text.length() - previousLine.textStart());
+ break;
+ case TextElide::ElideLeft:
+ {
+ height = 0.0;
+ previousLine = QTextLine();
+ for (int i = layout.lineCount() - 1; i >= 0; --i) {
+ qreal lineHeight = layout.lineAt(i).height();
+ if (i < layout.lineCount() - 1 && height + lineHeight > maximumHeight)
+ break;
+ height += lineHeight;
+ previousLine = layout.lineAt(i);
+ }
+
+ Q_ASSERT(previousLine.isValid());
+ elidedText += layout.engine()->elidedText(
+ Qt::ElideLeft, QFixed::fromReal(width), 0, 0,
+ previousLine.textStart() + previousLine.textLength());
+
+ int nextPosition = (previousLine.textStart() + previousLine.textLength());
+ if (nextPosition < text.length())
+ elidedText += text.mid(nextPosition);
+ break;
+ }
+ case TextElide::ElideMiddle:
+ {
+ height = 0.0;
+ QTextLine lastLineBefore;
+ QTextLine firstLineAfter;
+ for (int i = 0; i < (layout.lineCount() / 2) + (layout.lineCount() % 2); ++i) {
+ qreal lineHeight = 3 * layout.lineAt(i).height();
+ if (height + lineHeight > maximumHeight)
+ break;
+ height += lineHeight;
+
+ lastLineBefore = layout.lineAt(i);
+ firstLineAfter = layout.lineAt(layout.lineCount() - i - 1);
+ }
+
+ int nextPosition = 0;
+ if (lastLineBefore.isValid()) {
+ elidedText += text.left(lastLineBefore.textStart()
+ + lastLineBefore.textLength());
+ nextPosition = lastLineBefore.textStart() + lastLineBefore.textLength();
+ }
+
+ QString suffix;
+ int length = text.length() - nextPosition;
+ if (firstLineAfter.isValid()) {
+ length = firstLineAfter.textStart() - nextPosition;
+ suffix = text.mid(firstLineAfter.textStart());
+ }
+
+ elidedText += layout.engine()->elidedText(
+ Qt::ElideMiddle, QFixed::fromReal(width), 0, nextPosition, length);
+
+ elidedText += suffix;
+ break;
+ }
+ case TextElide::ElideNone:
+ Q_UNREACHABLE();
+ };
+
+ // Failsafe
+ if (elidedText.isEmpty() || elidedText == text)
+ needsElide = false;
+ else
+ text = elidedText;
+ }
+ } while (needsElide);
+
+ QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> glyphsPerTexture;
+
+ float originY = float(height) / 2.0f;
+ if (textInfo.m_VerticalAlignment == TextVerticalAlignment::Bottom)
+ originY = float(height);
+ else if (textInfo.m_VerticalAlignment == TextVerticalAlignment::Top)
+ originY = 0.0;
+
+ float originX = float(width) / 2.0f;
+ if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Right)
+ originX = float(width);
+ else if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Left)
+ originX = 0.0;
+
+ float offsetY = -originY;
+
+ QT3DSVec3 minimum(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), 0);
+ QT3DSVec3 maximum(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), 0);
+
+ // To match the original behavior of the sources, we don't actually align to
+ // the bounding box. This is only used for word wrapping and elide. Keeping the
+ // code here in case this was a mistake or we for some other reason want to change
+ // it.
+#if 0
+ // If there is no bounding box, then alignmentHeight == height, so we skip it
+ if (!boundingBox.isNull() && text3DS->verticalAlignment() == Q3DSTextNode::Bottom)
+ offsetY += float(maximumHeight - height);
+ else if (!boundingBox.isNull() && text3DS->verticalAlignment() == Q3DSTextNode::Middle)
+ offsetY += float(maximumHeight / 2.0 - height / 2.0);
+ float alignmentWidth = boundingBox.isNull() ? float(width) : float(maximumWidth);
+#else
+ float alignmentWidth = float(width);
+#endif
+
+ for (int j = 0; j < layout.lineCount(); ++j) {
+ QTextLine line = layout.lineAt(j);
+
+ float offsetX = -originX;
+ if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Right)
+ offsetX += alignmentWidth - float(line.naturalTextWidth());
+ else if (textInfo.m_HorizontalAlignment == TextHorizontalAlignment::Center)
+ offsetX += alignmentWidth / 2.0f - float(line.naturalTextWidth()) / 2.0f;
+
+ const QList<QGlyphRun> glyphRuns = line.glyphRuns();
+ for (const QGlyphRun &glyphRun : glyphRuns) {
+ const QVector<quint32> glyphIndexes = glyphRun.glyphIndexes();
+ const QVector<QPointF> glyphPositions = glyphRun.positions();
+
+ Q3DSDistanceFieldGlyphCache *cache = m_glyphCacheManager.glyphCache(
+ glyphRun.rawFont());
+ cache->populate(glyphRun.glyphIndexes());
+ cache->processPendingGlyphs();
+
+ qreal fontPixelSize = glyphRun.rawFont().pixelSize();
+
+ qreal shadowOffsetX = qreal(cache->fontSize())
+ * qreal(textInfo.m_DropShadowOffsetX) / 1000.0;
+ qreal shadowOffsetY = qreal(cache->fontSize())
+ * qreal(textInfo.m_DropShadowOffsetY) / 1000.0;
+
+ qreal maxTexMargin = cache->distanceFieldRadius();
+ qreal fontScale = cache->fontScale(fontPixelSize);
+ qreal margin = 2;
+ qreal texMargin = margin / fontScale;
+ if (texMargin > maxTexMargin) {
+ texMargin = maxTexMargin;
+ margin = maxTexMargin * fontScale;
+ }
+
+ for (int i = 0; i < glyphIndexes.size(); ++i) {
+ quint32 glyphIndex = glyphIndexes.at(i);
+ QPointF position = glyphPositions.at(i);
+
+ QSGDistanceFieldGlyphCache::TexCoord c = cache->glyphTexCoord(glyphIndex);
+ if (c.isNull())
+ continue;
+
+ QSGDistanceFieldGlyphCache::Metrics metrics = cache->glyphMetrics(glyphIndex,
+ fontPixelSize);
+ if (metrics.isNull())
+ continue;
+
+ metrics.width += margin * 2;
+ metrics.height += margin * 2;
+ metrics.baselineX -= margin;
+ metrics.baselineY += margin;
+ c.xMargin -= texMargin;
+ c.yMargin -= texMargin;
+ c.width += texMargin * 2;
+ c.height += texMargin * 2;
+
+ float cx1 = float(position.x() + metrics.baselineX) + offsetX;
+ float cx2 = cx1 + float(metrics.width);
+ float cy1 = float(position.y() - metrics.baselineY) + offsetY;
+ float cy2 = cy1 + float(metrics.height);
+
+ if (textInfo.m_DropShadow) {
+ if (shadowOffsetX < 0.0)
+ cx1 += float(shadowOffsetX * fontScale);
+ else
+ cx2 += float(shadowOffsetX * fontScale);
+
+ if (shadowOffsetY < 0.0)
+ cy1 += float(shadowOffsetY * fontScale);
+ else
+ cy2 += float(shadowOffsetY * fontScale);
+ }
+
+ cy1 = -cy1;
+ cy2 = -cy2;
+
+ if (cx1 < minimum.x)
+ minimum.x = cx1;
+ else if (cx1 > maximum.x)
+ maximum.x = cx1;
+
+ if (cx2 < minimum.x)
+ minimum.x = cx2;
+ else if (cx2 > maximum.x)
+ maximum.x = cx2;
+
+ if (cy1 < minimum.y)
+ minimum.y = cy1;
+ else if (cy1 > maximum.y)
+ maximum.y = cy1;
+
+ if (cy2 < minimum.y)
+ minimum.y = cy2;
+ else if (cy2 > maximum.y)
+ maximum.y = cy2;
+
+ if (boundingBox.x() > 0 || boundingBox.y() > 0) {
+ const float halfWidth = boundingBox.x() / 2.0f;
+ const float halfHeight = boundingBox.y() / 2.0f;
+ if (maximum.x < halfWidth)
+ maximum.x = halfWidth;
+ if (minimum.x > -halfWidth)
+ minimum.x = -halfWidth;
+ if (maximum.y < halfHeight)
+ maximum.y = halfHeight;
+ if (minimum.y > -halfHeight)
+ minimum.y = -halfHeight;
+ }
+
+ float tx1 = float(c.x + c.xMargin);
+ float tx2 = tx1 + float(c.width);
+ float ty1 = float(c.y + c.yMargin);
+ float ty2 = ty1 + float(c.height);
+
+ // Preserve original bounds of glyphs
+ float ttx1 = tx1;
+ float tty1 = ty1;
+ float ttx2 = tx2;
+ float tty2 = ty2;
+
+ if (textInfo.m_DropShadow) {
+ if (shadowOffsetX < 0.0)
+ tx1 += float(c.width * shadowOffsetX * fontScale) / float(metrics.width);
+ else
+ tx2 += float(c.width * shadowOffsetX * fontScale) / float(metrics.width);
+
+ if (shadowOffsetY < 0.0)
+ ty1 += float(c.height * shadowOffsetY * fontScale) / float(metrics.height);
+ else
+ ty2 += float(c.height * shadowOffsetY * fontScale) / float(metrics.height);
+ }
+
+ const QSGDistanceFieldGlyphCache::Texture *texture
+ = cache->glyphTexture(glyphIndex);
+ if (texture->textureId == 0) {
+ qWarning() << "Empty texture for glyph" << glyphIndex;
+ continue;
+ }
+
+ Q3DSDistanceFieldGlyphCache::TextureInfo *textureInfo = cache->textureInfoById(
+ texture->textureId);
+
+ GlyphInfo &glyphInfo = glyphsPerTexture[textureInfo];
+ glyphInfo.fontScale = float(fontScale);
+ glyphInfo.shadowOffsetX = float(shadowOffsetX);
+ glyphInfo.shadowOffsetY = float(shadowOffsetY);
+ glyphInfo.bounds = NVBounds3(minimum, maximum);
+
+ QVector<float> &vertexes = glyphInfo.vertexes;
+ vertexes.reserve(vertexes.size() + 20 + (textInfo.m_DropShadow ? 16 : 0));
+
+ vertexes.append(cx1);
+ vertexes.append(cy1);
+ vertexes.append(0.0);
+ vertexes.append(tx1);
+ vertexes.append(ty1);
+
+ if (textInfo.m_DropShadow) {
+ vertexes.append(ttx1);
+ vertexes.append(tty1);
+ vertexes.append(ttx2);
+ vertexes.append(tty2);
+ }
+
+ vertexes.append(cx2);
+ vertexes.append(cy1);
+ vertexes.append(0.0);
+ vertexes.append(tx2);
+ vertexes.append(ty1);
+
+ if (textInfo.m_DropShadow) {
+ vertexes.append(ttx1);
+ vertexes.append(tty1);
+ vertexes.append(ttx2);
+ vertexes.append(tty2);
+ }
+
+ vertexes.append(cx2);
+ vertexes.append(cy2);
+ vertexes.append(0.0);
+ vertexes.append(tx2);
+ vertexes.append(ty2);
+
+ if (textInfo.m_DropShadow) {
+ vertexes.append(ttx1);
+ vertexes.append(tty1);
+ vertexes.append(ttx2);
+ vertexes.append(tty2);
+ }
+
+ vertexes.append(cx1);
+ vertexes.append(cy2);
+ vertexes.append(0.0);
+ vertexes.append(tx1);
+ vertexes.append(ty2);
+
+ if (textInfo.m_DropShadow) {
+ vertexes.append(ttx1);
+ vertexes.append(tty1);
+ vertexes.append(ttx2);
+ vertexes.append(tty2);
+ }
+ }
+ }
+ }
+
+ return glyphsPerTexture;
+}
+
+template <typename T>
+static QVector<T> fillIndexBuffer(uint quadCount)
+{
+ QVector<T> indexes;
+
+ const uint triangleCount = 2 * quadCount;
+ indexes.resize(3 * int(triangleCount));
+
+ Q_ASSERT(indexes.size() % 6 == 0);
+
+ for (uint i = 0; i < quadCount; i ++) {
+ indexes[int(i * 6 + 0)] = T(i * 4 + 0);
+ indexes[int(i * 6 + 1)] = T(i * 4 + 3);
+ indexes[int(i * 6 + 2)] = T(i * 4 + 1);
+
+ indexes[int(i * 6 + 3)] = T(i * 4 + 1);
+ indexes[int(i * 6 + 4)] = T(i * 4 + 3);
+ indexes[int(i * 6 + 5)] = T(i * 4 + 2);
+ }
+
+ return indexes;
+}
+
+void Q3DSDistanceFieldRenderer::buildShaders()
+{
+ IShaderProgramGenerator &gen = m_context->GetShaderProgramGenerator();
+ gen.BeginProgram();
+ IShaderStageGenerator &vertexGenerator(*gen.GetStage(ShaderGeneratorStages::Vertex));
+ IShaderStageGenerator &fragmentGenerator(*gen.GetStage(ShaderGeneratorStages::Fragment));
+
+ if (m_context->GetRenderContext().GetRenderContextType() == NVRenderContextValues::GLES2) {
+ vertexGenerator.AddInclude("distancefieldtext.vert");
+ fragmentGenerator.AddInclude("distancefieldtext.frag");
+ } else {
+ vertexGenerator.AddInclude("distancefieldtext_core.vert");
+ fragmentGenerator.AddInclude("distancefieldtext_core.frag");
+ }
+
+ m_shader.program = gen.CompileGeneratedShader("distancefieldtext",
+ SShaderCacheProgramFlags(),
+ TShaderFeatureSet(), false);
+
+ if (m_shader.program) {
+ m_shader.mvp = NVRenderCachedShaderProperty<QT3DSMat44>(
+ "mvp", *m_shader.program);
+ m_shader.modelView = NVRenderCachedShaderProperty<QT3DSMat44>(
+ "modelView", *m_shader.program);
+ m_shader.textureWidth = NVRenderCachedShaderProperty<QT3DSI32>(
+ "textureWidth", *m_shader.program);
+ m_shader.textureHeight = NVRenderCachedShaderProperty<QT3DSI32>(
+ "textureHeight", *m_shader.program);
+ m_shader.fontScale = NVRenderCachedShaderProperty<QT3DSF32>(
+ "fontScale", *m_shader.program);
+ m_shader.texture = NVRenderCachedShaderProperty<NVRenderTexture2D *>(
+ "_qt_texture", *m_shader.program);
+ m_shader.color = NVRenderCachedShaderProperty<QT3DSVec4>(
+ "color", *m_shader.program);
+ }
+
+ gen.BeginProgram();
+ vertexGenerator = *gen.GetStage(ShaderGeneratorStages::Vertex);
+ fragmentGenerator = *gen.GetStage(ShaderGeneratorStages::Fragment);
+
+ if (m_context->GetRenderContext().GetRenderContextType() == NVRenderContextValues::GLES2) {
+ vertexGenerator.AddInclude("distancefieldtext_dropshadow.vert");
+ fragmentGenerator.AddInclude("distancefieldtext_dropshadow.frag");
+ } else {
+ vertexGenerator.AddInclude("distancefieldtext_dropshadow_core.vert");
+ fragmentGenerator.AddInclude("distancefieldtext_dropshadow_core.frag");
+ }
+
+ m_dropShadowShader.program = gen.CompileGeneratedShader("distancefieldtext_dropshadow",
+ SShaderCacheProgramFlags(),
+ TShaderFeatureSet(), false);
+
+ if (m_dropShadowShader.program) {
+ m_dropShadowShader.mvp = NVRenderCachedShaderProperty<QT3DSMat44>(
+ "mvp", *m_dropShadowShader.program);
+ m_dropShadowShader.modelView = NVRenderCachedShaderProperty<QT3DSMat44>(
+ "modelView", *m_dropShadowShader.program);
+ m_dropShadowShader.textureWidth = NVRenderCachedShaderProperty<QT3DSI32>(
+ "textureWidth", *m_dropShadowShader.program);
+ m_dropShadowShader.textureHeight = NVRenderCachedShaderProperty<QT3DSI32>(
+ "textureHeight", *m_dropShadowShader.program);
+ m_dropShadowShader.fontScale = NVRenderCachedShaderProperty<QT3DSF32>(
+ "fontScale", *m_dropShadowShader.program);
+ m_dropShadowShader.shadowOffset = NVRenderCachedShaderProperty<QT3DSVec2>(
+ "shadowOffset", *m_dropShadowShader.program);
+ m_dropShadowShader.texture = NVRenderCachedShaderProperty<NVRenderTexture2D *>(
+ "_qt_texture", *m_dropShadowShader.program);
+ m_dropShadowShader.color = NVRenderCachedShaderProperty<QT3DSVec4>(
+ "color", *m_dropShadowShader.program);
+ m_dropShadowShader.shadowColor = NVRenderCachedShaderProperty<QT3DSVec4>(
+ "shadowColor", *m_dropShadowShader.program);
+ }
+}
+
+Q3DSDistanceFieldMesh Q3DSDistanceFieldRenderer::buildMesh(const GlyphInfo &glyphInfo,
+ bool shadow)
+{
+ static NVRenderVertexBufferEntry entries[] = {
+ NVRenderVertexBufferEntry("vCoord", NVRenderComponentTypes::QT3DSF32, 3),
+ NVRenderVertexBufferEntry("tCoord", NVRenderComponentTypes::QT3DSF32, 2, 3 * sizeof(float))
+ };
+
+ static NVRenderVertexBufferEntry shadowEntries[] = {
+ NVRenderVertexBufferEntry("vCoord", NVRenderComponentTypes::QT3DSF32, 3),
+ NVRenderVertexBufferEntry("tCoord", NVRenderComponentTypes::QT3DSF32, 2,
+ 3 * sizeof(float)),
+ NVRenderVertexBufferEntry("textureBounds", NVRenderComponentTypes::QT3DSF32, 4,
+ 5 * sizeof(float))
+ };
+
+ const uint floatsPerVertex = 3 + 2 + (shadow ? 4 : 0);
+ const uint stride = floatsPerVertex * sizeof(float);
+ const uint offset = 0;
+
+ NVRenderContext &renderContext = m_context->GetRenderContext();
+ QVector<float> vertexes = glyphInfo.vertexes;
+
+ Q_ASSERT(uint(vertexes.size()) % floatsPerVertex == 0);
+ const uint vertexCount = uint(vertexes.size()) / floatsPerVertex;
+
+ Q_ASSERT(vertexCount % 4 == 0);
+ const uint quadCount = vertexCount / 4;
+
+ Q3DSDistanceFieldMesh mesh;
+
+ mesh.attribLayout = renderContext.CreateAttributeLayout(
+ toConstDataRef(shadow ? shadowEntries : entries, shadow ? 3 : 2));
+ mesh.vertexBuffer = renderContext.CreateVertexBuffer(
+ NVRenderBufferUsageType::Static, size_t(vertexes.size()) * sizeof(float), stride,
+ toU8DataRef(vertexes.begin(), QT3DSU32(vertexes.size())));
+
+ if (vertexCount <= 0xffff) {
+ QVector<QT3DSU16> indexes = fillIndexBuffer<QT3DSU16>(quadCount);
+ mesh.indexBuffer = renderContext.CreateIndexBuffer(
+ NVRenderBufferUsageType::Static, NVRenderComponentTypes::QT3DSU16,
+ size_t(indexes.size()) * sizeof(QT3DSU16),
+ toU8DataRef(indexes.begin(), QT3DSU32(indexes.size())));
+ } else {
+ QVector<QT3DSU32> indexes = fillIndexBuffer<QT3DSU32>(quadCount);
+ mesh.indexBuffer = renderContext.CreateIndexBuffer(
+ NVRenderBufferUsageType::Static, NVRenderComponentTypes::QT3DSU32,
+ size_t(indexes.size()) * sizeof(QT3DSU32),
+ toU8DataRef(indexes.begin(), QT3DSU32(indexes.size())));
+ }
+
+ mesh.inputAssembler = renderContext.CreateInputAssembler(
+ mesh.attribLayout, toConstDataRef(&mesh.vertexBuffer, 1), mesh.indexBuffer,
+ toConstDataRef(&stride, 1), toConstDataRef(&offset, 1));
+
+ return mesh;
+}
+
+void Q3DSDistanceFieldRenderer::renderMesh(
+ NVRenderInputAssembler *inputAssembler, NVRenderTexture2D *texture, const QT3DSMat44 &mvp,
+ const QT3DSMat44 &modelView, QT3DSI32 textureWidth, QT3DSI32 textureHeight,
+ QT3DSF32 fontScale, QT3DSVec4 color)
+{
+ NVRenderContext &renderContext = m_context->GetRenderContext();
+ renderContext.SetCullingEnabled(false);
+
+ renderContext.SetBlendFunction(NVRenderBlendFunctionArgument(
+ NVRenderSrcBlendFunc::One,
+ NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ NVRenderSrcBlendFunc::One,
+ NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ renderContext.SetBlendEquation(NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+
+ renderContext.SetActiveShader(m_shader.program);
+ m_shader.mvp.Set(mvp);
+ m_shader.modelView.Set(modelView);
+ m_shader.textureWidth.Set(textureWidth);
+ m_shader.textureHeight.Set(textureHeight);
+ m_shader.fontScale.Set(fontScale);
+ m_shader.texture.Set(texture);
+ m_shader.color.Set(color);
+
+ renderContext.SetInputAssembler(inputAssembler);
+ renderContext.Draw(NVRenderDrawMode::Triangles, inputAssembler->GetIndexCount(), 0);
+}
+
+void Q3DSDistanceFieldRenderer::renderMeshWithDropShadow(
+ NVRenderInputAssembler *inputAssembler, NVRenderTexture2D *texture, const QT3DSMat44 &mvp,
+ const QT3DSMat44 &modelView, QT3DSI32 textureWidth, QT3DSI32 textureHeight,
+ QT3DSF32 fontScale, QT3DSVec2 shadowOffset, QT3DSVec4 color, QT3DSVec4 shadowColor)
+{
+ NVRenderContext &renderContext = m_context->GetRenderContext();
+ renderContext.SetCullingEnabled(false);
+
+ renderContext.SetBlendFunction(NVRenderBlendFunctionArgument(
+ NVRenderSrcBlendFunc::One,
+ NVRenderDstBlendFunc::OneMinusSrcAlpha,
+ NVRenderSrcBlendFunc::One,
+ NVRenderDstBlendFunc::OneMinusSrcAlpha));
+ renderContext.SetBlendEquation(NVRenderBlendEquationArgument(
+ NVRenderBlendEquation::Add, NVRenderBlendEquation::Add));
+
+ renderContext.SetActiveShader(m_dropShadowShader.program);
+ m_dropShadowShader.mvp.Set(mvp);
+ m_dropShadowShader.modelView.Set(modelView);
+ m_dropShadowShader.textureWidth.Set(textureWidth);
+ m_dropShadowShader.textureHeight.Set(textureHeight);
+ m_dropShadowShader.fontScale.Set(fontScale);
+ m_dropShadowShader.shadowOffset.Set(shadowOffset);
+ m_dropShadowShader.texture.Set(texture);
+ m_dropShadowShader.color.Set(color);
+ m_dropShadowShader.shadowColor.Set(shadowColor);
+
+ renderContext.SetInputAssembler(inputAssembler);
+ renderContext.Draw(NVRenderDrawMode::Triangles, inputAssembler->GetIndexCount(), 0);
+}
+
+namespace std {
+template<class T>
+struct hash<QVector<T>>
+{
+ size_t operator()(const QVector<T> &s) const
+ {
+ return qHash(s);
+ }
+};
+
+template<>
+struct hash<TextHorizontalAlignment::Enum>
+{
+ size_t operator()(const TextHorizontalAlignment::Enum& s) const
+ {
+ return qHash(s);
+ }
+};
+
+template<>
+struct hash<TextVerticalAlignment::Enum>
+{
+ size_t operator()(const TextVerticalAlignment::Enum& s) const
+ {
+ return qHash(s);
+ }
+};
+
+template<>
+struct hash<TextElide::Enum>
+{
+ size_t operator()(const TextElide::Enum& s) const
+ {
+ return qHash(s);
+ }
+};
+
+template<>
+struct hash<TextWordWrap::Enum>
+{
+ size_t operator()(const TextWordWrap::Enum& s) const
+ {
+ return qHash(s);
+ }
+};
+}
+
+// Copied from boost
+template <class T>
+inline void hashCombine(std::size_t &seed, const T &v)
+{
+ std::hash<T> hasher;
+ seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+}
+
+size_t getTextHashValue(const SText &text)
+{
+ size_t hashValue = 0;
+ hashCombine(hashValue, text.m_TextColor.x);
+ hashCombine(hashValue, text.m_TextColor.y);
+ hashCombine(hashValue, text.m_TextColor.z);
+ hashCombine(hashValue, std::string(text.m_Font.c_str()));
+ hashCombine(hashValue, std::string(text.m_Text.c_str()));
+ hashCombine(hashValue, text.m_Elide);
+ hashCombine(hashValue, text.m_Leading);
+ hashCombine(hashValue, text.m_FontSize);
+ hashCombine(hashValue, text.m_Tracking);
+ hashCombine(hashValue, text.m_WordWrap);
+ hashCombine(hashValue, text.m_DropShadow);
+ hashCombine(hashValue, text.m_BoundingBox.x);
+ hashCombine(hashValue, text.m_BoundingBox.y);
+ hashCombine(hashValue, text.m_DropShadowOffsetX);
+ hashCombine(hashValue, text.m_DropShadowOffsetY);
+ hashCombine(hashValue, text.m_DropShadowStrength);
+ hashCombine(hashValue, text.m_VerticalAlignment);
+ hashCombine(hashValue, text.m_HorizontalAlignment);
+ hashCombine(hashValue, text.m_DropShadowVerticalAlignment);
+ hashCombine(hashValue, text.m_DropShadowHorizontalAlignment);
+ return hashValue;
+}
+
+size_t getGlyphHashValue(const GlyphInfo &glyph)
+{
+ size_t hashValue = 0;
+ hashCombine(hashValue, glyph.vertexes);
+ hashCombine(hashValue, glyph.glyphIndexes);
+ hashCombine(hashValue, glyph.fontScale);
+ hashCombine(hashValue, glyph.shadowOffsetX);
+ hashCombine(hashValue, glyph.shadowOffsetY);
+ return hashValue;
+}
+
+void Q3DSDistanceFieldRenderer::renderText(SText &text, const QT3DSMat44 &mvp,
+ const QT3DSMat44 &modelView)
+{
+ if (!m_shader.program)
+ buildShaders();
+
+ int shadowRgb = int(100 - int(text.m_DropShadowStrength));
+ QT3DSVec4 shadowColor(shadowRgb * 0.01f, shadowRgb * 0.01f, shadowRgb * 0.01f, 1);
+
+ size_t textHashValue = getTextHashValue(text);
+ if (!m_glyphCache.contains(textHashValue))
+ m_glyphCache[textHashValue] = buildGlyphsPerTexture(text);
+
+ QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> &glyphsPerTexture
+ = m_glyphCache[textHashValue];
+
+ QT3DSVec3 minimum(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), 0);
+ QT3DSVec3 maximum(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), 0);
+
+ QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>::const_iterator it;
+ for (it = glyphsPerTexture.constBegin(); it != glyphsPerTexture.constEnd(); ++it) {
+ const GlyphInfo &glyphInfo = it.value();
+
+ if (glyphInfo.bounds.minimum.x < minimum.x)
+ minimum.x = glyphInfo.bounds.minimum.x;
+ if (glyphInfo.bounds.minimum.y < minimum.y)
+ minimum.y = glyphInfo.bounds.minimum.y;
+ if (glyphInfo.bounds.minimum.z < minimum.z)
+ minimum.z = glyphInfo.bounds.minimum.z;
+
+ if (glyphInfo.bounds.maximum.x > maximum.x)
+ maximum.x = glyphInfo.bounds.maximum.x;
+ if (glyphInfo.bounds.maximum.y > maximum.y)
+ maximum.y = glyphInfo.bounds.maximum.y;
+ if (glyphInfo.bounds.maximum.z > maximum.z)
+ maximum.z = glyphInfo.bounds.maximum.z;
+
+ size_t glyphHashValue = getGlyphHashValue(glyphInfo);
+
+ if (!m_meshCache.contains(glyphHashValue))
+ m_meshCache[glyphHashValue] = buildMesh(glyphInfo, text.m_DropShadow);
+
+ Q3DSDistanceFieldMesh &mesh = m_meshCache[glyphHashValue];
+
+ STextureDetails textureDetails = it.key()->texture->GetTextureDetails();
+
+ if (text.m_DropShadow) {
+ renderMeshWithDropShadow(mesh.inputAssembler, it.key()->texture, mvp, modelView,
+ int(textureDetails.m_Width), int(textureDetails.m_Height),
+ glyphInfo.fontScale * float(m_pixelRatio),
+ QT3DSVec2(glyphInfo.shadowOffsetX, glyphInfo.shadowOffsetY),
+ QT3DSVec4(text.m_TextColor, 1),
+ shadowColor);
+ } else {
+ renderMesh(mesh.inputAssembler, it.key()->texture, mvp, modelView,
+ int(textureDetails.m_Width), int(textureDetails.m_Height),
+ glyphInfo.fontScale * float(m_pixelRatio),
+ QT3DSVec4(text.m_TextColor, 1));
+ }
+
+ m_renderedGlyphs += glyphHashValue;
+ }
+
+ text.m_Bounds = NVBounds3(minimum, maximum);
+}
+
+void Q3DSDistanceFieldRenderer::renderTextDepth(SText &text, const QT3DSMat44 &mvp,
+ const QT3DSMat44 &modelView)
+{
+ // TODO: Create a depth pass shader for distance field text
+ renderText(text, mvp, modelView);
+}
+
+void Q3DSDistanceFieldRenderer::setContext(IQt3DSRenderContext &context)
+{
+ m_context = &context;
+ m_glyphCacheManager.setContext(context);
+}
+
+ITextRendererCore &ITextRendererCore::createDistanceFieldRenderer(NVFoundationBase &fnd)
+{
+ return *QT3DS_NEW(fnd.getAllocator(), Q3DSDistanceFieldRenderer)(fnd);
+}
+
+// Unused methods:
+
+void Q3DSDistanceFieldRenderer::PreloadFonts()
+{
+ Q_ASSERT(false);
+}
+
+void Q3DSDistanceFieldRenderer::BeginPreloadFonts(IThreadPool &, IPerfTimer &)
+{
+ Q_ASSERT(false);
+}
+
+void Q3DSDistanceFieldRenderer::EndPreloadFonts()
+{
+ Q_ASSERT(false);
+}
+
+void Q3DSDistanceFieldRenderer::ReloadFonts()
+{
+ Q_ASSERT(false);
+}
+
+NVConstDataRef<SRendererFontEntry> Q3DSDistanceFieldRenderer::GetProjectFontList()
+{
+ Q_ASSERT(false);
+ return NVConstDataRef<SRendererFontEntry>();
+}
+
+Option<CRegisteredString> Q3DSDistanceFieldRenderer::GetFontNameForFont(CRegisteredString)
+{
+ Q_ASSERT(false);
+ return Option<CRegisteredString>();
+}
+
+Option<CRegisteredString> Q3DSDistanceFieldRenderer::GetFontNameForFont(const char8_t *)
+{
+ Q_ASSERT(false);
+ return Option<CRegisteredString>();
+}
+
+STextDimensions Q3DSDistanceFieldRenderer::MeasureText(const STextRenderInfo &, QT3DSF32,
+ const char8_t *)
+{
+ Q_ASSERT(false);
+ return STextDimensions();
+}
+
+STextTextureDetails Q3DSDistanceFieldRenderer::RenderText(const STextRenderInfo &,
+ NVRenderTexture2D &)
+{
+ Q_ASSERT(false);
+ return STextTextureDetails();
+}
+
+STextTextureDetails Q3DSDistanceFieldRenderer::RenderText(
+ const STextRenderInfo &, NVRenderPathFontItem &, NVRenderPathFontSpecification &)
+{
+ Q_ASSERT(false);
+ return STextTextureDetails();
+}
+
+SRenderTextureAtlasDetails Q3DSDistanceFieldRenderer::RenderText(const STextRenderInfo &)
+{
+ Q_ASSERT(false);
+ return SRenderTextureAtlasDetails();
+}
+
+void Q3DSDistanceFieldRenderer::BeginFrame()
+{
+ Q_ASSERT(false);
+}
+
+QT3DSI32 Q3DSDistanceFieldRenderer::CreateTextureAtlas()
+{
+ Q_ASSERT(false);
+ return 0;
+}
+
+STextTextureAtlasEntryDetails Q3DSDistanceFieldRenderer::RenderAtlasEntry(QT3DSU32,
+ NVRenderTexture2D &)
+{
+ Q_ASSERT(false);
+ return STextTextureAtlasEntryDetails();
+}
+
+#endif
diff --git a/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h
new file mode 100644
index 00000000..0e037fd8
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.h
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QT3DSDISTANCEFIELDRENDERER_H
+#define QT3DSDISTANCEFIELDRENDERER_H
+
+#include "Qt3DSFontDatabase_p.h"
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+#include "Qt3DSTextRenderer.h"
+#include "Qt3DSRenderText.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+
+#include "Qt3DSDistanceFieldGlyphCacheManager_p.h"
+#include "Qt3DSDistanceFieldGlyphCache_p.h"
+
+namespace qt3ds {
+namespace render {
+
+struct GlyphInfo {
+ QVector<float> vertexes;
+ QVector<quint32> glyphIndexes;
+ Q3DSDistanceFieldGlyphCache *cache;
+ float fontScale;
+ float shadowOffsetX;
+ float shadowOffsetY;
+ NVBounds3 bounds;
+};
+
+struct Q3DSDistanceFieldShader {
+ NVRenderShaderProgram *program = nullptr;
+ NVRenderCachedShaderProperty<QT3DSMat44> mvp;
+ NVRenderCachedShaderProperty<QT3DSMat44> modelView;
+ NVRenderCachedShaderProperty<QT3DSI32> textureWidth;
+ NVRenderCachedShaderProperty<QT3DSI32> textureHeight;
+ NVRenderCachedShaderProperty<QT3DSF32> fontScale;
+ NVRenderCachedShaderProperty<NVRenderTexture2D *> texture;
+ NVRenderCachedShaderProperty<QT3DSVec4> color;
+};
+
+struct Q3DSDistanceFieldDropShadowShader {
+ NVRenderShaderProgram *program = nullptr;
+ NVRenderCachedShaderProperty<QT3DSMat44> mvp;
+ NVRenderCachedShaderProperty<QT3DSMat44> modelView;
+ NVRenderCachedShaderProperty<QT3DSI32> textureWidth;
+ NVRenderCachedShaderProperty<QT3DSI32> textureHeight;
+ NVRenderCachedShaderProperty<QT3DSF32> fontScale;
+ NVRenderCachedShaderProperty<QT3DSVec2> shadowOffset;
+ NVRenderCachedShaderProperty<NVRenderTexture2D *> texture;
+ NVRenderCachedShaderProperty<QT3DSVec4> color;
+ NVRenderCachedShaderProperty<QT3DSVec4> shadowColor;
+};
+
+struct Q3DSDistanceFieldMesh
+{
+ NVRenderAttribLayout *attribLayout = nullptr;
+ NVRenderVertexBuffer *vertexBuffer = nullptr;
+ NVRenderIndexBuffer *indexBuffer = nullptr;
+ NVRenderInputAssembler *inputAssembler = nullptr;
+};
+
+class Q3DSDistanceFieldRenderer : public ITextRenderer
+{
+public:
+ Q3DSDistanceFieldRenderer(NVFoundationBase &foundation);
+ ~Q3DSDistanceFieldRenderer() override;
+ QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo> buildGlyphsPerTexture(
+ const SText &textInfo);
+ void buildShaders();
+ Q3DSDistanceFieldMesh buildMesh(const GlyphInfo &glyphInfo, bool shadow);
+ void renderMesh(NVRenderInputAssembler *inputAssembler,
+ NVRenderTexture2D *texture, const QT3DSMat44 &mvp,
+ const QT3DSMat44 &modelView, QT3DSI32 textureWidth,
+ QT3DSI32 textureHeight, QT3DSF32 fontScale, QT3DSVec4 color);
+ void renderMeshWithDropShadow(NVRenderInputAssembler *inputAssembler,
+ NVRenderTexture2D *texture, const QT3DSMat44 &mvp,
+ const QT3DSMat44 &modelView, QT3DSI32 textureWidth,
+ QT3DSI32 textureHeight, QT3DSF32 fontScale,
+ QT3DSVec2 shadowOffset, QT3DSVec4 color, QT3DSVec4 shadowColor);
+ void renderText(SText &text, const QT3DSMat44 &mvp, const QT3DSMat44 &modelView);
+ void renderTextDepth(SText &text, const QT3DSMat44 &mvp, const QT3DSMat44 &modelView);
+ void setContext(IQt3DSRenderContext &context);
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_foundation.getAllocator())
+
+ void AddSystemFontDirectory(const char8_t *dir) override;
+ void AddProjectFontDirectory(const char8_t *dir) override;
+ void ClearProjectFontDirectories() override;
+ ITextRenderer &GetTextRenderer(NVRenderContext &) override;
+ void EndFrame() override;
+
+ // Unused methods:
+ void PreloadFonts() override;
+ void BeginPreloadFonts(IThreadPool &, IPerfTimer &) override;
+ void EndPreloadFonts() override;
+ void ReloadFonts() override;
+
+ NVConstDataRef<SRendererFontEntry> GetProjectFontList() override;
+
+ Option<CRegisteredString> GetFontNameForFont(CRegisteredString) override;
+ Option<CRegisteredString> GetFontNameForFont(const char8_t *) override;
+
+ STextDimensions MeasureText(const STextRenderInfo &, QT3DSF32,
+ const char8_t *) override;
+
+ STextTextureDetails RenderText(const STextRenderInfo &,
+ NVRenderTexture2D &) override;
+
+ STextTextureDetails RenderText(const STextRenderInfo &, NVRenderPathFontItem &,
+ NVRenderPathFontSpecification &) override;
+
+ SRenderTextureAtlasDetails RenderText(const STextRenderInfo &) override;
+
+ void BeginFrame() override;
+
+ QT3DSI32 CreateTextureAtlas() override;
+ STextTextureAtlasEntryDetails RenderAtlasEntry(QT3DSU32, NVRenderTexture2D &) override;
+
+private:
+ IQt3DSRenderContext *m_context = nullptr;
+ QHash<size_t, QHash<Q3DSDistanceFieldGlyphCache::TextureInfo *, GlyphInfo>> m_glyphCache;
+ QHash<size_t, Q3DSDistanceFieldMesh> m_meshCache;
+
+ Q3DSFontDatabase m_fontDatabase;
+ Q3DSDistanceFieldGlyphCacheManager m_glyphCacheManager;
+
+ Q3DSDistanceFieldShader m_shader;
+ Q3DSDistanceFieldDropShadowShader m_dropShadowShader;
+ QVector<size_t> m_renderedGlyphs;
+
+ QStringList m_systemDirs;
+ QStringList m_projectDirs;
+
+ qreal m_pixelRatio = 0.0;
+
+ NVFoundationBase &m_foundation;
+ volatile QT3DSI32 mRefCount = 0;
+};
+
+}
+}
+
+#endif // Qt version check
+
+#endif // QT3DSDISTANCEFIELDRENDERER_H
diff --git a/src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp
new file mode 100644
index 00000000..b44cdbd1
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase.cpp
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "Qt3DSFontDatabase_p.h"
+
+#include <QtCore/qdir.h>
+#include "qloggingcategory.h"
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+QT_BEGIN_NAMESPACE
+
+Q3DSFontDatabase::Q3DSFontDatabase()
+{
+}
+
+void Q3DSFontDatabase::registerFonts(const QStringList &directories)
+{
+ const QStringList nameFilters = { QStringLiteral("*.ttf"), QStringLiteral("*.otf") };
+ for (const QString &directory : directories) {
+ QDir fontDir(directory);
+ if (!fontDir.exists()) {
+ qWarning("Attempted to register invalid font directory: %s",
+ qPrintable(directory));
+ continue;
+ }
+
+ const QFileInfoList entryInfoList = fontDir.entryInfoList(nameFilters);
+ for (const QFileInfo &entryInfo : entryInfoList) {
+ QRawFont font(entryInfo.absoluteFilePath(), 16);
+ if (!font.isValid()) {
+ qWarning("Invalid font file: %s", qPrintable(entryInfo.absoluteFilePath()));
+ continue;
+ }
+
+ // ### Only support scalable fonts
+
+ QString fontId = entryInfo.baseName();
+ if (std::find_if(m_fonts.constBegin(), m_fonts.constEnd(),
+ [fontId](const Font &f) { return f.fontId == fontId; })
+ != m_fonts.constEnd()) {
+ // already registered
+ continue;
+ }
+
+ m_fonts.append(Font(fontId, entryInfo.absoluteFilePath()));
+ }
+ }
+}
+
+void Q3DSFontDatabase::unregisterFonts(const QStringList &directories)
+{
+ for (const QString &directory : directories) {
+ QDir dir(directory);
+ QVector<Font>::iterator it = m_fonts.begin();
+ while (it != m_fonts.end()) {
+ if (dir == QDir(QFileInfo(it->filePath).absolutePath()))
+ it = m_fonts.erase(it);
+ else
+ ++it;
+ }
+ }
+}
+
+QRawFont Q3DSFontDatabase::findFont(const QString &fontId)
+{
+ for (Font &font : m_fonts) {
+ if (font.fontId == fontId) {
+ if (!font.rawFont.isValid())
+ font.rawFont = QRawFont(font.filePath, 16);
+ return font.rawFont;
+ }
+ }
+
+ return QRawFont::fromFont(QFont());
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2)
diff --git a/src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h
new file mode 100644
index 00000000..73e03624
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSFontDatabase_p.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef Q3DSFONTDATABASE_P_H
+#define Q3DSFONTDATABASE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qvector.h>
+#include <QtGui/qrawfont.h>
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+QT_BEGIN_NAMESPACE
+
+class Q3DSSceneManager;
+class Q3DSFontDatabase
+{
+public:
+ Q3DSFontDatabase();
+
+ void registerFonts(const QStringList &directories);
+ void unregisterFonts(const QStringList &directories);
+
+ QRawFont findFont(const QString &fontId);
+
+private:
+ struct Font {
+ Font() = default;
+ Font(const QString &id, const QString &path)
+ : fontId(id)
+ , filePath(path)
+ {}
+
+ QString fontId;
+ QString filePath;
+ QRawFont rawFont;
+ };
+
+ QVector<Font> m_fonts;
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+
+#endif // Q3DSFONTDATABASE_P_H
diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp
index e0eedfd3..3c940805 100644
--- a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp
+++ b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.cpp
@@ -65,6 +65,7 @@
#include "Qt3DSRenderShaderCodeGeneratorV2.h"
#include "Qt3DSRenderDefaultMaterialShaderGenerator.h"
#include "Qt3DSRenderCustomMaterialShaderGenerator.h"
+#include "Qt3DSDistanceFieldRenderer.h"
using namespace qt3ds::render;
@@ -85,6 +86,7 @@ struct SRenderContextCore : public IQt3DSRenderContextCore
NVScopedRefCounted<ITextRendererCore> m_TextRenderer;
NVScopedRefCounted<ITextRendererCore> m_OnscreenTexRenderer;
NVScopedRefCounted<IPathManagerCore> m_PathManagerCore;
+ NVScopedRefCounted<ITextRendererCore> m_distanceFieldRenderer;
QT3DSI32 mRefCount;
SRenderContextCore(NVFoundationBase &fnd, IStringTable &strTable)
@@ -127,6 +129,11 @@ struct SRenderContextCore : public IQt3DSRenderContextCore
const char8_t *inPrimitivesDirectory) override;
void SetTextRendererCore(ITextRendererCore &inRenderer) override { m_TextRenderer = inRenderer; }
ITextRendererCore *GetTextRendererCore() override { return m_TextRenderer.mPtr; }
+ void setDistanceFieldRenderer(ITextRendererCore &inRenderer) override
+ {
+ m_distanceFieldRenderer = inRenderer;
+ }
+ ITextRendererCore *getDistanceFieldRenderer() override { return m_distanceFieldRenderer.mPtr; }
void SetOnscreenTextRendererCore(ITextRendererCore &inRenderer) override
{
m_OnscreenTexRenderer = inRenderer;
@@ -213,6 +220,7 @@ struct SRenderContext : public IQt3DSRenderContext
NVScopedRefCounted<IOffscreenRenderManager> m_OffscreenRenderManager;
NVScopedRefCounted<IQt3DSRenderer> m_Renderer;
NVScopedRefCounted<ITextRenderer> m_TextRenderer;
+ NVScopedRefCounted<ITextRenderer> m_distanceFieldRenderer;
NVScopedRefCounted<ITextRenderer> m_OnscreenTextRenderer;
NVScopedRefCounted<ITextTextureCache> m_TextTextureCache;
NVScopedRefCounted<ITextTextureAtlas> m_TextTextureAtlas;
@@ -307,6 +315,15 @@ struct SRenderContext : public IQt3DSRenderContext
m_RenderContext->GetFoundation(), *m_TextRenderer, *m_RenderContext);
}
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ ITextRendererCore *distanceFieldRenderer = inCore.getDistanceFieldRenderer();
+ if (distanceFieldRenderer) {
+ m_distanceFieldRenderer = distanceFieldRenderer->GetTextRenderer(ctx);
+ static_cast<Q3DSDistanceFieldRenderer *>(m_distanceFieldRenderer.mPtr)
+ ->setContext(*this);
+ }
+#endif
+
ITextRendererCore *theOnscreenTextCore = inCore.GetOnscreenTextRendererCore();
if (theOnscreenTextCore) {
m_OnscreenTextRenderer = theOnscreenTextCore->GetTextRenderer(ctx);
@@ -381,6 +398,8 @@ struct SRenderContext : public IQt3DSRenderContext
ITextRenderer *GetTextRenderer() override { return m_TextRenderer; }
+ ITextRenderer *getDistanceFieldRenderer() override { return m_distanceFieldRenderer; }
+
ITextRenderer *GetOnscreenTextRenderer() override { return m_OnscreenTextRenderer; }
void SetSceneColor(Option<QT3DSVec4> inSceneColor) override { m_SceneColor = inSceneColor; }
@@ -797,6 +816,8 @@ struct SRenderContext : public IQt3DSRenderContext
m_TextTextureCache->EndFrame();
if (m_TextRenderer)
m_TextRenderer->EndFrame();
+ if (m_distanceFieldRenderer)
+ m_distanceFieldRenderer->EndFrame();
m_OffscreenRenderManager->EndFrame();
m_Renderer->EndFrame();
m_CustomMaterialSystem->EndFrame();
diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h
index be1498b8..012864ba 100644
--- a/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h
+++ b/src/Runtime/Source/runtimerender/Qt3DSRenderContextCore.h
@@ -70,6 +70,8 @@ namespace render {
// Text renderers may be provided by clients at runtime.
virtual void SetTextRendererCore(ITextRendererCore &inRenderer) = 0;
virtual ITextRendererCore *GetTextRendererCore() = 0;
+ virtual void setDistanceFieldRenderer(ITextRendererCore &inRenderer) = 0;
+ virtual ITextRendererCore *getDistanceFieldRenderer() = 0;
// this is our default 2D text onscreen renderer
virtual void SetOnscreenTextRendererCore(ITextRendererCore &inRenderer) = 0;
virtual ITextRendererCore *GetOnscreenTextRendererCore() = 0;
@@ -106,6 +108,7 @@ namespace render {
virtual IPerfTimer &GetPerfTimer() = 0;
virtual ITextTextureCache *GetTextureCache() = 0;
virtual ITextRenderer *GetTextRenderer() = 0;
+ virtual ITextRenderer *getDistanceFieldRenderer() = 0;
virtual IRenderList &GetRenderList() = 0;
virtual IPathManager &GetPathManager() = 0;
virtual IShaderProgramGenerator &GetShaderProgramGenerator() = 0;
diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderer.h b/src/Runtime/Source/runtimerender/Qt3DSRenderer.h
index 360ebf48..69a49942 100644
--- a/src/Runtime/Source/runtimerender/Qt3DSRenderer.h
+++ b/src/Runtime/Source/runtimerender/Qt3DSRenderer.h
@@ -182,7 +182,8 @@ namespace render {
// Testing function to allow clients to render a layer using a custom view project instead
// of the one that would be setup
// using the layer's camera in conjunction with the layer's position,scale.
- virtual void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inViewProjection) = 0;
+ virtual void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inProjection,
+ const QT3DSMat44 &inViewProjection) = 0;
// This allocator is cleared every frame on BeginFrame. Objects constructed using this
// allocator
diff --git a/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h b/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h
index 756c3a90..65b7cb00 100644
--- a/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h
+++ b/src/Runtime/Source/runtimerender/Qt3DSTextRenderer.h
@@ -79,6 +79,10 @@ namespace render {
static ITextRendererCore &CreateQtTextRenderer(NVFoundationBase &inFoundation,
IStringTable &inStrTable);
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ static ITextRendererCore &createDistanceFieldRenderer(NVFoundationBase &inFnd);
+#endif
+
// call this to create onscreen text renderer
// it needs true type fonts
static ITextRendererCore &CreateOnscreenTextRenderer(NVFoundationBase &inFoundation);
diff --git a/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp b/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp
index 8b44e00f..e4694c56 100644
--- a/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp
+++ b/src/Runtime/Source/runtimerender/graphobjects/Qt3DSRenderText.cpp
@@ -67,10 +67,5 @@ SText::SText()
NVBounds3 SText::GetTextBounds() const
{
- NVBounds3 retval;
- retval.setEmpty();
- if (m_TextTexture != NULL) {
- retval.include(m_Bounds);
- }
- return retval;
+ return m_Bounds;
}
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp
index 7d8054f7..24684861 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.cpp
@@ -430,6 +430,18 @@ namespace render {
}
}
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ void SDistanceFieldRenderable::Render(const QT3DSVec2 &inCameraVec)
+ {
+ m_distanceFieldText.renderText(m_text, m_mvp, m_modelView);
+ }
+
+ void SDistanceFieldRenderable::RenderDepthPass(const QT3DSVec2 &inCameraVec)
+ {
+ m_distanceFieldText.renderTextDepth(m_text, m_mvp, m_modelView);
+ }
+#endif
+
void SCustomMaterialRenderable::Render(const QT3DSVec2 & /*inCameraVec*/,
const SLayerRenderData &inLayerData,
const SLayer &inLayer, NVDataRef<SLight *> inLights,
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h
index d380b850..deca9c47 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRenderableObjects.h
@@ -42,6 +42,7 @@
#include "Qt3DSRenderShaderCache.h"
#include "foundation/Qt3DSInvasiveLinkedList.h"
#include "Qt3DSRenderableImage.h"
+#include "Qt3DSDistanceFieldRenderer.h"
namespace qt3ds {
namespace render {
@@ -59,7 +60,8 @@ namespace render {
CustomMaterialMeshSubset = 1 << 7,
HasRefraction = 1 << 8,
Path = 1 << 9,
- ShadowCaster = 1 << 10
+ ShadowCaster = 1 << 10,
+ DistanceField = 1 << 11,
};
};
@@ -129,6 +131,16 @@ namespace render {
void SetText(bool inText) { ClearOrSet(inText, RenderPreparationResultFlagValues::Text); }
bool IsText() const { return this->operator&(RenderPreparationResultFlagValues::Text); }
+ void setDistanceField(bool inText)
+ {
+ ClearOrSet(inText, RenderPreparationResultFlagValues::DistanceField);
+ }
+
+ bool isDistanceField() const
+ {
+ return this->operator&(RenderPreparationResultFlagValues::DistanceField);
+ }
+
void SetCustom(bool inCustom)
{
ClearOrSet(inCustom, RenderPreparationResultFlagValues::Custom);
@@ -296,6 +308,7 @@ namespace render {
m_RenderableFlags.SetDefaultMaterialMeshSubset(true);
m_RenderableFlags.SetCustom(false);
m_RenderableFlags.SetText(false);
+ m_RenderableFlags.setDistanceField(false);
}
void Render(const QT3DSVec2 &inCameraVec, TShaderFeatureSet inFeatureSet);
@@ -374,11 +387,41 @@ namespace render {
m_RenderableFlags.SetDefaultMaterialMeshSubset(false);
m_RenderableFlags.SetCustom(false);
m_RenderableFlags.SetText(true);
+ m_RenderableFlags.setDistanceField(false);
+ }
+
+ void Render(const QT3DSVec2 &inCameraVec);
+ void RenderDepthPass(const QT3DSVec2 &inCameraVec);
+ };
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ struct SDistanceFieldRenderable : public SRenderableObject
+ {
+ Q3DSDistanceFieldRenderer &m_distanceFieldText;
+ QT3DSMat44 m_mvp;
+ QT3DSMat44 m_modelView;
+ SText &m_text;
+
+ SDistanceFieldRenderable(SRenderableObjectFlags flags, QT3DSVec3 worldCenterPt,
+ SText &text, const NVBounds3 &bounds,
+ const QT3DSMat44 &mvp, const QT3DSMat44 &modelView,
+ Q3DSDistanceFieldRenderer &distanceFieldText)
+ : SRenderableObject(flags, worldCenterPt, text.m_GlobalTransform, bounds)
+ , m_distanceFieldText(distanceFieldText)
+ , m_mvp(mvp)
+ , m_modelView(modelView)
+ , m_text(text)
+ {
+ m_RenderableFlags.SetDefaultMaterialMeshSubset(false);
+ m_RenderableFlags.SetCustom(false);
+ m_RenderableFlags.SetText(false);
+ m_RenderableFlags.setDistanceField(true);
}
void Render(const QT3DSVec2 &inCameraVec);
void RenderDepthPass(const QT3DSVec2 &inCameraVec);
};
+#endif
struct SPathRenderable : public SRenderableObject
{
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp
index d6fb3b1b..7c2603f6 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.cpp
@@ -696,9 +696,19 @@ namespace render {
{
if (inRenderableObject.m_RenderableFlags.IsText()) {
STextRenderable &theRenderable = static_cast<STextRenderable &>(inRenderableObject);
- if (&theRenderable.m_Text == &inNode)
+ if (&theRenderable.m_Text == &inNode) {
return inPickRay.GetRelativeXY(inRenderableObject.m_GlobalTransform,
inRenderableObject.m_Bounds);
+ }
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ } else if (inRenderableObject.m_RenderableFlags.isDistanceField()) {
+ SDistanceFieldRenderable &theRenderable = static_cast<SDistanceFieldRenderable &>(
+ inRenderableObject);
+ if (&theRenderable.m_text == &inNode) {
+ return inPickRay.GetRelativeXY(inRenderableObject.m_GlobalTransform,
+ inRenderableObject.m_Bounds);
+ }
+#endif
} else if (inRenderableObject.m_RenderableFlags.IsDefaultMaterialMeshSubset()) {
SSubsetRenderable &theRenderable = static_cast<SSubsetRenderable &>(inRenderableObject);
if (&theRenderable.m_ModelContext.m_Model == &inNode)
@@ -964,14 +974,15 @@ namespace render {
}
// This doesn't have to be cheap.
- void Qt3DSRendererImpl::RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inViewProjection)
+ void Qt3DSRendererImpl::RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inProjection,
+ const QT3DSMat44 &inViewProjection)
{
SLayerRenderData *theData = GetOrCreateLayerRenderDataForNode(inLayer);
if (theData == NULL || theData->m_Camera == NULL) {
QT3DS_ASSERT(false);
return;
}
- theData->PrepareAndRender(inViewProjection);
+ theData->PrepareAndRender(inProjection, inViewProjection);
}
void Qt3DSRendererImpl::AddRenderWidget(IRenderWidget &inWidget)
@@ -1356,6 +1367,10 @@ namespace render {
&static_cast<SSubsetRenderable *>(&inRenderableObject)->m_ModelContext.m_Model;
else if (inRenderableObject.m_RenderableFlags.IsText())
thePickObject = &static_cast<STextRenderable *>(&inRenderableObject)->m_Text;
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ else if (inRenderableObject.m_RenderableFlags.isDistanceField())
+ thePickObject = &static_cast<SDistanceFieldRenderable *>(&inRenderableObject)->m_text;
+#endif
else if (inRenderableObject.m_RenderableFlags.IsCustomMaterialMeshSubset())
thePickObject = &static_cast<SCustomMaterialRenderable *>(&inRenderableObject)
->m_ModelContext.m_Model;
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h
index 686e7d3b..e5b39b84 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImpl.h
@@ -365,7 +365,8 @@ namespace render {
Option<NVRenderRectF> GetLayerRect(SLayer &inLayer) override;
- void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inViewProjection) override;
+ void RunLayerRender(SLayer &inLayer, const QT3DSMat44 &inProjection,
+ const QT3DSMat44 &inViewProjection) override;
// Note that this allocator is completely reset on BeginFrame.
NVAllocatorCallback &GetPerFrameAllocator() override
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp
index af3cb613..3fb3bc68 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.cpp
@@ -787,11 +787,15 @@ namespace render {
const QT3DSVec2 &inCameraProps, TShaderFeatureSet, QT3DSU32,
const SCamera &inCamera)
{
- if (inObject.m_RenderableFlags.IsDefaultMaterialMeshSubset())
+ if (inObject.m_RenderableFlags.IsDefaultMaterialMeshSubset()) {
static_cast<SSubsetRenderable &>(inObject).RenderDepthPass(inCameraProps);
- else if (inObject.m_RenderableFlags.IsText())
+ } else if (inObject.m_RenderableFlags.IsText()) {
static_cast<STextRenderable &>(inObject).RenderDepthPass(inCameraProps);
- else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) {
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ } else if (inObject.m_RenderableFlags.isDistanceField()) {
+ static_cast<SDistanceFieldRenderable &>(inObject).RenderDepthPass(inCameraProps);
+#endif
+ } else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) {
static_cast<SCustomMaterialRenderable &>(inObject).RenderDepthPass(
inCameraProps, inData.m_Layer, inData.m_Lights, inCamera, NULL);
} else if (inObject.m_RenderableFlags.IsPath()) {
@@ -845,6 +849,10 @@ namespace render {
static_cast<SSubsetRenderable &>(inObject).Render(inCameraProps, inFeatureSet);
else if (inObject.m_RenderableFlags.IsText())
static_cast<STextRenderable &>(inObject).Render(inCameraProps);
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ else if (inObject.m_RenderableFlags.isDistanceField())
+ static_cast<SDistanceFieldRenderable &>(inObject).Render(inCameraProps);
+#endif
else if (inObject.m_RenderableFlags.IsCustomMaterialMeshSubset()) {
// PKC : Need a better place to do this.
SCustomMaterialRenderable &theObject =
@@ -1419,6 +1427,15 @@ namespace render {
static_cast<STextRenderable &>(*m_TransparentObjects[idx]);
OffsetProjectionMatrix(theRenderable.m_ModelViewProjection,
theVertexOffsets);
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ } else if (m_TransparentObjects[idx]->m_RenderableFlags
+ .isDistanceField()) {
+ SDistanceFieldRenderable &theRenderable
+ = static_cast<SDistanceFieldRenderable &>(
+ *m_TransparentObjects[idx]);
+ OffsetProjectionMatrix(theRenderable.m_mvp,
+ theVertexOffsets);
+#endif
} else if (m_TransparentObjects[idx]->m_RenderableFlags.IsPath()) {
SPathRenderable &theRenderable =
static_cast<SPathRenderable &>(*m_TransparentObjects[idx]);
@@ -2140,7 +2157,8 @@ namespace render {
m_BoundingRectColor.setEmpty();
}
- void SLayerRenderData::PrepareAndRender(const QT3DSMat44 &inViewProjection)
+ void SLayerRenderData::PrepareAndRender(const QT3DSMat44 &inProjection,
+ const QT3DSMat44 &inViewProjection)
{
TRenderableObjectList theTransparentObjects(m_TransparentObjects);
TRenderableObjectList theOpaqueObjects(m_OpaqueObjects);
@@ -2148,7 +2166,7 @@ namespace render {
theOpaqueObjects.clear();
m_ModelContexts.clear();
SLayerRenderPreparationResultFlags theFlags;
- PrepareRenderablesForRender(inViewProjection, Empty(), 1.0, theFlags);
+ PrepareRenderablesForRender(inProjection, inViewProjection, Empty(), 1.0, theFlags);
RenderDepthPass(false);
Render();
}
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h
index 4e237b0b..6716eada 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderData.h
@@ -153,7 +153,7 @@ struct AdvancedBlendModes
// layer setup system. This assumes the client has setup the viewport, scissor, and render
// target
// the way they want them.
- void PrepareAndRender(const QT3DSMat44 &inViewProjection);
+ void PrepareAndRender(const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection);
SOffscreenRendererEnvironment CreateOffscreenRenderEnvironment() override;
IRenderTask &CreateRenderToTextureRunnable() override;
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp
index 68ed9907..2940f088 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.cpp
@@ -362,8 +362,8 @@ namespace render {
}
bool SLayerRenderPreparationData::PrepareTextForRender(
- SText &inText, const QT3DSMat44 &inViewProjection, QT3DSF32 inTextScaleFactor,
- SLayerRenderPreparationResultFlags &ioFlags)
+ SText &inText, const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection,
+ QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags)
{
ITextTextureCache *theTextRenderer = m_Renderer.GetQt3DSContext().GetTextureCache();
if (theTextRenderer == NULL)
@@ -378,31 +378,49 @@ namespace render {
if (theFlags.IsCompletelyTransparent() == false) {
retval = inText.m_Flags.IsDirty() || inText.m_Flags.IsTextDirty();
inText.m_Flags.SetTextDirty(false);
- TTPathObjectAndTexture theResult =
- theTextRenderer->RenderText(inText, inTextScaleFactor);
- inText.m_TextTexture = theResult.second.second.mPtr;
- inText.m_TextTextureDetails = theResult.second.first;
- inText.m_PathFontItem = theResult.first.second;
- inText.m_PathFontDetails = theResult.first.first;
- STextScaleAndOffset theScaleAndOffset(*inText.m_TextTexture,
- inText.m_TextTextureDetails, inText);
- QT3DSVec2 theTextScale(theScaleAndOffset.m_TextScale);
- QT3DSVec2 theTextOffset(theScaleAndOffset.m_TextOffset);
- QT3DSVec3 minimum(theTextOffset[0] - theTextScale[0], theTextOffset[1] - theTextScale[1],
- 0);
- QT3DSVec3 maximum(theTextOffset[0] + theTextScale[0], theTextOffset[1] + theTextScale[1],
- 0);
- inText.m_Bounds = NVBounds3(minimum, maximum);
QT3DSMat44 theMVP;
QT3DSMat33 theNormalMatrix;
inText.CalculateMVPAndNormalMatrix(inViewProjection, theMVP, theNormalMatrix);
- if (inText.m_PathFontDetails)
- ioFlags.SetRequiresStencilBuffer(true);
+ SRenderableObject *theRenderable = nullptr;
+#if QT_VERSION >= QT_VERSION_CHECK(5,12,2)
+ // TODO: Implement clipping for the distance field renderer
+ if (inText.m_WordWrap != TextWordWrap::Clip || (inText.m_BoundingBox.x == 0.0f
+ && inText.m_BoundingBox.y == 0.0f)) {
+ QT3DSMat44 modelView = (inProjection.getInverse() * inViewProjection)
+ * inText.m_GlobalTransform;
+ Q3DSDistanceFieldRenderer *distanceFieldText
+ = static_cast<Q3DSDistanceFieldRenderer *>(
+ m_Renderer.GetQt3DSContext().getDistanceFieldRenderer());
+ theRenderable = RENDER_FRAME_NEW(SDistanceFieldRenderable)(
+ theFlags, inText.GetGlobalPos(), inText, inText.m_Bounds, theMVP,
+ modelView, *distanceFieldText);
+ } else
+#endif
+ {
+ TTPathObjectAndTexture theResult
+ = theTextRenderer->RenderText(inText, inTextScaleFactor);
+ inText.m_TextTexture = theResult.second.second.mPtr;
+ inText.m_TextTextureDetails = theResult.second.first;
+ inText.m_PathFontItem = theResult.first.second;
+ inText.m_PathFontDetails = theResult.first.first;
+ STextScaleAndOffset theScaleAndOffset(*inText.m_TextTexture,
+ inText.m_TextTextureDetails, inText);
+ QT3DSVec2 theTextScale(theScaleAndOffset.m_TextScale);
+ QT3DSVec2 theTextOffset(theScaleAndOffset.m_TextOffset);
+ QT3DSVec3 minimum(theTextOffset[0] - theTextScale[0],
+ theTextOffset[1] - theTextScale[1], 0);
+ QT3DSVec3 maximum(theTextOffset[0] + theTextScale[0],
+ theTextOffset[1] + theTextScale[1], 0);
+ inText.m_Bounds = NVBounds3(minimum, maximum);
+
+ if (inText.m_PathFontDetails)
+ ioFlags.SetRequiresStencilBuffer(true);
- STextRenderable *theRenderable = RENDER_FRAME_NEW(STextRenderable)(
- theFlags, inText.GetGlobalPos(), m_Renderer, inText, inText.m_Bounds, theMVP,
- inViewProjection, *inText.m_TextTexture, theTextOffset, theTextScale);
+ theRenderable = RENDER_FRAME_NEW(STextRenderable)(
+ theFlags, inText.GetGlobalPos(), m_Renderer, inText, inText.m_Bounds, theMVP,
+ inViewProjection, *inText.m_TextTexture, theTextOffset, theTextScale);
+ }
m_TransparentObjects.push_back(theRenderable);
}
return retval;
@@ -997,11 +1015,13 @@ namespace render {
}
bool SLayerRenderPreparationData::PrepareRenderablesForRender(
- const QT3DSMat44 &inViewProjection, const Option<SClippingFrustum> &inClipFrustum,
- QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags)
+ const QT3DSMat44 &inProjection, const QT3DSMat44 &inViewProjection,
+ const Option<SClippingFrustum> &inClipFrustum,
+ QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags)
{
SStackPerfTimer __timer(m_Renderer.GetQt3DSContext().GetPerfTimer(),
"SLayerRenderData::PrepareRenderablesForRender");
+ m_projection = inProjection;
m_ViewProjection = inViewProjection;
QT3DSF32 theTextScaleFactor = inTextScaleFactor;
bool wasDataDirty = false;
@@ -1025,7 +1045,8 @@ namespace render {
SText *theText = static_cast<SText *>(theNode);
theText->CalculateGlobalVariables();
if (theText->m_Flags.IsGloballyActive()) {
- bool wasTextDirty = PrepareTextForRender(*theText, inViewProjection,
+ bool wasTextDirty = PrepareTextForRender(*theText, inProjection,
+ inViewProjection,
theTextScaleFactor, ioFlags);
wasDataDirty = wasDataDirty || wasTextDirty;
}
@@ -1344,6 +1365,7 @@ namespace render {
QT3DSF32 theTextScaleFactor = 1.0f;
if (m_Camera) {
+ m_projection = m_Camera->m_Projection;
m_Camera->CalculateViewProjectionMatrix(m_ViewProjection);
theTextScaleFactor = m_Camera->GetTextScaleFactor(
thePrepResult.GetLayerToPresentationViewport(),
@@ -1359,8 +1381,10 @@ namespace render {
// the near plane's bbox edges are calculated in the clipping frustum's
// constructor.
m_ClippingFrustum = SClippingFrustum(m_ViewProjection, nearPlane);
- } else
+ } else {
+ m_projection = QT3DSMat44::createIdentity();
m_ViewProjection = QT3DSMat44::createIdentity();
+ }
// Setup the light directions here.
@@ -1372,7 +1396,8 @@ namespace render {
m_ModelContexts.clear();
if (GetOffscreenRenderer() == false) {
bool renderablesDirty =
- PrepareRenderablesForRender(m_ViewProjection, m_ClippingFrustum,
+ PrepareRenderablesForRender(m_projection, m_ViewProjection,
+ m_ClippingFrustum,
theTextScaleFactor, thePrepResult.m_Flags);
wasDataDirty = wasDataDirty || renderablesDirty;
if (thePrepResult.m_Flags.RequiresStencilBuffer())
diff --git a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h
index 5b8d6e10..05923e7f 100644
--- a/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h
+++ b/src/Runtime/Source/runtimerender/rendererimpl/Qt3DSRendererImplLayerRenderPreparationData.h
@@ -270,6 +270,7 @@ namespace render {
TRenderableObjectList m_RenderedOpaqueObjects;
TRenderableObjectList m_RenderedTransparentObjects;
QT3DSMat44 m_ViewProjection;
+ QT3DSMat44 m_projection;
SClippingFrustum m_ClippingFrustum;
Option<SLayerRenderPreparationResult> m_LayerPrepResult;
// Widgets drawn at particular times during the rendering process
@@ -324,14 +325,16 @@ namespace render {
const Option<SClippingFrustum> &inClipFrustum,
TNodeLightEntryList &inScopedLights);
- bool PrepareTextForRender(SText &inText, const QT3DSMat44 &inViewProjection,
+ bool PrepareTextForRender(SText &inText, const QT3DSMat44 &inProjection,
+ const QT3DSMat44 &inViewProjection,
QT3DSF32 inTextScaleFactor,
SLayerRenderPreparationResultFlags &ioFlags);
bool PreparePathForRender(SPath &inPath, const QT3DSMat44 &inViewProjection,
const Option<SClippingFrustum> &inClipFrustum,
SLayerRenderPreparationResultFlags &ioFlags);
// Helper function used during PRepareForRender and PrepareAndRender
- bool PrepareRenderablesForRender(const QT3DSMat44 &inViewProjection,
+ bool PrepareRenderablesForRender(const QT3DSMat44 &inProjection,
+ const QT3DSMat44 &inViewProjection,
const Option<SClippingFrustum> &inClipFrustum,
QT3DSF32 inTextScaleFactor,
SLayerRenderPreparationResultFlags &ioFlags);
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 @@
<file>res/primitives/Rectangle.mesh</file>
<file>res/primitives/Sphere.mesh</file>
<file>res/Font/TitilliumWeb-Regular.ttf</file>
+ <file>res/effectlib/distancefieldtext.frag</file>
+ <file>res/effectlib/distancefieldtext.vert</file>
+ <file>res/effectlib/distancefieldtext_core.frag</file>
+ <file>res/effectlib/distancefieldtext_core.vert</file>
+ <file>res/effectlib/distancefieldtext_dropshadow.frag</file>
+ <file>res/effectlib/distancefieldtext_dropshadow.vert</file>
+ <file>res/effectlib/distancefieldtext_dropshadow_core.frag</file>
+ <file>res/effectlib/distancefieldtext_dropshadow_core.vert</file>
</qresource>
</RCC>
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);
+}