summaryrefslogtreecommitdiffstats
path: root/src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp
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 /src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp
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>
Diffstat (limited to 'src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp')
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSDistanceFieldRenderer.cpp1006
1 files changed, 1006 insertions, 0 deletions
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