summaryrefslogtreecommitdiffstats
path: root/src/Runtime/Source/runtimerender/Qt3DSRenderTextureAtlas.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Runtime/Source/runtimerender/Qt3DSRenderTextureAtlas.cpp')
-rw-r--r--src/Runtime/Source/runtimerender/Qt3DSRenderTextureAtlas.cpp364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderTextureAtlas.cpp b/src/Runtime/Source/runtimerender/Qt3DSRenderTextureAtlas.cpp
new file mode 100644
index 00000000..062e7d57
--- /dev/null
+++ b/src/Runtime/Source/runtimerender/Qt3DSRenderTextureAtlas.cpp
@@ -0,0 +1,364 @@
+/****************************************************************************
+**
+** Copyright (C) 2008-2012 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://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 "Qt3DSRenderTextureAtlas.h"
+#include "foundation/Qt3DSContainers.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "foundation/Qt3DSFoundation.h"
+#include "foundation/Qt3DSBroadcastingAllocator.h"
+#include "render/Qt3DSRenderTexture2D.h"
+#include "render/Qt3DSRenderContext.h"
+
+using namespace qt3ds::render;
+
+namespace {
+
+// a algorithm based on http://clb.demon.fi/files/RectangleBinPack/
+struct STextureAtlasBinPackSL
+{
+public:
+ STextureAtlasBinPackSL(NVRenderContext &inContext, QT3DSI32 width, QT3DSI32 height)
+ : m_BinWidth(width)
+ , m_BinHeight(height)
+ , m_SkyLine(inContext.GetAllocator(), "STextureAtlasBinPackSL::m_SkyLine")
+ {
+ // setup first entry
+ SSkylineNode theNode = { 0, 0, width };
+ m_SkyLine.push_back(theNode);
+ }
+
+ ~STextureAtlasBinPackSL() { m_SkyLine.clear(); }
+
+ /* insert new rect
+ *
+ */
+ STextureAtlasRect Insert(QT3DSI32 width, QT3DSI32 height)
+ {
+ QT3DSI32 binHeight;
+ QT3DSI32 binWidth;
+ QT3DSI32 binIndex;
+
+ STextureAtlasRect newNode = findPosition(width, height, &binWidth, &binHeight, &binIndex);
+
+ if (binIndex != -1) {
+ // adjust skyline nodes
+ addSkylineLevelNode(binIndex, newNode);
+ }
+
+ return newNode;
+ }
+
+private:
+ /// Represents a single level (a horizontal line) of the skyline/horizon/envelope.
+ struct SSkylineNode
+ {
+ int x; ///< The starting x-coordinate (leftmost).
+ int y; ///< The y-coordinate of the skyline level line.
+ int width; /// The line width. The ending coordinate (inclusive) will be x+width-1.
+ };
+
+ /* find position
+ *
+ */
+ STextureAtlasRect findPosition(QT3DSI32 width, QT3DSI32 height, QT3DSI32 *binWidth, QT3DSI32 *binHeight,
+ QT3DSI32 *binIndex)
+ {
+ *binWidth = m_BinWidth;
+ *binHeight = m_BinHeight;
+ *binIndex = -1;
+ STextureAtlasRect newRect;
+
+ for (QT3DSU32 i = 0; i < m_SkyLine.size(); ++i) {
+ QT3DSI32 y = getSkylineLevel(i, width, height);
+
+ if (y >= 0) {
+ if ((y + height < *binHeight)
+ || ((y + height == *binHeight) && m_SkyLine[i].width < *binWidth)) {
+ *binHeight = y + height;
+ *binIndex = i;
+ *binWidth = m_SkyLine[i].width;
+ newRect.m_X = m_SkyLine[i].x;
+ newRect.m_Y = y;
+ newRect.m_Width = width;
+ newRect.m_Height = height;
+ }
+ }
+ }
+
+ return newRect;
+ }
+
+ /* @brief check if rectangle can be placed into the the bin
+ *
+ * return skyline hight
+ */
+ int getSkylineLevel(QT3DSU32 binIndex, QT3DSI32 width, QT3DSI32 height)
+ {
+ // first check width exceed
+ QT3DSI32 x = m_SkyLine[binIndex].x;
+ if (x + width > m_BinWidth)
+ return -1;
+
+ QT3DSI32 leftAlign = width;
+ QT3DSU32 index = binIndex;
+ QT3DSI32 y = m_SkyLine[index].y;
+
+ while (leftAlign > 0) {
+ y = (y > m_SkyLine[index].y) ? y : m_SkyLine[index].y;
+ // check hight
+ if (y + height > m_BinHeight)
+ return -1;
+
+ leftAlign -= m_SkyLine[index].width;
+ ++index;
+
+ if (index > m_SkyLine.size())
+ return -1;
+ }
+
+ return y;
+ }
+
+ /* @brief add an new skyline entry
+ *
+ * return no return
+ */
+ void addSkylineLevelNode(QT3DSI32 binIndex, const STextureAtlasRect &newRect)
+ {
+ SSkylineNode newNode;
+
+ newNode.x = newRect.m_X;
+ newNode.y = newRect.m_Y + newRect.m_Height;
+ newNode.width = newRect.m_Width;
+ m_SkyLine.insert(m_SkyLine.begin() + binIndex, newNode);
+
+ // iterate over follow up nodes and adjust
+ for (QT3DSU32 i = binIndex + 1; i < m_SkyLine.size(); ++i) {
+ if (m_SkyLine[i].x < m_SkyLine[i - 1].x + m_SkyLine[i - 1].width) {
+ int shrink = m_SkyLine[i - 1].x + m_SkyLine[i - 1].width - m_SkyLine[i].x;
+
+ m_SkyLine[i].x += shrink;
+ m_SkyLine[i].width -= shrink;
+
+ if (m_SkyLine[i].width <= 0) {
+ m_SkyLine.erase(m_SkyLine.begin() + i);
+ --i;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ mergeSkylineLevelNodes();
+ }
+
+ /* @brief merge skyline node
+ *
+ * return no return
+ */
+ void mergeSkylineLevelNodes()
+ {
+ // check if we can merge nodes
+ for (QT3DSU32 i = 0; i < m_SkyLine.size() - 1; ++i) {
+ if (m_SkyLine[i].y == m_SkyLine[i + 1].y) {
+ m_SkyLine[i].width += m_SkyLine[i + 1].width;
+ m_SkyLine.erase(m_SkyLine.begin() + (i + 1));
+ --i;
+ }
+ }
+ }
+
+ QT3DSI32 m_BinWidth;
+ QT3DSI32 m_BinHeight;
+
+ nvvector<SSkylineNode> m_SkyLine;
+};
+
+struct STextureAtlasEntry
+{
+ STextureAtlasEntry()
+ : m_X(0)
+ , m_Y(0)
+ , m_Width(0)
+ , m_Height(0)
+ , m_pBuffer(NVDataRef<QT3DSU8>())
+ {
+ }
+ STextureAtlasEntry(QT3DSF32 x, QT3DSF32 y, QT3DSF32 w, QT3DSF32 h, NVDataRef<QT3DSU8> buffer)
+ : m_X(x)
+ , m_Y(y)
+ , m_Width(w)
+ , m_Height(h)
+ , m_pBuffer(buffer)
+ {
+ }
+ STextureAtlasEntry(const STextureAtlasEntry &entry)
+ {
+ m_X = entry.m_X;
+ m_Y = entry.m_Y;
+ m_Width = entry.m_Width;
+ m_Height = entry.m_Height;
+ m_pBuffer = entry.m_pBuffer;
+ }
+ ~STextureAtlasEntry() {}
+
+ QT3DSF32 m_X, m_Y;
+ QT3DSF32 m_Width, m_Height;
+ NVDataRef<QT3DSU8> m_pBuffer;
+};
+
+struct STextureAtlas : public ITextureAtlas
+{
+ NVFoundationBase &m_Foundation;
+ volatile QT3DSI32 mRefCount;
+ NVScopedRefCounted<NVRenderContext> m_RenderContext;
+
+ STextureAtlas(NVFoundationBase &inFnd, NVRenderContext &inRenderContext, QT3DSI32 width,
+ QT3DSI32 height)
+ : m_Foundation(inFnd)
+ , mRefCount(0)
+ , m_RenderContext(inRenderContext)
+ , m_Width(width)
+ , m_Height(height)
+ , m_Spacing(1)
+ , m_AtlasEntrys(inFnd.getAllocator(), "STextureAtlas::m_SkyLine")
+ {
+ m_pBinPack =
+ QT3DS_NEW(inFnd.getAllocator(), STextureAtlasBinPackSL)(inRenderContext, width, height);
+ }
+
+ virtual ~STextureAtlas()
+ {
+ RelaseEntries();
+
+ if (m_pBinPack)
+ NVDelete(m_Foundation.getAllocator(), m_pBinPack);
+ }
+
+ void RelaseEntries() override
+ {
+ nvvector<STextureAtlasEntry>::iterator it;
+
+ for (it = m_AtlasEntrys.begin(); it != m_AtlasEntrys.end(); it++) {
+ QT3DS_FREE(m_Foundation.getAllocator(), it->m_pBuffer.begin());
+ }
+
+ m_AtlasEntrys.clear();
+ }
+ QT3DSI32 GetWidth() const override { return m_Width; }
+ QT3DSI32 GetHeight() const override { return m_Height; }
+
+ QT3DSI32 GetAtlasEntryCount() const override { return m_AtlasEntrys.size(); }
+
+ TTextureAtlasEntryAndBuffer GetAtlasEntryByIndex(QT3DSU32 index) override
+ {
+ if (index >= m_AtlasEntrys.size())
+ return eastl::make_pair(STextureAtlasRect(), NVDataRef<QT3DSU8>());
+
+ return eastl::make_pair(STextureAtlasRect((QT3DSI32)m_AtlasEntrys[index].m_X,
+ (QT3DSI32)m_AtlasEntrys[index].m_Y,
+ (QT3DSI32)m_AtlasEntrys[index].m_Width,
+ (QT3DSI32)m_AtlasEntrys[index].m_Height),
+ m_AtlasEntrys[index].m_pBuffer);
+ }
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator())
+
+ STextureAtlasRect AddAtlasEntry(QT3DSI32 width, QT3DSI32 height, QT3DSI32 pitch,
+ QT3DSI32 dataWidth, NVConstDataRef<QT3DSU8> bufferData) override
+ {
+ STextureAtlasRect rect;
+
+ // pitch is the number of bytes per line in bufferData
+ // dataWidth is the relevant data width in bufferData. Rest is padding that can be ignored.
+ if (m_pBinPack) {
+ QT3DSI32 paddedWith, paddedPitch, paddedHeight;
+ // add spacing around the character
+ paddedWith = width + 2 * m_Spacing;
+ paddedPitch = dataWidth + 2 * m_Spacing;
+ paddedHeight = height + 2 * m_Spacing;
+ // first get entry in the texture atlas
+ rect = m_pBinPack->Insert(paddedWith, paddedHeight);
+ if (rect.m_Width == 0)
+ return rect;
+
+ // we align the data be to 4 byte
+ int alignment = (4 - (paddedPitch % 4)) % 4;
+ paddedPitch += alignment;
+
+ // since we do spacing around the character we need to copy line by line
+ QT3DSU8 *glyphBuffer =
+ (QT3DSU8 *)QT3DS_ALLOC(m_Foundation.getAllocator(),
+ paddedHeight * paddedPitch * sizeof(QT3DSU8), "STextureAtlas");
+ if (glyphBuffer) {
+ memset(glyphBuffer, 0, paddedHeight * paddedPitch);
+
+ QT3DSU8 *pDst = glyphBuffer + paddedPitch + m_Spacing;
+ QT3DSU8 *pSrc = const_cast<QT3DSU8 *>(bufferData.begin());
+ for (QT3DSI32 i = 0; i < height; ++i) {
+ memcpy(pDst, pSrc, dataWidth);
+
+ pDst += paddedPitch;
+ pSrc += pitch;
+ }
+
+ // add new entry
+ m_AtlasEntrys.push_back(STextureAtlasEntry(
+ (QT3DSF32)rect.m_X, (QT3DSF32)rect.m_Y, (QT3DSF32)paddedWith, (QT3DSF32)paddedHeight,
+ NVDataRef<QT3DSU8>(glyphBuffer, paddedHeight * paddedPitch * sizeof(QT3DSU8))));
+
+ // normalize texture coordinates
+ rect.m_NormX = (QT3DSF32)rect.m_X / (QT3DSF32)m_Width;
+ rect.m_NormY = (QT3DSF32)rect.m_Y / (QT3DSF32)m_Height;
+ rect.m_NormWidth = (QT3DSF32)paddedWith / (QT3DSF32)m_Width;
+ rect.m_NormHeight = (QT3DSF32)paddedHeight / (QT3DSF32)m_Height;
+ }
+ }
+
+ return rect;
+ }
+
+private:
+ QT3DSI32 m_Width; ///< texture atlas width
+ QT3DSI32 m_Height; ///< texture atlas height
+ QT3DSI32 m_Spacing; ///< spacing around the entry
+ nvvector<STextureAtlasEntry> m_AtlasEntrys; ///< our entries in the atlas
+ STextureAtlasBinPackSL *m_pBinPack; ///< our bin packer which actually does most of the work
+};
+
+} // namespace
+
+ITextureAtlas &ITextureAtlas::CreateTextureAtlas(NVFoundationBase &inFnd,
+ NVRenderContext &inRenderContext, QT3DSI32 width,
+ QT3DSI32 height)
+{
+ return *QT3DS_NEW(inFnd.getAllocator(), STextureAtlas)(inFnd, inRenderContext, width, height);
+}