From b4954701093739e7a4e54a0669f306922d0d4605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pasi=20Kera=CC=88nen?= Date: Thu, 6 Jun 2019 16:22:02 +0300 Subject: Long live the slayer! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initial commit of OpenGL Runtime to repository. Based on SHA1 61823aaccc6510699a54b34a2fe3f7523dab3b4e of qt3dstudio repository. Task-number: QT3DS-3600 Change-Id: Iaeb80237399f0e5656a19ebec9d1ab3a681d8832 Reviewed-by: Pasi Keränen --- .../resourcemanager/Qt3DSRenderBufferLoader.cpp | 326 +++++ .../resourcemanager/Qt3DSRenderBufferLoader.h | 85 ++ .../resourcemanager/Qt3DSRenderBufferManager.cpp | 1092 +++++++++++++++++ .../resourcemanager/Qt3DSRenderBufferManager.h | 119 ++ .../Qt3DSRenderImageBatchLoader.cpp | 538 +++++++++ .../resourcemanager/Qt3DSRenderImageBatchLoader.h | 96 ++ .../resourcemanager/Qt3DSRenderLoadedTexture.cpp | 715 +++++++++++ .../resourcemanager/Qt3DSRenderLoadedTexture.h | 184 +++ .../Qt3DSRenderLoadedTextureBMP.cpp | 1262 ++++++++++++++++++++ .../Qt3DSRenderLoadedTextureDDS.cpp | 695 +++++++++++ .../resourcemanager/Qt3DSRenderLoadedTextureDDS.h | 88 ++ .../Qt3DSRenderLoadedTextureFreeImageCompat.h | 413 +++++++ .../Qt3DSRenderLoadedTextureGIF.cpp | 851 +++++++++++++ .../Qt3DSRenderLoadedTextureHDR.cpp | 255 ++++ .../Qt3DSRenderLoadedTextureKTX.cpp | 277 +++++ .../resourcemanager/Qt3DSRenderLoadedTextureKTX.h | 39 + .../Qt3DSRenderPrefilterTexture.cpp | 599 ++++++++++ .../resourcemanager/Qt3DSRenderPrefilterTexture.h | 129 ++ .../Qt3DSRenderResourceBufferObjects.cpp | 101 ++ .../Qt3DSRenderResourceBufferObjects.h | 96 ++ .../resourcemanager/Qt3DSRenderResourceManager.cpp | 436 +++++++ .../resourcemanager/Qt3DSRenderResourceManager.h | 81 ++ .../Qt3DSRenderResourceTexture2D.cpp | 174 +++ .../resourcemanager/Qt3DSRenderResourceTexture2D.h | 126 ++ 24 files changed, 8777 insertions(+) create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureBMP.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureFreeImageCompat.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureGIF.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureHDR.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.h create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.cpp create mode 100644 src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.h (limited to 'src/runtimerender/resourcemanager') diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.cpp new file mode 100644 index 0000000..963186f --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.cpp @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** 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 "Qt3DSRenderBufferLoader.h" +#include "foundation/Qt3DSInvasiveLinkedList.h" +#include "foundation/Qt3DSMutex.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSSync.h" +#include "Qt3DSRenderInputStreamFactory.h" +#include "Qt3DSRenderThreadPool.h" + +using namespace qt3ds::render; + +namespace { +struct SBufferLoader; +struct SBufferLoadResult : public ILoadedBuffer +{ + NVFoundationBase &m_Foundation; + CRegisteredString m_Path; + IBufferLoaderCallback *m_UserData; + NVDataRef m_Data; + QT3DSI32 mRefCount; + + SBufferLoadResult(NVFoundationBase &fnd, CRegisteredString p, IBufferLoaderCallback *ud, + NVDataRef data) + : m_Foundation(fnd) + , m_Path(p) + , m_UserData(ud) + , m_Data(data) + , mRefCount(0) + { + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_Foundation.getAllocator()) + + CRegisteredString Path() override { return m_Path; } + // Data is released when the buffer itself is released. + NVDataRef Data() override { return m_Data; } + IBufferLoaderCallback *UserData() override { return m_UserData; } + + static SBufferLoadResult *Allocate(QT3DSU32 inBufferSize, NVFoundationBase &fnd, + CRegisteredString p, + NVScopedRefCounted ud) + { + size_t allocSize = sizeof(SBufferLoadResult) + inBufferSize; + QT3DSU8 *allocMem = + (QT3DSU8 *)fnd.getAllocator().allocate(allocSize, "ILoadedBuffer", __FILE__, __LINE__); + if (allocMem == NULL) + return NULL; + QT3DSU8 *bufferStart = allocMem + sizeof(SBufferLoadResult); + NVDataRef dataBuffer = toDataRef(bufferStart, inBufferSize); + return new (allocMem) SBufferLoadResult(fnd, p, ud, dataBuffer); + } +}; +struct SLoadedBufferImpl +{ + SBufferLoader &m_Loader; + QT3DSU64 m_JobId; + QT3DSU64 m_LoadId; + CRegisteredString m_Path; + NVScopedRefCounted m_UserData; + bool m_Quiet; + volatile bool m_Cancel; + NVScopedRefCounted m_Result; + SLoadedBufferImpl *m_NextBuffer; + SLoadedBufferImpl *m_PreviousBuffer; + + SLoadedBufferImpl(SBufferLoader &l, CRegisteredString inPath, + NVScopedRefCounted ud, bool inQuiet, QT3DSU64 loadId) + : m_Loader(l) + , m_JobId(0) + , m_LoadId(loadId) + , m_Path(inPath) + , m_UserData(ud) + , m_Quiet(inQuiet) + , m_Cancel(false) + , m_NextBuffer(NULL) + , m_PreviousBuffer(NULL) + { + } +}; + +DEFINE_INVASIVE_LIST(LoadedBufferImpl); +IMPLEMENT_INVASIVE_LIST(LoadedBufferImpl, m_PreviousBuffer, m_NextBuffer); + +struct SBufferLoader : public IBufferLoader +{ + NVFoundationBase &m_Foundation; + NVScopedRefCounted m_Factory; + NVScopedRefCounted m_ThreadPool; + + Mutex m_BuffersToLoadMutex; + TLoadedBufferImplList m_BuffersToLoad; + + Mutex m_BuffersLoadingMutex; + TLoadedBufferImplList m_BuffersLoading; + + Mutex m_LoadedBuffersMutex; + TLoadedBufferImplList m_LoadedBuffers; + + Sync m_BufferLoadedEvent; + + QT3DSU64 m_NextBufferId; + + QT3DSI32 mRefCount; + + SBufferLoader(NVFoundationBase &fnd, IInputStreamFactory &fac, IThreadPool &tp) + : m_Foundation(fnd) + , m_Factory(fac) + , m_ThreadPool(tp) + , m_BuffersToLoadMutex(fnd.getAllocator()) + , m_BuffersLoadingMutex(fnd.getAllocator()) + , m_LoadedBuffersMutex(fnd.getAllocator()) + , m_BufferLoadedEvent(fnd.getAllocator()) + , m_NextBufferId(1) + , mRefCount(0) + { + m_BufferLoadedEvent.reset(); + } + + virtual ~SBufferLoader() + { + { + Mutex::ScopedLock __locker(m_BuffersToLoadMutex); + for (TLoadedBufferImplList::iterator iter = m_BuffersToLoad.begin(), + end = m_BuffersToLoad.end(); + iter != end; ++iter) { + m_ThreadPool->CancelTask(iter->m_JobId); + } + } + + // Pull any remaining buffers out of the thread system. + while (WillLoadedBuffersBeAvailable()) { + NextLoadedBuffer(); + } + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + static void InitializeActiveLoadingBuffer(SLoadedBufferImpl &theBuffer) + { + Mutex::ScopedLock theLocker(theBuffer.m_Loader.m_BuffersToLoadMutex); + Mutex::ScopedLock theSecondLocker(theBuffer.m_Loader.m_BuffersLoadingMutex); + theBuffer.m_Loader.m_BuffersToLoad.remove(theBuffer); + theBuffer.m_Loader.m_BuffersLoading.push_back(theBuffer); + } + + static void SetBufferAsLoaded(SLoadedBufferImpl &theBuffer) + { + Mutex::ScopedLock theSecondLocker(theBuffer.m_Loader.m_BuffersLoadingMutex); + Mutex::ScopedLock theLocker(theBuffer.m_Loader.m_LoadedBuffersMutex); + theBuffer.m_Loader.m_BuffersLoading.remove(theBuffer); + theBuffer.m_Loader.m_LoadedBuffers.push_back(theBuffer); + theBuffer.m_Loader.m_BufferLoadedEvent.set(); + theBuffer.m_Loader.m_BufferLoadedEvent.reset(); + } + + static void LoadNextBuffer(void *loader) + { + SLoadedBufferImpl &theBuffer = *reinterpret_cast(loader); + + InitializeActiveLoadingBuffer(theBuffer); + NVScopedRefCounted theStream = + theBuffer.m_Loader.m_Factory->GetStreamForFile(theBuffer.m_Path.c_str(), + theBuffer.m_Quiet); + if (theStream && theBuffer.m_Cancel == false) { + theStream->SetPosition(0, SeekPosition::End); + QT3DSI64 theFileLen = theStream->GetPosition(); + if (theFileLen > 0 && theFileLen < (QT3DSU32)QT3DS_MAX_U32) { + QT3DSU32 required = (QT3DSU32)theFileLen; + theBuffer.m_Result = + SBufferLoadResult::Allocate(required, theBuffer.m_Loader.m_Foundation, + theBuffer.m_Path, theBuffer.m_UserData); + QT3DSU32 amountRead = 0; + QT3DSU32 total = amountRead; + if (theBuffer.m_Result && theBuffer.m_Cancel == false) { + NVDataRef theDataBuffer(theBuffer.m_Result->m_Data); + theStream->SetPosition(0, SeekPosition::Begin); + amountRead = theStream->Read(theDataBuffer); + total += amountRead; + // Ensure we keep trying, not all file systems allow huge reads. + while (total < required && amountRead > 0 && theBuffer.m_Cancel == false) { + NVDataRef newBuffer(theDataBuffer.mData + total, required - total); + amountRead = theStream->Read(newBuffer); + total += amountRead; + } + } + if (theBuffer.m_Cancel || total != required) { + theBuffer.m_Result->m_Data = NVDataRef(); + } + } + } + + // only callback if the file was successfully loaded. + if (theBuffer.m_UserData) { + if (theBuffer.m_Cancel == false && theBuffer.m_Result.mPtr + && theBuffer.m_Result->m_Data.size()) { + theBuffer.m_UserData->OnBufferLoaded(*theBuffer.m_Result.mPtr); + } else { + if (theBuffer.m_Cancel) + theBuffer.m_UserData->OnBufferLoadCancelled(theBuffer.m_Path); + else + theBuffer.m_UserData->OnBufferLoadFailed(theBuffer.m_Path); + } + } + + SetBufferAsLoaded(theBuffer); + } + static void CancelNextBuffer(void *loader) + { + SLoadedBufferImpl &theBuffer = *reinterpret_cast(loader); + theBuffer.m_Cancel = true; + InitializeActiveLoadingBuffer(theBuffer); + + if (theBuffer.m_UserData) + theBuffer.m_UserData->OnBufferLoadCancelled(theBuffer.m_Path); + + SetBufferAsLoaded(theBuffer); + } + + // nonblocking. Quiet failure is passed to the input stream factory. + QT3DSU64 QueueForLoading(CRegisteredString inPath, + IBufferLoaderCallback *inUserData = NULL, + bool inQuietFailure = false) override + { + SLoadedBufferImpl &theBuffer = *QT3DS_NEW(m_Foundation.getAllocator(), SLoadedBufferImpl)( + *this, inPath, inUserData, inQuietFailure, m_NextBufferId); + ++m_NextBufferId; + { + Mutex::ScopedLock theLocker(m_BuffersToLoadMutex); + m_BuffersToLoad.push_back(theBuffer); + } + theBuffer.m_JobId = m_ThreadPool->AddTask(&theBuffer, LoadNextBuffer, CancelNextBuffer); + return theBuffer.m_LoadId; + } + + void CancelBufferLoad(QT3DSU64 inBufferId) override + { + { + Mutex::ScopedLock theLocker(m_BuffersToLoadMutex); + SLoadedBufferImpl *theLoadedBuffer = NULL; + for (TLoadedBufferImplList::iterator iter = m_BuffersToLoad.begin(), + end = m_BuffersToLoad.end(); + iter != end && theLoadedBuffer == NULL; ++iter) { + if (iter->m_LoadId == inBufferId) { + theLoadedBuffer = &(*iter); + // both cancellation attempts are necessary. The user will still get + // a load result, it will just have no data. + theLoadedBuffer->m_Cancel = true; + m_ThreadPool->CancelTask(theLoadedBuffer->m_JobId); + } + } + } + } + + // If we were will to wait, will we ever get another buffer + bool WillLoadedBuffersBeAvailable() override + { + Mutex::ScopedLock theLocker(m_BuffersToLoadMutex); + Mutex::ScopedLock theSecondLocker(m_BuffersLoadingMutex); + return AreLoadedBuffersAvailable() || m_BuffersToLoad.empty() == false + || m_BuffersLoading.empty() == false; + } + // Will nextLoadedBuffer block or not? + bool AreLoadedBuffersAvailable() override + { + Mutex::ScopedLock theLocker(m_LoadedBuffersMutex); + return m_LoadedBuffers.empty() == false; + } + + // blocking, be careful with this. No order guarantees here. + NVScopedRefCounted NextLoadedBuffer() override + { + while (!AreLoadedBuffersAvailable()) { + m_BufferLoadedEvent.wait(); + } + SLoadedBufferImpl *theBuffer; + { + Mutex::ScopedLock theLocker(m_LoadedBuffersMutex); + theBuffer = m_LoadedBuffers.back_ptr(); + m_LoadedBuffers.remove(*theBuffer); + } + NVScopedRefCounted retval(theBuffer->m_Result); + if (retval.mPtr == NULL) { + retval = SBufferLoadResult::Allocate(0, m_Foundation, theBuffer->m_Path, + theBuffer->m_UserData); + } + NVDelete(m_Foundation.getAllocator(), theBuffer); + return retval; + } +}; +} + +IBufferLoader &IBufferLoader::Create(NVFoundationBase &fnd, IInputStreamFactory &inFactory, + IThreadPool &inThreadPool) +{ + return *QT3DS_NEW(fnd.getAllocator(), SBufferLoader)(fnd, inFactory, inThreadPool); +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.h b/src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.h new file mode 100644 index 0000000..8d75e25 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderBufferLoader.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_BUFFER_LOADED_H +#define QT3DS_RENDER_BUFFER_LOADED_H + +#include "Qt3DSRender.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/StringTable.h" + +namespace qt3ds { +namespace render { + + class IBufferLoaderCallback; + + class ILoadedBuffer : public NVRefCounted + { + public: + virtual CRegisteredString Path() = 0; + // Data is released when the buffer itself is released. + virtual NVDataRef Data() = 0; + virtual IBufferLoaderCallback *UserData() = 0; + }; + + class IBufferLoaderCallback : public NVRefCounted + { + public: + virtual void OnBufferLoaded(ILoadedBuffer &inBuffer) = 0; + virtual void OnBufferLoadFailed(CRegisteredString inPath) = 0; + virtual void OnBufferLoadCancelled(CRegisteredString inPath) = 0; + }; + + // Job of this object is to load buffers all the way to memory as fast as possible. + class IBufferLoader : public NVRefCounted + { + public: + // nonblocking. Quiet failure is passed to the input stream factory. + // Returns handle to loading buffer + virtual QT3DSU64 QueueForLoading(CRegisteredString inPath, + IBufferLoaderCallback *inUserData = NULL, + bool inQuietFailure = false) = 0; + // Cancel a buffer that has not made it to the loaded buffers list. + virtual void CancelBufferLoad(QT3DSU64 inBufferId) = 0; + // If we were will to wait, will we ever get another buffer + virtual bool WillLoadedBuffersBeAvailable() = 0; + // Will nextLoadedBuffer block or not? + virtual bool AreLoadedBuffersAvailable() = 0; + + // blocking, be careful with this. No guarantees about timely return here. + virtual NVScopedRefCounted NextLoadedBuffer() = 0; + + static IBufferLoader &Create(NVFoundationBase &fnd, IInputStreamFactory &inFactory, + IThreadPool &inThreadPool); + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp new file mode 100644 index 0000000..eb23f3d --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp @@ -0,0 +1,1092 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifdef _WIN32 +#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union +#endif +#include "Qt3DSRender.h" +#include "Qt3DSRenderBufferManager.h" +#include "EASTL/string.h" +#include "foundation/Qt3DSAllocator.h" +#include "render/Qt3DSRenderContext.h" +#include "foundation/Qt3DSAtomic.h" +#include "EASTL/hash_map.h" +#include "foundation/FileTools.h" +#include "Qt3DSImportMesh.h" +#include "Qt3DSRenderMesh.h" +#include "foundation/Qt3DSAllocatorCallback.h" +#include "Qt3DSRenderLoadedTexture.h" +#include "foundation/Qt3DSFoundation.h" +#include "Qt3DSRenderInputStreamFactory.h" +#include "Qt3DSRenderImageScaler.h" +#include "Qt3DSRenderImage.h" +#include "Qt3DSTextRenderer.h" +#include "foundation/Qt3DSPerfTimer.h" +#include "foundation/Qt3DSMutex.h" +#include "Qt3DSRenderPrefilterTexture.h" +#include + +using namespace qt3ds::render; + +namespace { + +using eastl::hash; +using eastl::pair; +using eastl::make_pair; +typedef eastl::basic_string TStr; +struct StrHasher +{ + size_t operator()(const TStr &str) const + { + return hash()((const char8_t *)str.c_str()); + } +}; + +struct StrEq +{ + bool operator()(const TStr &lhs, const TStr &rhs) const { return lhs == rhs; } +}; + +struct SImageEntry : public SImageTextureData +{ + bool m_Loaded; + SImageEntry() + : SImageTextureData(), m_Loaded(false) + { + } + SImageEntry(const SImageEntry &entry) + : SImageTextureData(entry), m_Loaded(entry.m_Loaded) + { + + } +}; + +struct SPrimitiveEntry +{ + // Name of the primitive as it will be in the UIP file + CRegisteredString m_PrimitiveName; + // Name of the primitive file on the filesystem + CRegisteredString m_FileName; +}; + +struct SBufferManager : public IBufferManager +{ + typedef eastl::hash_set, + eastl::equal_to, ForwardingAllocator> + TStringSet; + typedef nvhash_map TImageMap; + typedef nvhash_map TMeshMap; + typedef nvhash_map TAliasImageMap; + + NVScopedRefCounted m_Context; + NVScopedRefCounted m_StrTable; + NVScopedRefCounted m_InputStreamFactory; + IPerfTimer &m_PerfTimer; + volatile QT3DSI32 mRefCount; + TStr m_PathBuilder; + TImageMap m_ImageMap; + Mutex m_LoadedImageSetMutex; + TStringSet m_LoadedImageSet; + TAliasImageMap m_AliasImageMap; + TMeshMap m_MeshMap; + SPrimitiveEntry m_PrimitiveNames[5]; + nvvector m_EntryBuffer; + bool m_GPUSupportsDXT; + bool m_reloadableResources; + + QHash m_reloadableTextures; + + static const char8_t *GetPrimitivesDirectory() { return "res//primitives"; } + + SBufferManager(NVRenderContext &ctx, IStringTable &strTable, + IInputStreamFactory &inInputStreamFactory, IPerfTimer &inTimer) + : m_Context(ctx) + , m_StrTable(strTable) + , m_InputStreamFactory(inInputStreamFactory) + , m_PerfTimer(inTimer) + , mRefCount(0) + , m_PathBuilder(ForwardingAllocator(ctx.GetAllocator(), "SBufferManager::m_PathBuilder")) + , m_ImageMap(ctx.GetAllocator(), "SBufferManager::m_ImageMap") + , m_LoadedImageSetMutex(ctx.GetAllocator()) + , m_LoadedImageSet( + ForwardingAllocator(ctx.GetAllocator(), "SBufferManager::m_LoadedImageSet")) + , m_AliasImageMap(ctx.GetAllocator(), "SBufferManager::m_AliasImageMap") + , m_MeshMap(ctx.GetAllocator(), "SBufferManager::m_MeshMap") + , m_EntryBuffer(ctx.GetAllocator(), "SBufferManager::m_EntryBuffer") + , m_GPUSupportsDXT(ctx.AreDXTImagesSupported()) + , m_reloadableResources(false) + { + } + virtual ~SBufferManager() { Clear(); } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Context->GetAllocator()) + + CRegisteredString CombineBaseAndRelative(const char8_t *inBase, + const char8_t *inRelative) override + { + CFileTools::CombineBaseAndRelative(inBase, inRelative, m_PathBuilder); + return m_StrTable->RegisterStr(m_PathBuilder.c_str()); + } + + void SetImageHasTransparency(CRegisteredString inImagePath, bool inHasTransparency) override + { + pair theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + theImage.first->second.m_TextureFlags.SetHasTransparency(inHasTransparency); + } + + bool GetImageHasTransparency(CRegisteredString inSourcePath) const override + { + TImageMap::const_iterator theIter = m_ImageMap.find(inSourcePath); + if (theIter != m_ImageMap.end()) + return theIter->second.m_TextureFlags.HasTransparency(); + return false; + } + + void SetImageTransparencyToFalseIfNotSet(CRegisteredString inSourcePath) override + { + pair theImage = + m_ImageMap.insert(make_pair(inSourcePath, SImageEntry())); + // If we did actually insert something + if (theImage.second) + theImage.first->second.m_TextureFlags.SetHasTransparency(false); + } + + void SetInvertImageUVCoords(CRegisteredString inImagePath, bool inShouldInvertCoords) override + { + pair theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + theImage.first->second.m_TextureFlags.SetInvertUVCoords(inShouldInvertCoords); + } + + bool IsImageLoaded(CRegisteredString inSourcePath) override + { + Mutex::ScopedLock __locker(m_LoadedImageSetMutex); + return m_LoadedImageSet.find(inSourcePath) != m_LoadedImageSet.end(); + } + + bool AliasImagePath(CRegisteredString inSourcePath, CRegisteredString inAliasPath, + bool inIgnoreIfLoaded) override + { + if (inSourcePath.IsValid() == false || inAliasPath.IsValid() == false) + return false; + // If the image is loaded then we ignore this call in some cases. + if (inIgnoreIfLoaded && IsImageLoaded(inSourcePath)) + return false; + m_AliasImageMap.insert(eastl::make_pair(inSourcePath, inAliasPath)); + return true; + } + + void UnaliasImagePath(CRegisteredString inSourcePath) override + { + m_AliasImageMap.erase(inSourcePath); + } + + CRegisteredString GetImagePath(CRegisteredString inSourcePath) override + { + TAliasImageMap::iterator theAliasIter = m_AliasImageMap.find(inSourcePath); + if (theAliasIter != m_AliasImageMap.end()) + return theAliasIter->second; + return inSourcePath; + } + + CRegisteredString getImagePath(const QString &path) + { + TAliasImageMap::iterator theAliasIter + = m_AliasImageMap.find(m_StrTable->RegisterStr(qPrintable(path))); + if (theAliasIter != m_AliasImageMap.end()) + return theAliasIter->second; + return m_StrTable->RegisterStr(qPrintable(path)); + } + + static inline int wrapMod(int a, int base) + { + int ret = a % base; + if (ret < 0) + ret += base; + return ret; + } + + static inline void getWrappedCoords(int &sX, int &sY, int width, int height) + { + if (sY < 0) { + sX -= width >> 1; + sY = -sY; + } + if (sY >= height) { + sX += width >> 1; + sY = height - sY; + } + sX = wrapMod(sX, width); + sY = wrapMod(sY, height); + } + + template + void iterateAll(const V &vv, C c) + { + for (const auto x : vv) + c(x); + } + + void loadTextureImage(SReloadableImageTextureData &data) + { + CRegisteredString imagePath = getImagePath(data.m_path); + TImageMap::iterator theIter = m_ImageMap.find(imagePath); + if ((theIter == m_ImageMap.end() || theIter->second.m_Loaded == false) + && imagePath.IsValid()) { + NVScopedReleasable theLoadedImage; + SImageTextureData textureData; + + doImageLoad(imagePath, theLoadedImage); + + if (theLoadedImage) { + textureData = LoadRenderImage(imagePath, *theLoadedImage, data.m_scanTransparency, + data.m_bsdfMipmap); + data.m_Texture = textureData.m_Texture; + data.m_TextureFlags = textureData.m_TextureFlags; + data.m_BSDFMipMap = textureData.m_BSDFMipMap; + data.m_loaded = true; + iterateAll(data.m_callbacks, [](SImage *img){ img->m_Flags.SetDirty(true); }); + } else { + // We want to make sure that bad path fails once and doesn't fail over and over + // again which could slow down the system quite a bit. + pair theImage = + m_ImageMap.insert(make_pair(imagePath, SImageEntry())); + theImage.first->second.m_Loaded = true; + qCWarning(WARNING, "Failed to load image: %s", imagePath.c_str()); + theIter = theImage.first; + } + } else { + SImageEntry textureData = theIter->second; + if (textureData.m_Loaded) { + data.m_Texture = textureData.m_Texture; + data.m_TextureFlags = textureData.m_TextureFlags; + data.m_BSDFMipMap = textureData.m_BSDFMipMap; + data.m_loaded = true; + iterateAll(data.m_callbacks, [](SImage *img){ img->m_Flags.SetDirty(true); }); + } + } + } + + void unloadTextureImage(SReloadableImageTextureData &data) + { + CRegisteredString r = m_StrTable->RegisterStr(qPrintable(data.m_path)); + data.m_loaded = false; + data.m_Texture = nullptr; + data.m_BSDFMipMap = nullptr; + data.m_TextureFlags = {}; + iterateAll(data.m_callbacks, [](SImage *img){ img->m_Flags.SetDirty(true); }); + InvalidateBuffer(r); + } + + void loadSet(const QSet &imageSet) override + { + for (const auto &x : imageSet) { + if (!m_reloadableTextures.contains(x)) { + auto img = CreateReloadableImage(m_StrTable->RegisterStr(qPrintable(x)), false, + false); + img->m_initialized = false; + loadTextureImage(*m_reloadableTextures[x]); + } else if (!m_reloadableTextures[x]->m_loaded) { + loadTextureImage(*m_reloadableTextures[x]); + } + } + } + + void unloadSet(const QSet &imageSet) override + { + for (const auto &x : imageSet) { + if (m_reloadableTextures.contains(x)) { + if (m_reloadableTextures[x]->m_loaded) + unloadTextureImage(*m_reloadableTextures[x]); + } + } + } + + virtual ReloadableTexturePtr CreateReloadableImage(CRegisteredString inSourcePath, + bool inForceScanForTransparency, + bool inBsdfMipmaps) override + { + QString path = QString::fromLatin1(inSourcePath.c_str()); + const bool inserted = m_reloadableTextures.contains(path); + if (!inserted || (inserted && m_reloadableTextures[path]->m_initialized == false)) { + if (!inserted) + m_reloadableTextures.insert(path, ReloadableTexturePtr::create()); + m_reloadableTextures[path]->m_path = path; + m_reloadableTextures[path]->m_scanTransparency = inForceScanForTransparency; + m_reloadableTextures[path]->m_bsdfMipmap = inBsdfMipmaps; + m_reloadableTextures[path]->m_initialized = true; + + if (!m_reloadableResources) + loadTextureImage(*m_reloadableTextures[path]); + + CRegisteredString imagePath = getImagePath(path); + TImageMap::iterator theIter = m_ImageMap.find(imagePath); + if (theIter != m_ImageMap.end()) { + SImageEntry textureData = theIter->second; + if (textureData.m_Loaded) { + m_reloadableTextures[path]->m_Texture = textureData.m_Texture; + m_reloadableTextures[path]->m_TextureFlags = textureData.m_TextureFlags; + m_reloadableTextures[path]->m_BSDFMipMap = textureData.m_BSDFMipMap; + m_reloadableTextures[path]->m_loaded = true; + } + } + } + return m_reloadableTextures[path]; + } + + void doImageLoad(CRegisteredString inImagePath, + NVScopedReleasable &theLoadedImage) + { + SStackPerfTimer __perfTimer(m_PerfTimer, "Image Decompression"); + theLoadedImage = SLoadedTexture::Load( + inImagePath.c_str(), m_Context->GetFoundation(), *m_InputStreamFactory, + true, m_Context->GetRenderContextType()); + // Hackish solution to custom materials not finding their textures if they are used + // in sub-presentations. + if (!theLoadedImage) { + if (QDir(inImagePath.c_str()).isRelative()) { + QString searchPath = inImagePath.c_str(); + if (searchPath.startsWith(QLatin1String("./"))) + searchPath.prepend(QLatin1Char('.')); + int loops = 0; + while (!theLoadedImage && ++loops <= 3) { + theLoadedImage = SLoadedTexture::Load( + searchPath.toUtf8(), m_Context->GetFoundation(), + *m_InputStreamFactory, true, + m_Context->GetRenderContextType()); + searchPath.prepend(QLatin1String("../")); + } + } else { + // Some textures, for example environment maps for custom materials, + // have absolute path at this point. It points to the wrong place with + // the new project structure, so we need to split it up and construct + // the new absolute path here. + QString wholePath = inImagePath.c_str(); + QStringList splitPath = wholePath.split(QLatin1String("../")); + if (splitPath.size() > 1) { + QString searchPath = splitPath.at(0) + splitPath.at(1); + int loops = 0; + while (!theLoadedImage && ++loops <= 3) { + theLoadedImage = SLoadedTexture::Load( + searchPath.toUtf8(), m_Context->GetFoundation(), + *m_InputStreamFactory, true, + m_Context->GetRenderContextType()); + searchPath = splitPath.at(0); + for (int i = 0; i < loops; i++) + searchPath.append(QLatin1String("../")); + searchPath.append(splitPath.at(1)); + } + } + } + } + } + + void enableReloadableResources(bool enable) override + { + m_reloadableResources = enable; + } + + bool isReloadableResourcesEnabled() const override + { + return m_reloadableResources; + } + + SImageTextureData LoadRenderImage(CRegisteredString inImagePath, + SLoadedTexture &inLoadedImage, + bool inForceScanForTransparency, bool inBsdfMipmaps) override + { + SStackPerfTimer __perfTimer(m_PerfTimer, "Image Upload"); + { + Mutex::ScopedLock __mapLocker(m_LoadedImageSetMutex); + m_LoadedImageSet.insert(inImagePath); + } + pair theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + bool wasInserted = theImage.second; + theImage.first->second.m_Loaded = true; + // inLoadedImage.EnsureMultiplerOfFour( m_Context->GetFoundation(), inImagePath.c_str() ); + + NVRenderTexture2D *theTexture = m_Context->CreateTexture2D(); + if (inLoadedImage.data) { + qt3ds::render::NVRenderTextureFormats::Enum destFormat = inLoadedImage.format; + if (inBsdfMipmaps) { + if (m_Context->GetRenderContextType() == render::NVRenderContextValues::GLES2) + destFormat = qt3ds::render::NVRenderTextureFormats::RGBA8; + else + destFormat = qt3ds::render::NVRenderTextureFormats::RGBA16F; + } + else { + theTexture->SetTextureData( + NVDataRef((QT3DSU8 *)inLoadedImage.data, inLoadedImage.dataSizeInBytes), 0, + inLoadedImage.width, inLoadedImage.height, inLoadedImage.format, destFormat); + { + static int enable = qEnvironmentVariableIntValue("QT3DS_GENERATE_MIPMAPS"); + if (enable) { + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::LinearMipmapLinear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + theTexture->GenerateMipmaps(); + } + } + } + + if (inBsdfMipmaps + && NVRenderTextureFormats::isUncompressedTextureFormat(inLoadedImage.format)) { + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::LinearMipmapLinear); + Qt3DSRenderPrefilterTexture *theBSDFMipMap = theImage.first->second.m_BSDFMipMap; + if (theBSDFMipMap == NULL) { + theBSDFMipMap = Qt3DSRenderPrefilterTexture::Create( + m_Context, inLoadedImage.width, inLoadedImage.height, *theTexture, + destFormat, m_Context->GetFoundation()); + theImage.first->second.m_BSDFMipMap = theBSDFMipMap; + } + + if (theBSDFMipMap) { + theBSDFMipMap->Build(inLoadedImage.data, inLoadedImage.dataSizeInBytes, + inLoadedImage.format); + } + } + } else if (inLoadedImage.dds) { + theImage.first->second.m_Texture = theTexture; + bool supportsDXT = m_GPUSupportsDXT; + bool isDXT = NVRenderTextureFormats::isCompressedTextureFormat(inLoadedImage.format); + bool requiresDecompression = (supportsDXT == false && isDXT) || false; + // test code for DXT decompression + // if ( isDXT ) requiresDecompression = true; + if (requiresDecompression) { + qCWarning(WARNING, PERF_INFO, + "Image %s is DXT format which is unsupported by " + "the graphics subsystem, decompressing in CPU", + inImagePath.c_str()); + } + STextureData theDecompressedImage; + for (int idx = 0; idx < inLoadedImage.dds->numMipmaps; ++idx) { + if (inLoadedImage.dds->mipwidth[idx] && inLoadedImage.dds->mipheight[idx]) { + if (requiresDecompression == false) { + theTexture->SetTextureData( + toU8DataRef((char *)inLoadedImage.dds->data[idx], + (QT3DSU32)inLoadedImage.dds->size[idx]), + (QT3DSU8)idx, (QT3DSU32)inLoadedImage.dds->mipwidth[idx], + (QT3DSU32)inLoadedImage.dds->mipheight[idx], inLoadedImage.format); + } else { + theDecompressedImage = + inLoadedImage.DecompressDXTImage(idx, &theDecompressedImage); + + if (theDecompressedImage.data) { + theTexture->SetTextureData( + toU8DataRef((char *)theDecompressedImage.data, + (QT3DSU32)theDecompressedImage.dataSizeInBytes), + (QT3DSU8)idx, (QT3DSU32)inLoadedImage.dds->mipwidth[idx], + (QT3DSU32)inLoadedImage.dds->mipheight[idx], + theDecompressedImage.format); + } + } + } + } + if (theDecompressedImage.data) + inLoadedImage.ReleaseDecompressedTexture(theDecompressedImage); + } + if (wasInserted == true || inForceScanForTransparency) + theImage.first->second.m_TextureFlags.SetHasTransparency( + inLoadedImage.ScanForTransparency()); + theImage.first->second.m_Texture = theTexture; + return theImage.first->second; + } + + SImageTextureData LoadRenderImage(CRegisteredString inImagePath, + bool inForceScanForTransparency, bool inBsdfMipmaps) override + { + inImagePath = GetImagePath(inImagePath); + + if (!inImagePath.IsValid()) + return SImageEntry(); + + TImageMap::iterator theIter = m_ImageMap.find(inImagePath); + if (theIter == m_ImageMap.end() && inImagePath.IsValid()) { + NVScopedReleasable theLoadedImage; + + doImageLoad(inImagePath, theLoadedImage); + + if (theLoadedImage) { + return LoadRenderImage(inImagePath, *theLoadedImage, inForceScanForTransparency, + inBsdfMipmaps); + } else { + // We want to make sure that bad path fails once and doesn't fail over and over + // again + // which could slow down the system quite a bit. + pair theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + theImage.first->second.m_Loaded = true; + qCWarning(WARNING, "Failed to load image: %s", inImagePath.c_str()); + theIter = theImage.first; + } + } + return theIter->second; + } + + qt3dsimp::SMultiLoadResult LoadPrimitive(const char8_t *inRelativePath) + { + CRegisteredString theName(m_StrTable->RegisterStr(inRelativePath)); + if (m_PrimitiveNames[0].m_PrimitiveName.IsValid() == false) { + IStringTable &strTable(m_Context->GetStringTable()); + m_PrimitiveNames[0].m_PrimitiveName = strTable.RegisterStr("#Rectangle"); + m_PrimitiveNames[0].m_FileName = strTable.RegisterStr("Rectangle.mesh"); + m_PrimitiveNames[1].m_PrimitiveName = strTable.RegisterStr("#Sphere"); + m_PrimitiveNames[1].m_FileName = strTable.RegisterStr("Sphere.mesh"); + m_PrimitiveNames[2].m_PrimitiveName = strTable.RegisterStr("#Cube"); + m_PrimitiveNames[2].m_FileName = strTable.RegisterStr("Cube.mesh"); + m_PrimitiveNames[3].m_PrimitiveName = strTable.RegisterStr("#Cone"); + m_PrimitiveNames[3].m_FileName = strTable.RegisterStr("Cone.mesh"); + m_PrimitiveNames[4].m_PrimitiveName = strTable.RegisterStr("#Cylinder"); + m_PrimitiveNames[4].m_FileName = strTable.RegisterStr("Cylinder.mesh"); + } + for (size_t idx = 0; idx < 5; ++idx) { + if (m_PrimitiveNames[idx].m_PrimitiveName == theName) { + CFileTools::CombineBaseAndRelative(GetPrimitivesDirectory(), + m_PrimitiveNames[idx].m_FileName, m_PathBuilder); + QT3DSU32 id = 1; + NVScopedRefCounted theInStream( + m_InputStreamFactory->GetStreamForFile(m_PathBuilder.c_str())); + if (theInStream) + return qt3dsimp::Mesh::LoadMulti(m_Context->GetAllocator(), *theInStream, id); + else { + qCCritical(INTERNAL_ERROR, "Unable to find mesh primitive %s", + m_PathBuilder.c_str()); + return qt3dsimp::SMultiLoadResult(); + } + } + } + return qt3dsimp::SMultiLoadResult(); + } + + virtual NVConstDataRef CreatePackedPositionDataArray( + const qt3dsimp::SMultiLoadResult &inResult) + { + // we assume a position consists of 3 floats + QT3DSU32 vertexCount = inResult.m_Mesh->m_VertexBuffer.m_Data.size() + / inResult.m_Mesh->m_VertexBuffer.m_Stride; + QT3DSU32 dataSize = vertexCount * 3 * sizeof(QT3DSF32); + QT3DSF32 *posData = static_cast( + QT3DS_ALLOC(m_Context->GetAllocator(), dataSize, + "SRenderMesh::CreatePackedPositionDataArray")); + QT3DSU8 *baseOffset = reinterpret_cast(inResult.m_Mesh); + // copy position data + if (posData) { + QT3DSF32 *srcData = reinterpret_cast( + inResult.m_Mesh->m_VertexBuffer.m_Data.begin(baseOffset)); + QT3DSU32 srcStride = inResult.m_Mesh->m_VertexBuffer.m_Stride / sizeof(QT3DSF32); + QT3DSF32 *dstData = posData; + QT3DSU32 dstStride = 3; + + for (QT3DSU32 i = 0; i < vertexCount; ++i) { + dstData[0] = srcData[0]; + dstData[1] = srcData[1]; + dstData[2] = srcData[2]; + + dstData += dstStride; + srcData += srcStride; + } + + return toConstDataRef(reinterpret_cast(posData), dataSize); + } + + return NVConstDataRef(); + } + + SRenderMesh *createRenderMesh(const qt3dsimp::SMultiLoadResult &result) + { + SRenderMesh *theNewMesh = QT3DS_NEW(m_Context->GetAllocator(), SRenderMesh)( + qt3ds::render::NVRenderDrawMode::Triangles, + qt3ds::render::NVRenderWinding::CounterClockwise, result.m_Id, + m_Context->GetAllocator()); + QT3DSU8 *baseAddress = reinterpret_cast(result.m_Mesh); + NVConstDataRef theVBufData( + result.m_Mesh->m_VertexBuffer.m_Data.begin(baseAddress), + result.m_Mesh->m_VertexBuffer.m_Data.size()); + + NVRenderVertexBuffer *theVertexBuffer = m_Context->CreateVertexBuffer( + qt3ds::render::NVRenderBufferUsageType::Static, + result.m_Mesh->m_VertexBuffer.m_Data.m_Size, + result.m_Mesh->m_VertexBuffer.m_Stride, theVBufData); + + // create a tight packed position data VBO + // this should improve our depth pre pass rendering + NVRenderVertexBuffer *thePosVertexBuffer = nullptr; + NVConstDataRef posData = CreatePackedPositionDataArray(result); + if (posData.size()) { + thePosVertexBuffer + = m_Context->CreateVertexBuffer(qt3ds::render::NVRenderBufferUsageType::Static, + posData.size(), 3 * sizeof(QT3DSF32), posData); + } + + NVRenderIndexBuffer *theIndexBuffer = nullptr; + if (result.m_Mesh->m_IndexBuffer.m_Data.size()) { + using qt3ds::render::NVRenderComponentTypes; + QT3DSU32 theIndexBufferSize = result.m_Mesh->m_IndexBuffer.m_Data.size(); + NVRenderComponentTypes::Enum bufComponentType = + result.m_Mesh->m_IndexBuffer.m_ComponentType; + QT3DSU32 sizeofType + = qt3ds::render::NVRenderComponentTypes::getSizeofType(bufComponentType); + + if (sizeofType == 2 || sizeofType == 4) { + // Ensure type is unsigned; else things will fail in rendering pipeline. + if (bufComponentType == NVRenderComponentTypes::QT3DSI16) + bufComponentType = NVRenderComponentTypes::QT3DSU16; + if (bufComponentType == NVRenderComponentTypes::QT3DSI32) + bufComponentType = NVRenderComponentTypes::QT3DSU32; + + NVConstDataRef theIBufData( + result.m_Mesh->m_IndexBuffer.m_Data.begin(baseAddress), + result.m_Mesh->m_IndexBuffer.m_Data.size()); + theIndexBuffer = m_Context->CreateIndexBuffer( + qt3ds::render::NVRenderBufferUsageType::Static, bufComponentType, + theIndexBufferSize, theIBufData); + } else { + QT3DS_ASSERT(false); + } + } + nvvector &theEntryBuffer(m_EntryBuffer); + theEntryBuffer.resize(result.m_Mesh->m_VertexBuffer.m_Entries.size()); + for (QT3DSU32 entryIdx = 0, + entryEnd = result.m_Mesh->m_VertexBuffer.m_Entries.size(); + entryIdx < entryEnd; ++entryIdx) { + theEntryBuffer[entryIdx] + = result.m_Mesh->m_VertexBuffer.m_Entries.index(baseAddress, entryIdx) + .ToVertexBufferEntry(baseAddress); + } + // create our attribute layout + NVRenderAttribLayout *theAttribLayout + = m_Context->CreateAttributeLayout(theEntryBuffer); + // create our attribute layout for depth pass + qt3ds::render::NVRenderVertexBufferEntry theEntries[] = { + qt3ds::render::NVRenderVertexBufferEntry( + "attr_pos", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3), + }; + NVRenderAttribLayout *theAttribLayoutDepth + = m_Context->CreateAttributeLayout(toConstDataRef(theEntries, 1)); + + // create input assembler object + QT3DSU32 strides = result.m_Mesh->m_VertexBuffer.m_Stride; + QT3DSU32 offsets = 0; + NVRenderInputAssembler *theInputAssembler = m_Context->CreateInputAssembler( + theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer, + toConstDataRef(&strides, 1), toConstDataRef(&offsets, 1), + result.m_Mesh->m_DrawMode); + + // create depth input assembler object + QT3DSU32 posStrides = thePosVertexBuffer ? 3 * sizeof(QT3DSF32) : strides; + NVRenderInputAssembler *theInputAssemblerDepth = m_Context->CreateInputAssembler( + theAttribLayoutDepth, + toConstDataRef(thePosVertexBuffer ? &thePosVertexBuffer : &theVertexBuffer, 1), + theIndexBuffer, toConstDataRef(&posStrides, 1), toConstDataRef(&offsets, 1), + result.m_Mesh->m_DrawMode); + + NVRenderInputAssembler *theInputAssemblerPoints = m_Context->CreateInputAssembler( + theAttribLayoutDepth, + toConstDataRef(thePosVertexBuffer ? &thePosVertexBuffer : &theVertexBuffer, 1), + nullptr, toConstDataRef(&posStrides, 1), toConstDataRef(&offsets, 1), + NVRenderDrawMode::Points); + + if (!theInputAssembler || !theInputAssemblerDepth || !theInputAssemblerPoints) { + QT3DS_ASSERT(false); + return nullptr; + } + theNewMesh->m_Joints.resize(result.m_Mesh->m_Joints.size()); + for (QT3DSU32 jointIdx = 0, jointEnd = result.m_Mesh->m_Joints.size(); + jointIdx < jointEnd; ++jointIdx) { + const qt3dsimp::Joint &theImportJoint( + result.m_Mesh->m_Joints.index(baseAddress, jointIdx)); + SRenderJoint &theNewJoint(theNewMesh->m_Joints[jointIdx]); + theNewJoint.m_JointID = theImportJoint.m_JointID; + theNewJoint.m_ParentID = theImportJoint.m_ParentID; + memCopy(theNewJoint.m_invBindPose, theImportJoint.m_invBindPose, + 16 * sizeof(QT3DSF32)); + memCopy(theNewJoint.m_localToGlobalBoneSpace, + theImportJoint.m_localToGlobalBoneSpace, 16 * sizeof(QT3DSF32)); + } + + for (QT3DSU32 subsetIdx = 0, subsetEnd = result.m_Mesh->m_Subsets.size(); + subsetIdx < subsetEnd; ++subsetIdx) { + SRenderSubset theSubset(m_Context->GetAllocator()); + const qt3dsimp::MeshSubset &source( + result.m_Mesh->m_Subsets.index(baseAddress, subsetIdx)); + theSubset.m_Bounds = source.m_Bounds; + theSubset.m_Count = source.m_Count; + theSubset.m_Offset = source.m_Offset; + theSubset.m_Joints = theNewMesh->m_Joints; + theSubset.m_Name = m_StrTable->RegisterStr(source.m_Name.begin(baseAddress)); + theVertexBuffer->addRef(); + theSubset.m_VertexBuffer = theVertexBuffer; + if (thePosVertexBuffer) { + thePosVertexBuffer->addRef(); + theSubset.m_PosVertexBuffer = thePosVertexBuffer; + } + if (theIndexBuffer) { + theIndexBuffer->addRef(); + theSubset.m_IndexBuffer = theIndexBuffer; + } + theSubset.m_InputAssembler = theInputAssembler; + theSubset.m_InputAssemblerDepth = theInputAssemblerDepth; + theSubset.m_InputAssemblerPoints = theInputAssemblerPoints; + theSubset.m_PrimitiveType = result.m_Mesh->m_DrawMode; + theInputAssembler->addRef(); + theInputAssemblerDepth->addRef(); + theSubset.m_InputAssemblerPoints->addRef(); + theNewMesh->m_Subsets.push_back(theSubset); + } + // If we want to, we can break up models into sub-subsets. + // These are assumed to use the same material as the outer subset but have fewer triangles + // and should have a more exact bounding box. This sort of thing helps with using the + // frustum culling system but it is really done incorrectly. + // It should be done via some sort of oct-tree mechanism and so that the sub-subsets + // are spatially sorted and it should only be done upon save-to-binary with the + // results saved out to disk. As you can see, doing it properly requires some real + // engineering effort so it is somewhat unlikely it will ever happen. + // Or it could be done on import if someone really wants to change the mesh buffer + // format. Either way it isn't going to happen here and it isn't going to happen this way + // but this is a working example of using the technique. +#ifdef QT3DS_RENDER_GENERATE_SUB_SUBSETS + Option thePosAttrOpt + = theVertexBuffer->GetEntryByName("attr_pos"); + bool hasPosAttr = thePosAttrOpt.hasValue() + && thePosAttrOpt->m_ComponentType == qt3ds::render::NVRenderComponentTypes::QT3DSF32 + && thePosAttrOpt->m_NumComponents == 3; + + for (size_t subsetIdx = 0, subsetEnd = theNewMesh->m_Subsets.size(); + subsetIdx < subsetEnd; ++subsetIdx) { + SRenderSubset &theOuterSubset = theNewMesh->m_Subsets[subsetIdx]; + if (theOuterSubset.m_Count && theIndexBuffer + && theIndexBuffer->GetComponentType() + == qt3ds::render::NVRenderComponentTypes::QT3DSU16 + && theNewMesh->m_DrawMode == NVRenderDrawMode::Triangles && hasPosAttr) { + // Num tris in a sub subset. + QT3DSU32 theSubsetSize = 3334 * 3; // divisible by three. + size_t theNumSubSubsets = ((theOuterSubset.m_Count - 1) / theSubsetSize) + 1; + QT3DSU32 thePosAttrOffset = thePosAttrOpt->m_FirstItemOffset; + const QT3DSU8 *theVertData = result.m_Mesh->m_VertexBuffer.m_Data.begin(); + const QT3DSU8 *theIdxData = result.m_Mesh->m_IndexBuffer.m_Data.begin(); + QT3DSU32 theVertStride = result.m_Mesh->m_VertexBuffer.m_Stride; + QT3DSU32 theOffset = theOuterSubset.m_Offset; + QT3DSU32 theCount = theOuterSubset.m_Count; + for (size_t subSubsetIdx = 0, subSubsetEnd = theNumSubSubsets; + subSubsetIdx < subSubsetEnd; ++subSubsetIdx) { + SRenderSubsetBase theBase; + theBase.m_Offset = theOffset; + theBase.m_Count = NVMin(theSubsetSize, theCount); + theBase.m_Bounds.setEmpty(); + theCount -= theBase.m_Count; + theOffset += theBase.m_Count; + // Create new bounds. + // Offset is in item size, not bytes. + const QT3DSU16 *theSubsetIdxData + = reinterpret_cast(theIdxData + theBase.m_Offset * 2); + for (size_t theIdxIdx = 0, theIdxEnd = theBase.m_Count; + theIdxIdx < theIdxEnd; ++theIdxIdx) { + QT3DSU32 theVertOffset = theSubsetIdxData[theIdxIdx] * theVertStride; + theVertOffset += thePosAttrOffset; + QT3DSVec3 thePos = *( + reinterpret_cast(theVertData + theVertOffset)); + theBase.m_Bounds.include(thePos); + } + theOuterSubset.m_SubSubsets.push_back(theBase); + } + } else { + SRenderSubsetBase theBase; + theBase.m_Bounds = theOuterSubset.m_Bounds; + theBase.m_Count = theOuterSubset.m_Count; + theBase.m_Offset = theOuterSubset.m_Offset; + theOuterSubset.m_SubSubsets.push_back(theBase); + } + } +#endif + if (posData.size()) { + m_Context->GetAllocator().deallocate( + static_cast(const_cast(posData.begin()))); + } + + return theNewMesh; + } + + void loadCustomMesh(const QString &name, qt3dsimp::Mesh *mesh) override + { + if (!name.isEmpty() && mesh) { + CRegisteredString meshName = m_StrTable->RegisterStr(name); + pair theMesh + = m_MeshMap.insert({ meshName, static_cast(nullptr) }); + // Only create the mesh if it doesn't yet exist + if (theMesh.second) { + qt3dsimp::SMultiLoadResult result; + result.m_Mesh = mesh; + theMesh.first->second = createRenderMesh(result); + } + } + } + + SRenderMesh *LoadMesh(CRegisteredString inMeshPath) override + { + if (inMeshPath.IsValid() == false) + return nullptr; + pair theMesh = + m_MeshMap.insert(make_pair(inMeshPath, static_cast(nullptr))); + if (theMesh.second) { + // Check to see if this is primitive + qt3dsimp::SMultiLoadResult theResult = LoadPrimitive(inMeshPath); + + // Attempt a load from the filesystem if this mesh isn't a primitive. + if (!theResult.m_Mesh) { + m_PathBuilder = inMeshPath; + TStr::size_type pound = m_PathBuilder.rfind('#'); + QT3DSU32 id = 0; + if (pound != TStr::npos) { + id = QT3DSU32(atoi(m_PathBuilder.c_str() + pound + 1)); + m_PathBuilder.erase(m_PathBuilder.begin() + pound, m_PathBuilder.end()); + } + NVScopedRefCounted theStream( + m_InputStreamFactory->GetStreamForFile(m_PathBuilder.c_str())); + if (theStream) { + theResult = qt3dsimp::Mesh::LoadMulti( + m_Context->GetAllocator(), *theStream, id); + } + if (!theResult.m_Mesh) + qCWarning(WARNING, "Failed to load mesh: %s", m_PathBuilder.c_str()); + } + + if (theResult.m_Mesh) { + theMesh.first->second = createRenderMesh(theResult); + m_Context->GetAllocator().deallocate(theResult.m_Mesh); + } + } + return theMesh.first->second; + } + + SRenderMesh *CreateMesh(Qt3DSBCharPtr inSourcePath, QT3DSU8 *inVertData, QT3DSU32 inNumVerts, + QT3DSU32 inVertStride, QT3DSU32 *inIndexData, QT3DSU32 inIndexCount, + qt3ds::NVBounds3 inBounds) override + { + CRegisteredString sourcePath = m_StrTable->RegisterStr(inSourcePath); + + // eastl::pair thePair(sourcePath, (SRenderMesh*)NULL); + pair theMesh; + // Make sure there isn't already a buffer entry for this mesh. + if (m_MeshMap.contains(sourcePath)) { + theMesh = make_pair(m_MeshMap.find(sourcePath), true); + } else { + theMesh = m_MeshMap.insert(make_pair(sourcePath, (SRenderMesh *)NULL)); + } + + if (theMesh.second == true) { + SRenderMesh *theNewMesh = QT3DS_NEW(m_Context->GetAllocator(), SRenderMesh)( + qt3ds::render::NVRenderDrawMode::Triangles, + qt3ds::render::NVRenderWinding::CounterClockwise, 0, m_Context->GetAllocator()); + + // If we failed to create the RenderMesh, return a failure. + if (!theNewMesh) { + QT3DS_ASSERT(false); + return NULL; + } + + // Get rid of any old mesh that was sitting here and fill it with a new one. + // NOTE : This is assuming that the source of our mesh data doesn't do its own memory + // management and always returns new buffer pointers every time. + // Don't know for sure if that's what we'll get from our intended sources, but that's + // easily + // adjustable by looking for matching pointers in the Subsets. + if (theNewMesh && theMesh.first->second != NULL) { + delete theMesh.first->second; + theMesh.first->second = NULL; + } + + theMesh.first->second = theNewMesh; + QT3DSU32 vertDataSize = inNumVerts * inVertStride; + NVConstDataRef theVBufData(inVertData, vertDataSize); + // NVConstDataRef theVBufData( theResult.m_Mesh->m_VertexBuffer.m_Data.begin( + // baseAddress ) + // , theResult.m_Mesh->m_VertexBuffer.m_Data.size() ); + + NVRenderVertexBuffer *theVertexBuffer = + m_Context->CreateVertexBuffer(qt3ds::render::NVRenderBufferUsageType::Static, + vertDataSize, inVertStride, theVBufData); + NVRenderIndexBuffer *theIndexBuffer = NULL; + if (inIndexData != NULL && inIndexCount > 3) { + NVConstDataRef theIBufData((QT3DSU8 *)inIndexData, inIndexCount * sizeof(QT3DSU32)); + theIndexBuffer = + m_Context->CreateIndexBuffer(qt3ds::render::NVRenderBufferUsageType::Static, + qt3ds::render::NVRenderComponentTypes::QT3DSU32, + inIndexCount * sizeof(QT3DSU32), theIBufData); + } + + // WARNING + // Making an assumption here about the contents of the stream + // PKC TODO : We may have to consider some other format. + qt3ds::render::NVRenderVertexBufferEntry theEntries[] = { + qt3ds::render::NVRenderVertexBufferEntry("attr_pos", + qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3), + qt3ds::render::NVRenderVertexBufferEntry( + "attr_uv", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 2, 12), + qt3ds::render::NVRenderVertexBufferEntry( + "attr_norm", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3, 18), + }; + + // create our attribute layout + NVRenderAttribLayout *theAttribLayout = + m_Context->CreateAttributeLayout(toConstDataRef(theEntries, 3)); + /* + // create our attribute layout for depth pass + qt3ds::render::NVRenderVertexBufferEntry theEntriesDepth[] = { + qt3ds::render::NVRenderVertexBufferEntry( "attr_pos", + qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3 ), + }; + NVRenderAttribLayout* theAttribLayoutDepth = m_Context->CreateAttributeLayout( + toConstDataRef( theEntriesDepth, 1 ) ); + */ + // create input assembler object + QT3DSU32 strides = inVertStride; + QT3DSU32 offsets = 0; + NVRenderInputAssembler *theInputAssembler = m_Context->CreateInputAssembler( + theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer, + toConstDataRef(&strides, 1), toConstDataRef(&offsets, 1), + qt3ds::render::NVRenderDrawMode::Triangles); + + if (!theInputAssembler) { + QT3DS_ASSERT(false); + return NULL; + } + + // Pull out just the mesh object name from the total path + eastl::string fullName(inSourcePath); + eastl::string subName(inSourcePath); + if (fullName.rfind("#") != eastl::string::npos) { + subName = fullName.substr(fullName.rfind("#"), eastl::string::npos); + } + + theNewMesh->m_Joints.clear(); + SRenderSubset theSubset(m_Context->GetAllocator()); + theSubset.m_Bounds = inBounds; + theSubset.m_Count = inIndexCount; + theSubset.m_Offset = 0; + theSubset.m_Joints = theNewMesh->m_Joints; + theSubset.m_Name = m_StrTable->RegisterStr(subName.c_str()); + theVertexBuffer->addRef(); + theSubset.m_VertexBuffer = theVertexBuffer; + theSubset.m_PosVertexBuffer = NULL; + if (theIndexBuffer) + theIndexBuffer->addRef(); + theSubset.m_IndexBuffer = theIndexBuffer; + theSubset.m_InputAssembler = theInputAssembler; + theSubset.m_InputAssemblerDepth = theInputAssembler; + theSubset.m_InputAssemblerPoints = theInputAssembler; + theSubset.m_PrimitiveType = qt3ds::render::NVRenderDrawMode::Triangles; + theSubset.m_InputAssembler->addRef(); + theSubset.m_InputAssemblerDepth->addRef(); + theSubset.m_InputAssemblerPoints->addRef(); + theNewMesh->m_Subsets.push_back(theSubset); + } + + return theMesh.first->second; + } + + void ReleaseMesh(SRenderMesh &inMesh) + { + for (QT3DSU32 subsetIdx = 0, subsetEnd = inMesh.m_Subsets.size(); subsetIdx < subsetEnd; + ++subsetIdx) { + inMesh.m_Subsets[subsetIdx].m_VertexBuffer->release(); + if (inMesh.m_Subsets[subsetIdx].m_PosVertexBuffer) // can be NULL + inMesh.m_Subsets[subsetIdx].m_PosVertexBuffer->release(); + if (inMesh.m_Subsets[subsetIdx].m_IndexBuffer) // can be NULL + inMesh.m_Subsets[subsetIdx].m_IndexBuffer->release(); + inMesh.m_Subsets[subsetIdx].m_InputAssembler->release(); + inMesh.m_Subsets[subsetIdx].m_InputAssemblerDepth->release(); + if (inMesh.m_Subsets[subsetIdx].m_InputAssemblerPoints) + inMesh.m_Subsets[subsetIdx].m_InputAssemblerPoints->release(); + } + NVDelete(m_Context->GetAllocator(), &inMesh); + } + void ReleaseTexture(SImageEntry &inEntry) + { + if (inEntry.m_Texture) + inEntry.m_Texture->release(); + if (inEntry.m_BSDFMipMap) + inEntry.m_BSDFMipMap->release(); + } + void Clear() override + { + m_reloadableTextures.clear(); + for (TMeshMap::iterator iter = m_MeshMap.begin(), end = m_MeshMap.end(); iter != end; + ++iter) { + SRenderMesh *theMesh = iter->second; + if (theMesh) + ReleaseMesh(*theMesh); + } + m_MeshMap.clear(); + for (TImageMap::iterator iter = m_ImageMap.begin(), end = m_ImageMap.end(); iter != end; + ++iter) { + SImageEntry &theEntry = iter->second; + ReleaseTexture(theEntry); + } + m_ImageMap.clear(); + m_AliasImageMap.clear(); + { + Mutex::ScopedLock __locker(m_LoadedImageSetMutex); + m_LoadedImageSet.clear(); + } + } + void InvalidateBuffer(CRegisteredString inSourcePath) override + { + { + TMeshMap::iterator iter = m_MeshMap.find(inSourcePath); + if (iter != m_MeshMap.end()) { + if (iter->second) + ReleaseMesh(*iter->second); + m_MeshMap.erase(iter); + return; + } + } + { + TImageMap::iterator iter = m_ImageMap.find(inSourcePath); + if (iter != m_ImageMap.end()) { + SImageEntry &theEntry = iter->second; + ReleaseTexture(theEntry); + m_ImageMap.erase(inSourcePath); + { + Mutex::ScopedLock __locker(m_LoadedImageSetMutex); + m_LoadedImageSet.erase(inSourcePath); + } + } + } + } + IStringTable &GetStringTable() override { return *m_StrTable; } +}; +} + +IBufferManager &IBufferManager::Create(NVRenderContext &inRenderContext, IStringTable &inStrTable, + IInputStreamFactory &inFactory, IPerfTimer &inPerfTimer) +{ + return *QT3DS_NEW(inRenderContext.GetAllocator(), SBufferManager)(inRenderContext, inStrTable, + inFactory, inPerfTimer); +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.h b/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.h new file mode 100644 index 0000000..070ab67 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_BUFFER_MANAGER_H +#define QT3DS_RENDER_BUFFER_MANAGER_H +#include "Qt3DSRender.h" +#include "EASTL/utility.h" //pair +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/StringTable.h" +#include "Qt3DSRenderImageTextureData.h" +#include "foundation/Qt3DSBounds3.h" + +namespace qt3dsimp { + struct Mesh; +} + +namespace qt3ds { +namespace render { + + class IBufferManager : public NVRefCounted + { + protected: + virtual ~IBufferManager() {} + + public: + // Path manipulation used to get the final path form a base path plus relative extension + virtual CRegisteredString CombineBaseAndRelative(const char8_t *inBase, + const char8_t *inRelative) = 0; + virtual void SetImageHasTransparency(CRegisteredString inSourcePath, + bool inHasTransparency) = 0; + virtual bool GetImageHasTransparency(CRegisteredString inSourcePath) const = 0; + virtual void SetImageTransparencyToFalseIfNotSet(CRegisteredString inSourcePath) = 0; + virtual void SetInvertImageUVCoords(CRegisteredString inSourcePath, + bool inShouldInvertCoords) = 0; + + // Returns true if this image has been loaded into memory + // This call is threadsafe. Nothing else on this object is guaranteed to be. + virtual bool IsImageLoaded(CRegisteredString inSourcePath) = 0; + + // Alias one image path with another image path. Optionally this object will ignore the + // call if + // the source path is already loaded. Aliasing is currently used to allow a default image + // to be shown + // in place of an image that is loading offline. + // Returns true if the image was aliased, false otherwise. + virtual bool AliasImagePath(CRegisteredString inSourcePath, CRegisteredString inAliasPath, + bool inIgnoreIfLoaded) = 0; + virtual void UnaliasImagePath(CRegisteredString inSourcePath) = 0; + + // Returns the given source path unless the source path is aliased; in which case returns + // the aliased path. + virtual CRegisteredString GetImagePath(CRegisteredString inSourcePath) = 0; + // Returns a texture and a boolean indicating if this texture has transparency in it or not. + // Can't name this LoadImage because that gets mangled by windows to LoadImageA (uggh) + // In some cases we need to only scan particular images for transparency. + virtual SImageTextureData LoadRenderImage(CRegisteredString inImagePath, + SLoadedTexture &inTexture, + bool inForceScanForTransparency = false, + bool inBsdfMipmaps = false) = 0; + virtual SImageTextureData LoadRenderImage(CRegisteredString inSourcePath, + bool inForceScanForTransparency = false, + bool inBsdfMipmaps = false) = 0; + + virtual ReloadableTexturePtr CreateReloadableImage(CRegisteredString inSourcePath, + bool inForceScanForTransparency = false, + bool inBsdfMipmaps = false) = 0; + virtual void enableReloadableResources(bool enable) = 0; + virtual bool isReloadableResourcesEnabled() const = 0; + + virtual void loadSet(const QSet &imageSet) = 0; + virtual void unloadSet(const QSet &imageSet) = 0; + + virtual void loadCustomMesh(const QString &name, qt3dsimp::Mesh *mesh) = 0; + virtual SRenderMesh *LoadMesh(CRegisteredString inSourcePath) = 0; + + virtual SRenderMesh *CreateMesh(const char *inSourcePath, QT3DSU8 *inVertData, + QT3DSU32 inNumVerts, QT3DSU32 inVertStride, QT3DSU32 *inIndexData, + QT3DSU32 inIndexCount, qt3ds::NVBounds3 inBounds) = 0; + + // Remove *all* buffers from the buffer manager; + virtual void Clear() = 0; + virtual void InvalidateBuffer(CRegisteredString inSourcePath) = 0; + virtual IStringTable &GetStringTable() = 0; + + static IBufferManager &Create(NVRenderContext &inRenderContext, IStringTable &inStrTable, + IInputStreamFactory &inInputStreamFactory, + IPerfTimer &inTimer); + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.cpp new file mode 100644 index 0000000..72495cd --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.cpp @@ -0,0 +1,538 @@ +/**************************************************************************** +** +** 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 "Qt3DSRenderImageBatchLoader.h" +#include "foundation/Qt3DSMutex.h" +#include "foundation/Qt3DSSync.h" +#include "foundation/Qt3DSContainers.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/StringTable.h" +#include "Qt3DSRenderInputStreamFactory.h" +#include "Qt3DSRenderBufferManager.h" +#include "Qt3DSRenderThreadPool.h" +#include "Qt3DSRenderImageScaler.h" +#include "Qt3DSRenderLoadedTexture.h" +#include "foundation/Qt3DSInvasiveLinkedList.h" +#include "foundation/Qt3DSPool.h" +#include "foundation/Qt3DSPerfTimer.h" + +using namespace qt3ds::render; + +namespace { + +struct SImageLoaderBatch; +typedef Mutex::ScopedLock TScopedLock; + +struct SLoadingImage +{ + SImageLoaderBatch *m_Batch; + CRegisteredString m_SourcePath; + QT3DSU64 m_TaskId; + SLoadingImage *m_Tail; + + // Called from main thread + SLoadingImage(CRegisteredString inSourcePath) + : m_Batch(NULL) + , m_SourcePath(inSourcePath) + , m_TaskId(0) + , m_Tail(NULL) + { + } + SLoadingImage() + : m_Batch(NULL) + , m_TaskId(0) + , m_Tail(NULL) + { + } + // Called from main thread + void Setup(SImageLoaderBatch &inBatch); + + // Called from loader thread + static void LoadImage(void *inImg); + + // Potentially called from loader thread + static void TaskCancelled(void *inImg); +}; + +struct SLoadingImageTailOp +{ + SLoadingImage *get(SLoadingImage &inImg) { return inImg.m_Tail; } + void set(SLoadingImage &inImg, SLoadingImage *inItem) { inImg.m_Tail = inItem; } +}; + +typedef InvasiveSingleLinkedList TLoadingImageList; + +struct SBatchLoader; + +struct SImageLoaderBatch +{ + // All variables setup in main thread and constant from then on except + // loaded image count. + SBatchLoader &m_Loader; + NVScopedRefCounted m_LoadListener; + Sync m_LoadEvent; + Mutex m_LoadMutex; + TLoadingImageList m_Images; + + TImageBatchId m_BatchId; + // Incremented in main thread + QT3DSU32 m_LoadedOrCanceledImageCount; + QT3DSU32 m_FinalizedImageCount; + QT3DSU32 m_NumImages; + NVRenderContextType m_contextType; + bool m_preferKTX; + bool m_ibl; + + // Called from main thread + static SImageLoaderBatch *CreateLoaderBatch(SBatchLoader &inLoader, TImageBatchId inBatchId, + NVConstDataRef inSourcePaths, + CRegisteredString inImageTillLoaded, + IImageLoadListener *inListener, + NVRenderContextType contextType, + bool preferKTX, bool ibl); + + // Called from main thread + SImageLoaderBatch(SBatchLoader &inLoader, IImageLoadListener *inLoadListener, + const TLoadingImageList &inImageList, TImageBatchId inBatchId, + QT3DSU32 inImageCount, NVRenderContextType contextType, + bool preferKTX, bool ibl); + + // Called from main thread + ~SImageLoaderBatch(); + + // Called from main thread + bool IsLoadingFinished() + { + Mutex::ScopedLock __locker(m_LoadMutex); + return m_LoadedOrCanceledImageCount >= m_NumImages; + } + + bool IsFinalizedFinished() + { + Mutex::ScopedLock __locker(m_LoadMutex); + return m_FinalizedImageCount >= m_NumImages; + } + + void IncrementLoadedImageCount() + { + Mutex::ScopedLock __locker(m_LoadMutex); + ++m_LoadedOrCanceledImageCount; + } + void IncrementFinalizedImageCount() + { + Mutex::ScopedLock __locker(m_LoadMutex); + ++m_FinalizedImageCount; + } + // Called from main thread + void Cancel(); + void Cancel(CRegisteredString inSourcePath); +}; + +struct SBatchLoadedImage +{ + CRegisteredString m_SourcePath; + SLoadedTexture *m_Texture; + SImageLoaderBatch *m_Batch; + SBatchLoadedImage() + : m_Texture(NULL) + , m_Batch(NULL) + { + } + + // Called from loading thread + SBatchLoadedImage(CRegisteredString inSourcePath, SLoadedTexture *inTexture, + SImageLoaderBatch &inBatch) + : m_SourcePath(inSourcePath) + , m_Texture(inTexture) + , m_Batch(&inBatch) + { + } + + // Called from main thread + bool Finalize(IBufferManager &inMgr); +}; + +struct SBatchLoader : public IImageBatchLoader +{ + typedef nvhash_map TImageLoaderBatchMap; + typedef nvhash_map TSourcePathToBatchMap; + typedef Pool TLoadingImagePool; + typedef Pool TBatchPool; + + // Accessed from loader thread + NVFoundationBase &m_Foundation; + volatile QT3DSI32 mRefCount; + // Accessed from loader thread + IInputStreamFactory &m_InputStreamFactory; + //!!Not threadsafe! accessed only from main thread + IBufferManager &m_BufferManager; + // Accessed from main thread + IThreadPool &m_ThreadPool; + // Accessed from both threads + IPerfTimer &m_PerfTimer; + // main thread + TImageBatchId m_NextBatchId; + // main thread + TImageLoaderBatchMap m_Batches; + // main thread + Mutex m_LoaderMutex; + + // Both loader and main threads + nvvector m_LoadedImages; + // main thread + nvvector m_FinishedBatches; + // main thread + TSourcePathToBatchMap m_SourcePathToBatches; + // main thread + nvvector m_LoaderBuilderWorkspace; + TLoadingImagePool m_LoadingImagePool; + TBatchPool m_BatchPool; + + SBatchLoader(NVFoundationBase &inFoundation, IInputStreamFactory &inFactory, + IBufferManager &inBufferManager, IThreadPool &inThreadPool, IPerfTimer &inTimer) + : m_Foundation(inFoundation) + , mRefCount(0) + , m_InputStreamFactory(inFactory) + , m_BufferManager(inBufferManager) + , m_ThreadPool(inThreadPool) + , m_PerfTimer(inTimer) + , m_NextBatchId(1) + , m_Batches(inFoundation.getAllocator(), "SBatchLoader::m_Batches") + , m_LoaderMutex(inFoundation.getAllocator()) + , m_LoadedImages(inFoundation.getAllocator(), "SBatchLoader::m_LoadedImages") + , m_FinishedBatches(inFoundation.getAllocator(), "SBatchLoader::m_FinishedBatches") + , m_SourcePathToBatches(inFoundation.getAllocator(), "SBatchLoader::m_SourcePathToBatches") + , m_LoaderBuilderWorkspace(inFoundation.getAllocator(), + "SBatchLoader::m_LoaderBuilderWorkspace") + , m_LoadingImagePool( + ForwardingAllocator(inFoundation.getAllocator(), "SBatchLoader::m_LoadingImagePool")) + , m_BatchPool(ForwardingAllocator(inFoundation.getAllocator(), "SBatchLoader::m_BatchPool")) + { + } + + virtual ~SBatchLoader() + { + nvvector theCancelledBatches(m_Foundation.getAllocator(), "~SBatchLoader"); + for (TImageLoaderBatchMap::iterator theIter = m_Batches.begin(), theEnd = m_Batches.end(); + theIter != theEnd; ++theIter) { + theIter->second->Cancel(); + theCancelledBatches.push_back(theIter->second->m_BatchId); + } + for (QT3DSU32 idx = 0, end = theCancelledBatches.size(); idx < end; ++idx) + BlockUntilLoaded(theCancelledBatches[idx]); + + QT3DS_ASSERT(m_Batches.size() == 0); + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + // Returns an ID to the load request. Request a block of images to be loaded. + // Also takes an image that the buffer system will return when requested for the given source + // paths + // until said path is loaded. + // An optional listener can be passed in to get callbacks about the batch. + TImageBatchId LoadImageBatch(NVConstDataRef inSourcePaths, + CRegisteredString inImageTillLoaded, + IImageLoadListener *inListener, + NVRenderContextType contextType, + bool preferKTX, bool iblImages) override + { + if (inSourcePaths.size() == 0) + return 0; + + TScopedLock __loaderLock(m_LoaderMutex); + + TImageBatchId theBatchId = 0; + + // Empty loop intentional to find an unused batch id. + for (theBatchId = m_NextBatchId; m_Batches.find(theBatchId) != m_Batches.end(); + ++m_NextBatchId, theBatchId = m_NextBatchId) { + } + + SImageLoaderBatch *theBatch(SImageLoaderBatch::CreateLoaderBatch( + *this, theBatchId, inSourcePaths, inImageTillLoaded, inListener, contextType, + preferKTX, iblImages)); + if (theBatch) { + m_Batches.insert(eastl::make_pair(theBatchId, theBatch)); + return theBatchId; + } + return 0; + } + + void CancelImageBatchLoading(TImageBatchId inBatchId) override + { + SImageLoaderBatch *theBatch(GetBatch(inBatchId)); + if (theBatch) + theBatch->Cancel(); + } + + // Blocks if the image is currently in-flight + void CancelImageLoading(CRegisteredString inSourcePath) override + { + TScopedLock __loaderLock(m_LoaderMutex); + TSourcePathToBatchMap::iterator theIter = m_SourcePathToBatches.find(inSourcePath); + if (theIter != m_SourcePathToBatches.end()) { + TImageBatchId theBatchId = theIter->second; + TImageLoaderBatchMap::iterator theBatchIter = m_Batches.find(theBatchId); + if (theBatchIter != m_Batches.end()) + theBatchIter->second->Cancel(inSourcePath); + } + } + + SImageLoaderBatch *GetBatch(TImageBatchId inId) + { + TScopedLock __loaderLock(m_LoaderMutex); + TImageLoaderBatchMap::iterator theIter = m_Batches.find(inId); + if (theIter != m_Batches.end()) + return theIter->second; + return NULL; + } + + void BlockUntilLoaded(TImageBatchId inId) override + { + for (SImageLoaderBatch *theBatch = GetBatch(inId); theBatch; theBatch = GetBatch(inId)) { + // Only need to block if images aren't loaded. Don't need to block if they aren't + // finalized. + if (!theBatch->IsLoadingFinished()) { + theBatch->m_LoadEvent.wait(200); + theBatch->m_LoadEvent.reset(); + } + BeginFrame(true); + } + } + void ImageLoaded(SLoadingImage &inImage, SLoadedTexture *inTexture) + { + TScopedLock __loaderLock(m_LoaderMutex); + m_LoadedImages.push_back( + SBatchLoadedImage(inImage.m_SourcePath, inTexture, *inImage.m_Batch)); + inImage.m_Batch->IncrementLoadedImageCount(); + inImage.m_Batch->m_LoadEvent.set(); + } + // These are called by the render context, users don't need to call this. + void BeginFrame(bool firstFrame) override + { + TScopedLock __loaderLock(m_LoaderMutex); + // Pass 1 - send out all image loaded signals + for (QT3DSU32 idx = 0, end = m_LoadedImages.size(); idx < end; ++idx) { + + m_SourcePathToBatches.erase(m_LoadedImages[idx].m_SourcePath); + m_LoadedImages[idx].Finalize(m_BufferManager); + m_LoadedImages[idx].m_Batch->IncrementFinalizedImageCount(); + if (m_LoadedImages[idx].m_Batch->IsFinalizedFinished()) + m_FinishedBatches.push_back(m_LoadedImages[idx].m_Batch->m_BatchId); + if (!firstFrame) + break; + } + if (firstFrame) + m_LoadedImages.clear(); + else if (m_LoadedImages.size()) + m_LoadedImages.erase(m_LoadedImages.begin()); + // pass 2 - clean up any existing batches. + for (QT3DSU32 idx = 0, end = m_FinishedBatches.size(); idx < end; ++idx) { + TImageLoaderBatchMap::iterator theIter = m_Batches.find(m_FinishedBatches[idx]); + if (theIter != m_Batches.end()) { + SImageLoaderBatch *theBatch = theIter->second; + if (theBatch->m_LoadListener) + theBatch->m_LoadListener->OnImageBatchComplete(theBatch->m_BatchId); + m_Batches.erase(m_FinishedBatches[idx]); + theBatch->~SImageLoaderBatch(); + m_BatchPool.deallocate(theBatch); + } + } + m_FinishedBatches.clear(); + } + + void EndFrame() override {} +}; + +void SLoadingImage::Setup(SImageLoaderBatch &inBatch) +{ + m_Batch = &inBatch; + m_TaskId = inBatch.m_Loader.m_ThreadPool.AddTask(this, LoadImage, TaskCancelled); +} + +void SLoadingImage::LoadImage(void *inImg) +{ + SLoadingImage *theThis = reinterpret_cast(inImg); + SStackPerfTimer theTimer(theThis->m_Batch->m_Loader.m_PerfTimer, "Image Decompression"); + if (theThis->m_Batch->m_Loader.m_BufferManager.IsImageLoaded(theThis->m_SourcePath) == false) { + SLoadedTexture *theTexture = SLoadedTexture::Load( + theThis->m_SourcePath.c_str(), theThis->m_Batch->m_Loader.m_Foundation, + theThis->m_Batch->m_Loader.m_InputStreamFactory, true, + theThis->m_Batch->m_contextType, + theThis->m_Batch->m_preferKTX); + // if ( theTexture ) + // theTexture->EnsureMultiplerOfFour( theThis->m_Batch->m_Loader.m_Foundation, + //theThis->m_SourcePath.c_str() ); + + theThis->m_Batch->m_Loader.ImageLoaded(*theThis, theTexture); + } else { + theThis->m_Batch->m_Loader.ImageLoaded(*theThis, NULL); + } +} + +void SLoadingImage::TaskCancelled(void *inImg) +{ + SLoadingImage *theThis = reinterpret_cast(inImg); + theThis->m_Batch->m_Loader.ImageLoaded(*theThis, NULL); +} + +bool SBatchLoadedImage::Finalize(IBufferManager &inMgr) +{ + if (m_Texture) { + eastl::string thepath(m_SourcePath); + bool isIBL = this->m_Batch->m_ibl; + inMgr.LoadRenderImage(m_SourcePath, *m_Texture, false, isIBL); + inMgr.UnaliasImagePath(m_SourcePath); + } + if (m_Batch->m_LoadListener) + m_Batch->m_LoadListener->OnImageLoadComplete( + m_SourcePath, m_Texture ? ImageLoadResult::Succeeded : ImageLoadResult::Failed); + + if (m_Texture) { + m_Texture->release(); + return true; + } + + return false; +} + +SImageLoaderBatch * +SImageLoaderBatch::CreateLoaderBatch(SBatchLoader &inLoader, TImageBatchId inBatchId, + NVConstDataRef inSourcePaths, + CRegisteredString inImageTillLoaded, + IImageLoadListener *inListener, + NVRenderContextType contextType, + bool preferKTX, bool iblImages) +{ + TLoadingImageList theImages; + QT3DSU32 theLoadingImageCount = 0; + for (QT3DSU32 idx = 0, end = inSourcePaths.size(); idx < end; ++idx) { + CRegisteredString theSourcePath(inSourcePaths[idx]); + + if (theSourcePath.IsValid() == false) + continue; + + if (inLoader.m_BufferManager.IsImageLoaded(theSourcePath)) + continue; + + eastl::pair theInserter = + inLoader.m_SourcePathToBatches.insert(eastl::make_pair(inSourcePaths[idx], inBatchId)); + + // If the loader has already seen this image. + if (theInserter.second == false) + continue; + + if (inImageTillLoaded.IsValid()) { + // Alias the image so any further requests for this source path will result in + // the default images (image till loaded). + bool aliasSuccess = + inLoader.m_BufferManager.AliasImagePath(theSourcePath, inImageTillLoaded, true); + (void)aliasSuccess; + QT3DS_ASSERT(aliasSuccess); + } + + theImages.push_front( + *inLoader.m_LoadingImagePool.construct(theSourcePath, __FILE__, __LINE__)); + ++theLoadingImageCount; + } + if (theImages.empty() == false) { + SImageLoaderBatch *theBatch = + (SImageLoaderBatch *)inLoader.m_BatchPool.allocate(__FILE__, __LINE__); + new (theBatch) + SImageLoaderBatch(inLoader, inListener, theImages, inBatchId, theLoadingImageCount, + contextType, preferKTX, iblImages); + return theBatch; + } + return NULL; +} + +SImageLoaderBatch::SImageLoaderBatch(SBatchLoader &inLoader, IImageLoadListener *inLoadListener, + const TLoadingImageList &inImageList, TImageBatchId inBatchId, + QT3DSU32 inImageCount, NVRenderContextType contextType, + bool preferKTX, bool ibl) + : m_Loader(inLoader) + , m_LoadListener(inLoadListener) + , m_LoadEvent(inLoader.m_Foundation.getAllocator()) + , m_LoadMutex(inLoader.m_Foundation.getAllocator()) + , m_Images(inImageList) + , m_BatchId(inBatchId) + , m_LoadedOrCanceledImageCount(0) + , m_FinalizedImageCount(0) + , m_NumImages(inImageCount) + , m_contextType(contextType) + , m_preferKTX(preferKTX) + , m_ibl(ibl) +{ + for (TLoadingImageList::iterator iter = m_Images.begin(), end = m_Images.end(); iter != end; + ++iter) { + iter->Setup(*this); + } +} + +SImageLoaderBatch::~SImageLoaderBatch() +{ + for (TLoadingImageList::iterator iter = m_Images.begin(), end = m_Images.end(); iter != end; + ++iter) { + TLoadingImageList::iterator temp(iter); + ++iter; + m_Loader.m_LoadingImagePool.deallocate(temp.m_Obj); + } +} + +void SImageLoaderBatch::Cancel() +{ + for (TLoadingImageList::iterator iter = m_Images.begin(), end = m_Images.end(); iter != end; + ++iter) + m_Loader.m_ThreadPool.CancelTask(iter->m_TaskId); +} + +void SImageLoaderBatch::Cancel(CRegisteredString inSourcePath) +{ + for (TLoadingImageList::iterator iter = m_Images.begin(), end = m_Images.end(); iter != end; + ++iter) { + if (iter->m_SourcePath == inSourcePath) { + m_Loader.m_ThreadPool.CancelTask(iter->m_TaskId); + break; + } + } +} +} + +IImageBatchLoader &IImageBatchLoader::CreateBatchLoader(NVFoundationBase &inFoundation, + IInputStreamFactory &inFactory, + IBufferManager &inBufferManager, + IThreadPool &inThreadPool, + IPerfTimer &inTimer) +{ + return *QT3DS_NEW(inFoundation.getAllocator(), + SBatchLoader)(inFoundation, inFactory, inBufferManager, inThreadPool, inTimer); +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.h b/src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.h new file mode 100644 index 0000000..2805940 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderImageBatchLoader.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_THREADED_IMAGE_LOADER_H +#define QT3DS_RENDER_THREADED_IMAGE_LOADER_H +#include "Qt3DSRender.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/Qt3DSDataRef.h" +#include "render/Qt3DSRenderBaseTypes.h" + +namespace qt3ds { +namespace render { + struct ImageLoadResult + { + enum Enum { + Succeeded, + Failed, + }; + }; + + class IImageLoadListener : public NVRefCounted + { + protected: + virtual ~IImageLoadListener() {} + + public: + virtual void OnImageLoadComplete(CRegisteredString inPath, + ImageLoadResult::Enum inResult) = 0; + virtual void OnImageBatchComplete(QT3DSU64 inBatch) = 0; + }; + + typedef QT3DSU32 TImageBatchId; + + class IImageBatchLoader : public NVRefCounted + { + protected: + virtual ~IImageBatchLoader() {} + + public: + // Returns an ID to the load request. Request a block of images to be loaded. + // Also takes an image that the buffer system will return when requested for the given + // source paths + // until said path is loaded. + // An optional listener can be passed in to get callbacks about the batch. + virtual TImageBatchId LoadImageBatch(NVConstDataRef inSourcePaths, + CRegisteredString inImageTillLoaded, + IImageLoadListener *inListener, + NVRenderContextType type, + bool preferKTX, bool iblImages) = 0; + // Blocks if any of the images in the batch are in flight + virtual void CancelImageBatchLoading(TImageBatchId inBatchId) = 0; + // Blocks if the image is currently in-flight + virtual void CancelImageLoading(CRegisteredString inSourcePath) = 0; + // Block until every image in the batch is loaded. + virtual void BlockUntilLoaded(TImageBatchId inId) = 0; + + // These are called by the render context, users don't need to call this. + virtual void BeginFrame(bool firstFrame) = 0; + virtual void EndFrame() = 0; + + static IImageBatchLoader &CreateBatchLoader(NVFoundationBase &inFoundation, + IInputStreamFactory &inFactory, + IBufferManager &inBufferManager, + IThreadPool &inThreadPool, IPerfTimer &inTimer); + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.cpp new file mode 100644 index 0000000..1176273 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.cpp @@ -0,0 +1,715 @@ +/**************************************************************************** +** +** 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 "Qt3DSRenderLoadedTexture.h" +#include "foundation/IOStreams.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "Qt3DSDMWindowsCompatibility.h" +#include "Qt3DSRenderInputStreamFactory.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "Qt3DSRenderImageScaler.h" +#include "Qt3DSTextRenderer.h" +#include + +using namespace qt3ds::render; + +SLoadedTexture *SLoadedTexture::LoadQImage(const QString &inPath, QT3DSI32 flipVertical, + NVFoundationBase &fnd, + NVRenderContextType renderContextType) +{ + Q_UNUSED(flipVertical) + Q_UNUSED(renderContextType) + SLoadedTexture *retval(NULL); + NVAllocatorCallback &alloc(fnd.getAllocator()); + QImage image(inPath); + const QImage::Format format = image.format(); + switch (format) { + case QImage::Format_RGBA64: + image = image.convertToFormat(QImage::Format_RGBA8888); + break; + case QImage::Format_RGBX64: + image = image.convertToFormat(QImage::Format_RGBX8888); + break; + default: + break; + } + image = image.mirrored(); + image = image.rgbSwapped(); + retval = QT3DS_NEW(alloc, SLoadedTexture)(alloc); + retval->width = image.width(); + retval->height = image.height(); + retval->components = image.pixelFormat().channelCount(); + retval->image = image; + retval->data = (void*)retval->image.bits(); + retval->dataSizeInBytes = image.byteCount(); + retval->setFormatFromComponents(); + return retval; +} + + +namespace { + +/** + !!Large section of code ripped from FreeImage!! + +*/ +// ---------------------------------------------------------- +// Structures used by DXT textures +// ---------------------------------------------------------- +typedef QT3DSU8 BYTE; +typedef QT3DSU16 WORD; + +typedef struct tagColor8888 +{ + BYTE b; + BYTE g; + BYTE r; + BYTE a; +} Color8888; + +typedef struct tagColor565 +{ + WORD b : 5; + WORD g : 6; + WORD r : 5; +} Color565; + +typedef struct tagDXTColBlock +{ + Color565 colors[2]; + BYTE row[4]; +} DXTColBlock; + +typedef struct tagDXTAlphaBlockExplicit +{ + WORD row[4]; +} DXTAlphaBlockExplicit; + +typedef struct tagDXTAlphaBlock3BitLinear +{ + BYTE alpha[2]; + BYTE data[6]; +} DXTAlphaBlock3BitLinear; + +typedef struct tagDXT1Block +{ + DXTColBlock color; +} DXT1Block; + +typedef struct tagDXT3Block +{ // also used by dxt2 + DXTAlphaBlockExplicit alpha; + DXTColBlock color; +} DXT3Block; + +typedef struct tagDXT5Block +{ // also used by dxt4 + DXTAlphaBlock3BitLinear alpha; + DXTColBlock color; +} DXT5Block; + +static void GetBlockColors(const DXTColBlock &block, Color8888 colors[4], bool isDXT1) +{ + int i; + for (i = 0; i < 2; i++) { + colors[i].a = 0xff; + colors[i].r = (BYTE)(block.colors[i].r * 0xff / 0x1f); + colors[i].g = (BYTE)(block.colors[i].g * 0xff / 0x3f); + colors[i].b = (BYTE)(block.colors[i].b * 0xff / 0x1f); + } + + WORD *wCol = (WORD *)block.colors; + if (wCol[0] > wCol[1] || !isDXT1) { + // 4 color block + for (i = 0; i < 2; i++) { + colors[i + 2].a = 0xff; + colors[i + 2].r = + (BYTE)((WORD(colors[0].r) * (2 - i) + WORD(colors[1].r) * (1 + i)) / 3); + colors[i + 2].g = + (BYTE)((WORD(colors[0].g) * (2 - i) + WORD(colors[1].g) * (1 + i)) / 3); + colors[i + 2].b = + (BYTE)((WORD(colors[0].b) * (2 - i) + WORD(colors[1].b) * (1 + i)) / 3); + } + } else { + // 3 color block, number 4 is transparent + colors[2].a = 0xff; + colors[2].r = (BYTE)((WORD(colors[0].r) + WORD(colors[1].r)) / 2); + colors[2].g = (BYTE)((WORD(colors[0].g) + WORD(colors[1].g)) / 2); + colors[2].b = (BYTE)((WORD(colors[0].b) + WORD(colors[1].b)) / 2); + + colors[3].a = 0x00; + colors[3].g = 0x00; + colors[3].b = 0x00; + colors[3].r = 0x00; + } +} + +struct DXT_INFO_1 +{ + typedef DXT1Block Block; + enum { isDXT1 = 1, bytesPerBlock = 8 }; +}; + +struct DXT_INFO_3 +{ + typedef DXT3Block Block; + enum { isDXT1 = 1, bytesPerBlock = 16 }; +}; + +struct DXT_INFO_5 +{ + typedef DXT5Block Block; + enum { isDXT1 = 1, bytesPerBlock = 16 }; +}; + +template +class DXT_BLOCKDECODER_BASE +{ +protected: + Color8888 m_colors[4]; + const typename INFO::Block *m_pBlock; + unsigned m_colorRow; + +public: + void Setup(const BYTE *pBlock) + { + m_pBlock = (const typename INFO::Block *)pBlock; + GetBlockColors(m_pBlock->color, m_colors, INFO::isDXT1); + } + + void SetY(int y) { m_colorRow = m_pBlock->color.row[y]; } + + void GetColor(int x, int y, Color8888 &color) + { + Q_UNUSED(y) + unsigned bits = (m_colorRow >> (x * 2)) & 3; + color = m_colors[bits]; + std::swap(color.r, color.b); + } +}; + +class DXT_BLOCKDECODER_1 : public DXT_BLOCKDECODER_BASE +{ +public: + typedef DXT_INFO_1 INFO; +}; + +class DXT_BLOCKDECODER_3 : public DXT_BLOCKDECODER_BASE +{ +public: + typedef DXT_BLOCKDECODER_BASE base; + typedef DXT_INFO_3 INFO; + +protected: + unsigned m_alphaRow; + +public: + void SetY(int y) + { + base::SetY(y); + m_alphaRow = m_pBlock->alpha.row[y]; + } + + void GetColor(int x, int y, Color8888 &color) + { + base::GetColor(x, y, color); + const unsigned bits = (m_alphaRow >> (x * 4)) & 0xF; + color.a = (BYTE)((bits * 0xFF) / 0xF); + } +}; + +class DXT_BLOCKDECODER_5 : public DXT_BLOCKDECODER_BASE +{ +public: + typedef DXT_BLOCKDECODER_BASE base; + typedef DXT_INFO_5 INFO; + +protected: + unsigned m_alphas[8]; + unsigned m_alphaBits; + int m_offset; + +public: + void Setup(const BYTE *pBlock) + { + base::Setup(pBlock); + + const DXTAlphaBlock3BitLinear &block = m_pBlock->alpha; + m_alphas[0] = block.alpha[0]; + m_alphas[1] = block.alpha[1]; + if (m_alphas[0] > m_alphas[1]) { + // 8 alpha block + for (int i = 0; i < 6; i++) { + m_alphas[i + 2] = ((6 - i) * m_alphas[0] + (1 + i) * m_alphas[1] + 3) / 7; + } + } else { + // 6 alpha block + for (int i = 0; i < 4; i++) { + m_alphas[i + 2] = ((4 - i) * m_alphas[0] + (1 + i) * m_alphas[1] + 2) / 5; + } + m_alphas[6] = 0; + m_alphas[7] = 0xFF; + } + } + + void SetY(int y) + { + base::SetY(y); + int i = y / 2; + const DXTAlphaBlock3BitLinear &block = m_pBlock->alpha; + m_alphaBits = unsigned(block.data[0 + i * 3]) | (unsigned(block.data[1 + i * 3]) << 8) + | (unsigned(block.data[2 + i * 3]) << 16); + m_offset = (y & 1) * 12; + } + + void GetColor(int x, int y, Color8888 &color) + { + base::GetColor(x, y, color); + unsigned bits = (m_alphaBits >> (x * 3 + m_offset)) & 7; + color.a = (BYTE)m_alphas[bits]; + std::swap(color.r, color.b); + } +}; + +template +void DecodeDXTBlock(BYTE *dstData, const BYTE *srcBlock, long dstPitch, int bw, int bh) +{ + DECODER decoder; + decoder.Setup(srcBlock); + for (int y = 0; y < bh; y++) { + // Note that this assumes the pointer is pointing to the *last* valid start + // row. + BYTE *dst = dstData - y * dstPitch; + decoder.SetY(y); + for (int x = 0; x < bw; x++) { + decoder.GetColor(x, y, (Color8888 &)*dst); + dst += 4; + } + } +} + +struct STextureDataWriter +{ + QT3DSU32 m_Width; + QT3DSU32 m_Height; + QT3DSU32 m_Stride; + QT3DSU32 m_NumComponents; + STextureData &m_TextureData; + STextureDataWriter(QT3DSU32 w, QT3DSU32 h, bool hasA, STextureData &inTd, NVAllocatorCallback &alloc) + : m_Width(w) + , m_Height(h) + , m_Stride(hasA ? m_Width * 4 : m_Width * 3) + , m_NumComponents(hasA ? 4 : 3) + , m_TextureData(inTd) + { + QT3DSU32 dataSize = m_Stride * m_Height; + if (dataSize > m_TextureData.dataSizeInBytes) { + alloc.deallocate(m_TextureData.data); + m_TextureData.data = + alloc.allocate(dataSize, "SLoadedTexture::DecompressDXTImage", __FILE__, __LINE__); + m_TextureData.dataSizeInBytes = dataSize; + } + memZero(m_TextureData.data, m_TextureData.dataSizeInBytes); + m_TextureData.format = hasA ? NVRenderTextureFormats::RGBA8 : NVRenderTextureFormats::RGB8; + } + + void WritePixel(QT3DSU32 X, QT3DSU32 Y, QT3DSU8 *pixelData) + { + if (X < m_Width && Y < m_Height) { + char *textureData = reinterpret_cast(m_TextureData.data); + QT3DSU32 offset = Y * m_Stride + X * m_NumComponents; + + for (QT3DSU32 idx = 0; idx < m_NumComponents; ++idx) + QT3DS_ASSERT(textureData[offset + idx] == 0); + + memCopy(textureData + offset, pixelData, m_NumComponents); + } + } + + // Incoming pixels are assumed to be RGBA or RGBX, 32 bit in any case + void WriteBlock(QT3DSU32 X, QT3DSU32 Y, QT3DSU32 width, QT3DSU32 height, QT3DSU8 *pixelData) + { + QT3DSU32 offset = 0; + for (QT3DSU32 yidx = 0; yidx < height; ++yidx) { + for (QT3DSU32 xidx = 0; xidx < width; ++xidx, offset += 4) { + WritePixel(X + xidx, Y + (height - yidx - 1), pixelData + offset); + } + } + } + bool Finished() { return false; } +}; + +struct STextureAlphaScanner +{ + bool &m_Alpha; + + STextureAlphaScanner(bool &inAlpha) + : m_Alpha(inAlpha) + { + } + + void WriteBlock(QT3DSU32 X, QT3DSU32 Y, QT3DSU32 width, QT3DSU32 height, QT3DSU8 *pixelData) + { + Q_UNUSED(X) + Q_UNUSED(Y) + QT3DSU32 offset = 0; + for (QT3DSU32 yidx = 0; yidx < height; ++yidx) { + for (QT3DSU32 xidx = 0; xidx < width; ++xidx, offset += 4) { + if (pixelData[offset + 3] < 255) + m_Alpha = true; + } + } + } + + // If we detect alpha we can stop right there. + bool Finished() { return m_Alpha; } +}; +// Scan the dds image's mipmap 0 level for alpha. +template +static void DecompressDDS(void *inSrc, QT3DSU32 inDataSize, QT3DSU32 inWidth, QT3DSU32 inHeight, + TWriterType ioWriter) +{ + typedef typename DECODER::INFO INFO; + typedef typename INFO::Block Block; + (void)inDataSize; + + const QT3DSU8 *pbSrc = (const QT3DSU8 *)inSrc; + // Each DX block is composed of 16 pixels. Free image decodes those + // pixels into a 4x4 block of data. + QT3DSU8 pbDstData[4 * 4 * 4]; + // The decoder decodes backwards + // So we need to point to the last line. + QT3DSU8 *pbDst = pbDstData + 48; + + int width = (int)inWidth; + int height = (int)inHeight; + int lineStride = 16; + for (int y = 0; y < height && ioWriter.Finished() == false; y += 4) { + int yPixels = NVMin(height - y, 4); + for (int x = 0; x < width && ioWriter.Finished() == false; x += 4) { + int xPixels = NVMin(width - x, 4); + DecodeDXTBlock(pbDst, pbSrc, lineStride, xPixels, yPixels); + pbSrc += INFO::bytesPerBlock; + ioWriter.WriteBlock(x, y, xPixels, yPixels, pbDstData); + } + } +} + +bool ScanDDSForAlpha(Qt3DSDDSImage *dds) +{ + bool hasAlpha = false; + switch (dds->format) { + case qt3ds::render::NVRenderTextureFormats::RGBA_DXT1: + DecompressDDS(dds->data[0], dds->size[0], dds->mipwidth[0], + dds->mipheight[0], STextureAlphaScanner(hasAlpha)); + break; + case qt3ds::render::NVRenderTextureFormats::RGBA_DXT3: + DecompressDDS(dds->data[0], dds->size[0], dds->mipwidth[0], + dds->mipheight[0], STextureAlphaScanner(hasAlpha)); + break; + case qt3ds::render::NVRenderTextureFormats::RGBA_DXT5: + DecompressDDS(dds->data[0], dds->size[0], dds->mipwidth[0], + dds->mipheight[0], STextureAlphaScanner(hasAlpha)); + break; + default: + QT3DS_ASSERT(false); + break; + } + return hasAlpha; +} + +bool ScanImageForAlpha(const void *inData, QT3DSU32 inWidth, QT3DSU32 inHeight, QT3DSU32 inPixelSizeInBytes, + QT3DSU8 inAlphaSizeInBits) +{ + const QT3DSU8 *rowPtr = reinterpret_cast(inData); + bool hasAlpha = false; + if (inAlphaSizeInBits == 0) + return hasAlpha; + if (inPixelSizeInBytes != 2 && inPixelSizeInBytes != 4) { + QT3DS_ASSERT(false); + return false; + } + if (inAlphaSizeInBits > 8) { + QT3DS_ASSERT(false); + return false; + } + + QT3DSU32 alphaRightShift = inPixelSizeInBytes * 8 - inAlphaSizeInBits; + QT3DSU32 maxAlphaValue = (1 << inAlphaSizeInBits) - 1; + + for (QT3DSU32 rowIdx = 0; rowIdx < inHeight && hasAlpha == false; ++rowIdx) { + for (QT3DSU32 idx = 0; idx < inWidth && hasAlpha == false; + ++idx, rowPtr += inPixelSizeInBytes) { + QT3DSU32 pixelValue = 0; + if (inPixelSizeInBytes == 2) + pixelValue = *(reinterpret_cast(rowPtr)); + else + pixelValue = *(reinterpret_cast(rowPtr)); + pixelValue = pixelValue >> alphaRightShift; + if (pixelValue < maxAlphaValue) + hasAlpha = true; + } + } + return hasAlpha; +} +} + +SLoadedTexture::~SLoadedTexture() +{ + if (dds) { + if (dds->dataBlock) + QT3DS_FREE(m_Allocator, dds->dataBlock); + + QT3DS_FREE(m_Allocator, dds); + } else if (data && image.byteCount() <= 0) { + m_Allocator.deallocate(data); + } + if (m_Palette) + m_Allocator.deallocate(m_Palette); + if (m_TransparencyTable) + m_Allocator.deallocate(m_TransparencyTable); +} + +void SLoadedTexture::release() +{ + NVAllocatorCallback *theAllocator(&m_Allocator); + this->~SLoadedTexture(); + theAllocator->deallocate(this); +} + +bool SLoadedTexture::ScanForTransparency() +{ + switch (format) { + case NVRenderTextureFormats::SRGB8A8: + case NVRenderTextureFormats::RGBA8: + if (!data) { // dds + return true; + } else { + return ScanImageForAlpha(data, width, height, 4, 8); + } + break; + // Scan the image. + case NVRenderTextureFormats::SRGB8: + case NVRenderTextureFormats::RGB8: + return false; + break; + case NVRenderTextureFormats::RGB565: + return false; + break; + case NVRenderTextureFormats::RGBA5551: + if (!data) { // dds + return true; + } else { + return ScanImageForAlpha(data, width, height, 2, 1); + } + break; + case NVRenderTextureFormats::Alpha8: + return true; + break; + case NVRenderTextureFormats::Luminance8: + return false; + break; + case NVRenderTextureFormats::LuminanceAlpha8: + if (!data) { // dds + return true; + } else { + return ScanImageForAlpha(data, width, height, 2, 8); + } + break; + case NVRenderTextureFormats::RGB_DXT1: + return false; + break; + case NVRenderTextureFormats::RGBA_DXT3: + case NVRenderTextureFormats::RGBA_DXT1: + case NVRenderTextureFormats::RGBA_DXT5: + if (dds) { + return ScanDDSForAlpha(dds); + } else { + QT3DS_ASSERT(false); + return false; + } + break; + case NVRenderTextureFormats::RGB9E5: + return false; + break; + case NVRenderTextureFormats::RG32F: + case NVRenderTextureFormats::RGB32F: + case NVRenderTextureFormats::RGBA16F: + case NVRenderTextureFormats::RGBA32F: + // PKC TODO : For now, since IBL will be the main consumer, we'll just pretend there's no + // alpha. + // Need to do a proper scan down the line, but doing it for floats is a little different + // from + // integer scans. + return false; + break; + default: + break; + } + QT3DS_ASSERT(false); + return false; +} + +void SLoadedTexture::EnsureMultiplerOfFour(NVFoundationBase &inFoundation, const char *inPath) +{ + if (width % 4 || height % 4) { + qCWarning(PERF_WARNING, + "Image %s has non multiple of four width or height; perf hit for scaling", inPath); + if (data) { + QT3DSU32 newWidth = ITextRenderer::NextMultipleOf4(width); + QT3DSU32 newHeight = ITextRenderer::NextMultipleOf4(height); + QT3DSU32 newDataSize = newWidth * newHeight * components; + NVAllocatorCallback &theAllocator(inFoundation.getAllocator()); + QT3DSU8 *newData = (QT3DSU8 *)(theAllocator.allocate(newDataSize, "Scaled Image Data", + __FILE__, __LINE__)); + CImageScaler theScaler(theAllocator); + if (components == 4) { + theScaler.FastExpandRowsAndColumns((unsigned char *)data, width, height, newData, + newWidth, newHeight); + } else + theScaler.ExpandRowsAndColumns((unsigned char *)data, width, height, newData, + newWidth, newHeight, components); + + theAllocator.deallocate(data); + data = newData; + width = newWidth; + height = newHeight; + dataSizeInBytes = newDataSize; + } + } +} + +STextureData SLoadedTexture::DecompressDXTImage(int inMipMapIdx, STextureData *inOptLastImage) +{ + STextureData retval; + if (inOptLastImage) + retval = *inOptLastImage; + + if (dds == NULL || inMipMapIdx >= dds->numMipmaps) { + QT3DS_ASSERT(false); + ReleaseDecompressedTexture(retval); + return STextureData(); + } + char *srcData = (char *)dds->data[inMipMapIdx]; + int srcDataSize = dds->size[inMipMapIdx]; + QT3DSU32 imgWidth = (QT3DSU32)dds->mipwidth[inMipMapIdx]; + QT3DSU32 imgHeight = (QT3DSU32)dds->mipheight[inMipMapIdx]; + + switch (format) { + case NVRenderTextureFormats::RGB_DXT1: + DecompressDDS( + srcData, srcDataSize, imgWidth, imgHeight, + STextureDataWriter(imgWidth, imgHeight, false, retval, m_Allocator)); + break; + case NVRenderTextureFormats::RGBA_DXT1: + DecompressDDS( + srcData, srcDataSize, imgWidth, imgHeight, + STextureDataWriter(imgWidth, imgHeight, true, retval, m_Allocator)); + break; + case NVRenderTextureFormats::RGBA_DXT3: + DecompressDDS( + srcData, srcDataSize, imgWidth, imgHeight, + STextureDataWriter(imgWidth, imgHeight, true, retval, m_Allocator)); + break; + case NVRenderTextureFormats::RGBA_DXT5: + DecompressDDS( + srcData, srcDataSize, imgWidth, imgHeight, + STextureDataWriter(imgWidth, imgHeight, true, retval, m_Allocator)); + break; + default: + QT3DS_ASSERT(false); + break; + } + return retval; +} + +void SLoadedTexture::ReleaseDecompressedTexture(STextureData inImage) +{ + if (inImage.data) + m_Allocator.deallocate(inImage.data); +} + +#ifndef EA_PLATFORM_WINDOWS +#define stricmp strcasecmp +#endif + +SLoadedTexture *SLoadedTexture::Load(const QString &inPath, NVFoundationBase &inFoundation, + IInputStreamFactory &inFactory, bool inFlipY, + NVRenderContextType renderContextType, bool preferKTX) +{ + if (inPath.isEmpty()) + return nullptr; + + // Check KTX path first + QString path = inPath; + QString ktxSource = inPath; + if (preferKTX) { + ktxSource = ktxSource.left(ktxSource.lastIndexOf(QLatin1Char('.'))); + ktxSource.append(QLatin1String(".ktx")); + } + + SLoadedTexture *theLoadedImage = nullptr; + // We will get invalid error logs of files not found if we don't force quiet mode + // If the file is actually missing, it will be logged later (loaded image is null) + NVScopedRefCounted theStream( + inFactory.GetStreamForFile(preferKTX ? ktxSource : inPath, true)); + if (!theStream.mPtr) { + if (preferKTX) + theStream = inFactory.GetStreamForFile(inPath, true); + else + return nullptr; + } else { + path = ktxSource; + } + QString fileName; + inFactory.GetPathForFile(path, fileName, true); + if (theStream.mPtr && path.size() > 3) { + if (path.endsWith(QLatin1String("png"), Qt::CaseInsensitive) + || path.endsWith(QLatin1String("jpg"), Qt::CaseInsensitive) + || path.endsWith(QLatin1String("peg"), Qt::CaseInsensitive)) { + theLoadedImage = LoadQImage(fileName, inFlipY, inFoundation, renderContextType); + } else if (path.endsWith(QLatin1String("dds"), Qt::CaseInsensitive)) { + theLoadedImage = LoadDDS(*theStream, inFlipY, inFoundation, renderContextType); + } else if (path.endsWith(QLatin1String("gif"), Qt::CaseInsensitive)) { + theLoadedImage = LoadGIF(*theStream, !inFlipY, inFoundation, renderContextType); + } else if (path.endsWith(QLatin1String("bmp"), Qt::CaseInsensitive)) { + theLoadedImage = LoadBMP(*theStream, !inFlipY, inFoundation, renderContextType); + } else if (path.endsWith(QLatin1String("hdr"), Qt::CaseInsensitive)) { + theLoadedImage = LoadHDR(*theStream, inFoundation, renderContextType); + } else if (path.endsWith(QLatin1String("ktx"), Qt::CaseInsensitive)) { + theLoadedImage = LoadKTX(*theStream, inFlipY, inFoundation, renderContextType); + } else { + qCWarning(INTERNAL_ERROR, "Unrecognized image extension: %s", qPrintable(inPath)); + } + } + + return theLoadedImage; +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.h b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.h new file mode 100644 index 0000000..f019400 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTexture.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_LOADED_TEXTURE_H +#define QT3DS_RENDER_LOADED_TEXTURE_H +#include "Qt3DSRender.h" +#include "foundation/Qt3DSSimpleTypes.h" +#include "render/Qt3DSRenderBaseTypes.h" +#include "Qt3DSRenderLoadedTextureDDS.h" +#include "foundation/Qt3DSRefCounted.h" +#include + +namespace qt3ds { +namespace foundation { + class ISeekableIOStream; + class IInStream; +} +} + +namespace qt3ds { +namespace render { + + class IInputStreamFactory; + + struct STextureData + { + void *data; + QT3DSU32 dataSizeInBytes; + qt3ds::render::NVRenderTextureFormats::Enum format; + STextureData() + : data(NULL) + , dataSizeInBytes(0) + , format(qt3ds::render::NVRenderTextureFormats::Unknown) + { + } + }; + struct ExtendedTextureFormats + { + enum Enum { + NoExtendedFormat = 0, + Palettized, + CustomRGB, + }; + }; + // Utility class used for loading image data from disk. + // Supports jpg, png, and dds. + struct SLoadedTexture : public NVReleasable + { + private: + ~SLoadedTexture(); + + public: + NVAllocatorCallback &m_Allocator; + QT3DSI32 width; + QT3DSI32 height; + QT3DSI32 components; + void *data; + QImage image; + QT3DSU32 dataSizeInBytes; + qt3ds::render::NVRenderTextureFormats::Enum format; + Qt3DSDDSImage *dds; + ExtendedTextureFormats::Enum m_ExtendedFormat; + // Used for palettized images. + void *m_Palette; + QT3DSI32 m_CustomMasks[3]; + int m_BitCount; + char8_t m_BackgroundColor[3]; + uint8_t *m_TransparencyTable; + int32_t m_TransparentPaletteIndex; + + SLoadedTexture(NVAllocatorCallback &inAllocator) + : m_Allocator(inAllocator) + , width(0) + , height(0) + , components(0) + , data(NULL) + , image(0) + , dataSizeInBytes(0) + , format(qt3ds::render::NVRenderTextureFormats::RGBA8) + , dds(NULL) + , m_ExtendedFormat(ExtendedTextureFormats::NoExtendedFormat) + , m_Palette(NULL) + , m_BitCount(0) + , m_TransparencyTable(NULL) + , m_TransparentPaletteIndex(-1) + { + m_CustomMasks[0] = 0; + m_CustomMasks[1] = 0; + m_CustomMasks[2] = 0; + m_BackgroundColor[0] = 0; + m_BackgroundColor[1] = 0; + m_BackgroundColor[2] = 0; + } + void setFormatFromComponents() + { + switch (components) { + case 1: // undefined, but in this context probably luminance + format = qt3ds::render::NVRenderTextureFormats::Luminance8; + break; + case 2: + format = qt3ds::render::NVRenderTextureFormats::LuminanceAlpha8; + break; + case 3: + format = qt3ds::render::NVRenderTextureFormats::RGB8; + break; + + default: + // fallthrough intentional + case 4: + format = qt3ds::render::NVRenderTextureFormats::RGBA8; + break; + } + } + + void EnsureMultiplerOfFour(NVFoundationBase &inFoundation, const char *inPath); + // Returns true if this image has a pixel less than 255. + bool ScanForTransparency(); + + // Be sure to call this or risk leaking an enormous amount of memory + void release() override; + + // Not all video cards support dxt compression. Giving the last image allows + // this object to potentially reuse the memory + STextureData DecompressDXTImage(int inMipMapIdx, STextureData *inOptLastImage = NULL); + void ReleaseDecompressedTexture(STextureData inImage); + + static SLoadedTexture *Load(const QString &inPath, NVFoundationBase &inAllocator, + IInputStreamFactory &inFactory, bool inFlipY = true, + NVRenderContextType renderContextType + = NVRenderContextValues::NullContext, bool preferKTX = false); + static SLoadedTexture *LoadDDS(IInStream &inStream, QT3DSI32 flipVertical, + NVFoundationBase &fnd, + NVRenderContextType renderContextType); + static SLoadedTexture *LoadKTX(IInStream &inStream, QT3DSI32 flipVertical, + NVFoundationBase &fnd, + NVRenderContextType renderContextType); + static SLoadedTexture *LoadBMP(ISeekableIOStream &inStream, bool inFlipY, + NVFoundationBase &inFnd, + NVRenderContextType renderContextType); + static SLoadedTexture *LoadGIF(ISeekableIOStream &inStream, bool inFlipY, + NVFoundationBase &inFnd, + NVRenderContextType renderContextType); + static SLoadedTexture *LoadHDR(ISeekableIOStream &inStream, NVFoundationBase &inFnd, + NVRenderContextType renderContextType); + + static SLoadedTexture *LoadQImage(const QString &inPath, QT3DSI32 flipVertical, + NVFoundationBase &fnd, + NVRenderContextType renderContextType); + + private: + // Implemented in the bmp loader. + void FreeImagePostProcess(bool inFlipY); + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureBMP.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureBMP.cpp new file mode 100644 index 0000000..29e75a8 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureBMP.cpp @@ -0,0 +1,1262 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +// ========================================================== +// BMP Loader and Writer +// +// Design and implementation by +// - Floris van den Berg (flvdberg@wxs.nl) +// - Markus Loibl (markus.loibl@epost.de) +// - Martin Weber (martweb@gmx.net) +// - Herve Drolon (drolon@infonie.fr) +// - Michal Novotny (michal@etc.cz) +// +// This file is part of FreeImage 3 +// +// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY +// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES +// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE +// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED +// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT +// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY +// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL +// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER +// THIS DISCLAIMER. +// +// Use at your own risk! +// ========================================================== + +#include "Qt3DSRenderLoadedTextureFreeImageCompat.h" + +// ---------------------------------------------------------- +// Constants + headers +// ---------------------------------------------------------- + +static const BYTE RLE_COMMAND = 0; +static const BYTE RLE_ENDOFLINE = 0; +static const BYTE RLE_ENDOFBITMAP = 1; +static const BYTE RLE_DELTA = 2; + +static const BYTE BI_RGB = 0; +static const BYTE BI_RLE8 = 1; +static const BYTE BI_RLE4 = 2; +static const BYTE BI_BITFIELDS = 3; + +// ---------------------------------------------------------- + +#ifdef _WIN32 +#pragma pack(push, 1) +#else +#pragma pack(1) +#endif + +typedef struct tagBITMAPCOREHEADER +{ + DWORD bcSize; + WORD bcWidth; + WORD bcHeight; + WORD bcPlanes; + WORD bcBitCnt; +} BITMAPCOREHEADER, *PBITMAPCOREHEADER; + +typedef struct tagBITMAPINFOOS2_1X_HEADER +{ + DWORD biSize; + WORD biWidth; + WORD biHeight; + WORD biPlanes; + WORD biBitCount; +} BITMAPINFOOS2_1X_HEADER, *PBITMAPINFOOS2_1X_HEADER; + +typedef struct tagBITMAPFILEHEADER +{ + WORD bfType; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; +} BITMAPFILEHEADER, *PBITMAPFILEHEADER; + + +#ifdef _WIN32 +#pragma pack(pop) +#else +#pragma pack() +#endif + +// ========================================================== +// Plugin Interface +// ========================================================== + +static int s_format_id; + +// ========================================================== +// Internal functions +// ========================================================== + +#ifdef FREEIMAGE_BIGENDIAN +static void SwapInfoHeader(BITMAPINFOHEADER *header) +{ + SwapLong(&header->biSize); + SwapLong((DWORD *)&header->biWidth); + SwapLong((DWORD *)&header->biHeight); + SwapShort(&header->biPlanes); + SwapShort(&header->biBitCount); + SwapLong(&header->biCompression); + SwapLong(&header->biSizeImage); + SwapLong((DWORD *)&header->biXPelsPerMeter); + SwapLong((DWORD *)&header->biYPelsPerMeter); + SwapLong(&header->biClrUsed); + SwapLong(&header->biClrImportant); +} + +static void SwapCoreHeader(BITMAPCOREHEADER *header) +{ + SwapLong(&header->bcSize); + SwapShort(&header->bcWidth); + SwapShort(&header->bcHeight); + SwapShort(&header->bcPlanes); + SwapShort(&header->bcBitCnt); +} + +static void SwapOS21XHeader(BITMAPINFOOS2_1X_HEADER *header) +{ + SwapLong(&header->biSize); + SwapShort(&header->biWidth); + SwapShort(&header->biHeight); + SwapShort(&header->biPlanes); + SwapShort(&header->biBitCount); +} + +static void SwapFileHeader(BITMAPFILEHEADER *header) +{ + SwapShort(&header->bfType); + SwapLong(&header->bfSize); + SwapShort(&header->bfReserved1); + SwapShort(&header->bfReserved2); + SwapLong(&header->bfOffBits); +} +#endif + +// -------------------------------------------------------------------------- + +/** +Load uncompressed image pixels for 1-, 4-, 8-, 16-, 24- and 32-bit dib +@param io FreeImage IO +@param handle FreeImage IO handle +@param dib Image to be loaded +@param height Image height +@param pitch Image pitch +@param bit_count Image bit-depth (1-, 4-, 8-, 16-, 24- or 32-bit) +*/ +static void LoadPixelData(FreeImageIO *io, fi_handle handle, FIBITMAP *dib, int height, int pitch, + int bit_count) +{ + (void)bit_count; + // Load pixel data + // NB: height can be < 0 for BMP data + if (height > 0) { + io->read_proc((void *)FreeImage_GetBits(dib), height * pitch, 1, handle); + } else { + int positiveHeight = abs(height); + for (int c = 0; c < positiveHeight; ++c) { + io->read_proc((void *)FreeImage_GetScanLine(dib, positiveHeight - c - 1), pitch, 1, + handle); + } + } + +// swap as needed +#ifdef FREEIMAGE_BIGENDIAN + if (bit_count == 16) { + for (unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { + WORD *pixel = (WORD *)FreeImage_GetScanLine(dib, y); + for (unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { + SwapShort(pixel); + pixel++; + } + } + } +#endif + +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB + if (bit_count == 24 || bit_count == 32) { + for (unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { + BYTE *pixel = FreeImage_GetScanLine(dib, y); + for (unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { + INPLACESWAP(pixel[0], pixel[2]); + pixel += (bit_count >> 3); + } + } + } +#endif +} + +/** +Load image pixels for 4-bit RLE compressed dib +@param io FreeImage IO +@param handle FreeImage IO handle +@param width Image width +@param height Image height +@param dib Image to be loaded +@return Returns TRUE if successful, returns FALSE otherwise +*/ +static BOOL LoadPixelDataRLE4(FreeImageIO *io, fi_handle handle, int width, int height, + FIBITMAP *dib) +{ + int status_byte = 0; + BYTE second_byte = 0; + int bits = 0; + + BYTE *pixels = NULL; // temporary 8-bit buffer + + try { + height = abs(height); + + pixels = (BYTE *)malloc(width * height * sizeof(BYTE)); + if (!pixels) + throw(1); + memset(pixels, 0, width * height * sizeof(BYTE)); + + BYTE *q = pixels; + BYTE *end = pixels + height * width; + + for (int scanline = 0; scanline < height;) { + if (q < pixels || q >= end) { + break; + } + if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + if (status_byte != 0) { + status_byte = (int)MIN((size_t)status_byte, (size_t)(end - q)); + // Encoded mode + if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + for (int i = 0; i < status_byte; i++) { + *q++ = (BYTE)((i & 0x01) ? (second_byte & 0x0f) : ((second_byte >> 4) & 0x0f)); + } + bits += status_byte; + } else { + // Escape mode + if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + switch (status_byte) { + case RLE_ENDOFLINE: { + // End of line + bits = 0; + scanline++; + q = pixels + scanline * width; + } break; + + case RLE_ENDOFBITMAP: + // End of bitmap + q = end; + break; + + case RLE_DELTA: { + // read the delta values + + BYTE delta_x = 0; + BYTE delta_y = 0; + + if (io->read_proc(&delta_x, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + if (io->read_proc(&delta_y, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + + // apply them + + bits += delta_x; + scanline += delta_y; + q = pixels + scanline * width + bits; + } break; + + default: { + // Absolute mode + status_byte = (int)MIN((size_t)status_byte, (size_t)(end - q)); + for (int i = 0; i < status_byte; i++) { + if ((i & 0x01) == 0) { + if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + } + *q++ = + (BYTE)((i & 0x01) ? (second_byte & 0x0f) : ((second_byte >> 4) & 0x0f)); + } + bits += status_byte; + // Read pad byte + if (((status_byte & 0x03) == 1) || ((status_byte & 0x03) == 2)) { + BYTE padding = 0; + if (io->read_proc(&padding, sizeof(BYTE), 1, handle) != 1) { + throw(1); + } + } + } break; + } + } + } + + { + // Convert to 4-bit + for (int y = 0; y < height; y++) { + const BYTE *src = (BYTE *)pixels + y * width; + BYTE *dst = FreeImage_GetScanLine(dib, y); + + BOOL hinibble = TRUE; + + for (int cols = 0; cols < width; cols++) { + if (hinibble) { + dst[cols >> 1] = (src[cols] << 4); + } else { + dst[cols >> 1] |= src[cols]; + } + + hinibble = !hinibble; + } + } + } + + free(pixels); + + return TRUE; + + } catch (int) { + if (pixels) + free(pixels); + return FALSE; + } +} + +/** +Load image pixels for 8-bit RLE compressed dib +@param io FreeImage IO +@param handle FreeImage IO handle +@param width Image width +@param height Image height +@param dib Image to be loaded +@return Returns TRUE if successful, returns FALSE otherwise +*/ +static BOOL LoadPixelDataRLE8(FreeImageIO *io, fi_handle handle, int width, int height, + FIBITMAP *dib) +{ + BYTE status_byte = 0; + BYTE second_byte = 0; + int scanline = 0; + int bits = 0; + + for (;;) { + if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { + return FALSE; + } + + switch (status_byte) { + case RLE_COMMAND: + if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { + return FALSE; + } + + switch (status_byte) { + case RLE_ENDOFLINE: + bits = 0; + scanline++; + break; + + case RLE_ENDOFBITMAP: + return TRUE; + + case RLE_DELTA: { + // read the delta values + + BYTE delta_x = 0; + BYTE delta_y = 0; + + if (io->read_proc(&delta_x, sizeof(BYTE), 1, handle) != 1) { + return FALSE; + } + if (io->read_proc(&delta_y, sizeof(BYTE), 1, handle) != 1) { + return FALSE; + } + + // apply them + + bits += delta_x; + scanline += delta_y; + + break; + } + + default: { + if (scanline >= abs(height)) { + return TRUE; + } + + int count = MIN((int)status_byte, width - bits); + + BYTE *sline = FreeImage_GetScanLine(dib, scanline); + + if (io->read_proc((void *)(sline + bits), sizeof(BYTE) * count, 1, handle) != 1) { + return FALSE; + } + + // align run length to even number of bytes + + if ((status_byte & 1) == 1) { + if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { + return FALSE; + } + } + + bits += status_byte; + + break; + } + } + + break; + + default: { + if (scanline >= abs(height)) { + return TRUE; + } + + int count = MIN((int)status_byte, width - bits); + + BYTE *sline = FreeImage_GetScanLine(dib, scanline); + + if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { + return FALSE; + } + + for (int i = 0; i < count; i++) { + *(sline + bits) = second_byte; + + bits++; + } + + break; + } + } + } +} + +// -------------------------------------------------------------------------- + +static FIBITMAP *LoadWindowsBMP(FreeImageIO *io, fi_handle handle, int flags, + unsigned bitmap_bits_offset) +{ + FIBITMAP *dib = NULL; + (void)flags; + try { + // load the info header + + BITMAPINFOHEADER bih; + + io->read_proc(&bih, sizeof(BITMAPINFOHEADER), 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapInfoHeader(&bih); +#endif + + // keep some general information about the bitmap + + int used_colors = bih.biClrUsed; + int width = bih.biWidth; + int height = bih.biHeight; // WARNING: height can be < 0 => check each call using 'height' + // as a parameter + int alloc_height = abs(height); + int bit_count = bih.biBitCount; + int compression = bih.biCompression; + int pitch = CalculatePitch(CalculateLine(width, bit_count)); + + switch (bit_count) { + case 1: + case 4: + case 8: { + if ((used_colors <= 0) || (used_colors > CalculateUsedPaletteEntries(bit_count))) + used_colors = CalculateUsedPaletteEntries(bit_count); + + // allocate enough memory to hold the bitmap (header, palette, pixels) and read the + // palette + + dib = FreeImage_Allocate(width, alloc_height, bit_count, io); + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information + FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); + FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); + + // load the palette + + io->read_proc(FreeImage_GetPalette(dib), used_colors * sizeof(RGBQUAD), 1, handle); +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB + RGBQUAD *pal = FreeImage_GetPalette(dib); + for (int i = 0; i < used_colors; i++) { + INPLACESWAP(pal[i].rgbRed, pal[i].rgbBlue); + } +#endif + + // seek to the actual pixel data. + // this is needed because sometimes the palette is larger than the entries it contains + // predicts + + if (bitmap_bits_offset > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + + (used_colors * sizeof(RGBQUAD)))) + io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); + + // read the pixel data + + switch (compression) { + case BI_RGB: + LoadPixelData(io, handle, dib, height, pitch, bit_count); + return dib; + + case BI_RLE4: + if (LoadPixelDataRLE4(io, handle, width, height, dib)) { + return dib; + } else { + throw "Error encountered while decoding RLE4 BMP data"; + } + break; + + case BI_RLE8: + if (LoadPixelDataRLE8(io, handle, width, height, dib)) { + return dib; + } else { + throw "Error encountered while decoding RLE8 BMP data"; + } + break; + + default: + throw "compression type not supported"; + } + } break; // 1-, 4-, 8-bit + + case 16: { + if (bih.biCompression == BI_BITFIELDS) { + DWORD bitfields[3]; + + io->read_proc(bitfields, 3 * sizeof(DWORD), 1, handle); + + dib = FreeImage_Allocate(width, alloc_height, bit_count, bitfields[0], bitfields[1], + bitfields[2], io); + } else { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI16_555_RED_MASK, + FI16_555_GREEN_MASK, FI16_555_BLUE_MASK, io); + } + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information + FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); + FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); + + // load pixel data and swap as needed if OS is Big Endian + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + return dib; + } break; // 16-bit + + case 24: + case 32: { + if (bih.biCompression == BI_BITFIELDS) { + DWORD bitfields[3]; + + io->read_proc(bitfields, 3 * sizeof(DWORD), 1, handle); + + dib = FreeImage_Allocate(width, alloc_height, bit_count, bitfields[0], bitfields[1], + bitfields[2], io); + } else { + if (bit_count == 32) { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); + } else { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); + } + } + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information + FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); + FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); + + // Skip over the optional palette + // A 24 or 32 bit DIB may contain a palette for faster color reduction + + if (used_colors > 0) { + io->seek_proc(handle, used_colors * sizeof(RGBQUAD), SEEK_CUR); + } else if ((bih.biCompression != BI_BITFIELDS) + && (bitmap_bits_offset + > sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))) { + io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); + } + + // read in the bitmap bits + // load pixel data and swap as needed if OS is Big Endian + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + // check if the bitmap contains transparency, if so enable it in the header + + return dib; + } break; // 24-, 32-bit + } + } catch (const char *message) { + if (dib) { + FreeImage_Unload(dib); + } + if (message) { + FreeImage_OutputMessageProc(s_format_id, message, io); + } + } + + return NULL; +} + +// -------------------------------------------------------------------------- + +static FIBITMAP *LoadOS22XBMP(FreeImageIO *io, fi_handle handle, int flags, + unsigned bitmap_bits_offset) +{ + FIBITMAP *dib = NULL; + (void)flags; + try { + // load the info header + + BITMAPINFOHEADER bih; + + io->read_proc(&bih, sizeof(BITMAPINFOHEADER), 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapInfoHeader(&bih); +#endif + + // keep some general information about the bitmap + + int used_colors = bih.biClrUsed; + int width = bih.biWidth; + int height = bih.biHeight; // WARNING: height can be < 0 => check each read_proc using + // 'height' as a parameter + int alloc_height = abs(height); + int bit_count = bih.biBitCount; + int compression = bih.biCompression; + int pitch = CalculatePitch(CalculateLine(width, bit_count)); + + switch (bit_count) { + case 1: + case 4: + case 8: { + if ((used_colors <= 0) || (used_colors > CalculateUsedPaletteEntries(bit_count))) + used_colors = CalculateUsedPaletteEntries(bit_count); + + // allocate enough memory to hold the bitmap (header, palette, pixels) and read the + // palette + + dib = FreeImage_Allocate(width, alloc_height, bit_count, io); + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information + FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); + FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); + + // load the palette + + io->seek_proc(handle, sizeof(BITMAPFILEHEADER) + bih.biSize, SEEK_SET); + + RGBQUAD *pal = FreeImage_GetPalette(dib); + + for (int count = 0; count < used_colors; count++) { + FILE_BGR bgr; + + io->read_proc(&bgr, sizeof(FILE_BGR), 1, handle); + + pal[count].rgbRed = bgr.r; + pal[count].rgbGreen = bgr.g; + pal[count].rgbBlue = bgr.b; + } + + // seek to the actual pixel data. + // this is needed because sometimes the palette is larger than the entries it contains + // predicts + + if (bitmap_bits_offset + > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * 3))) + io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); + + // read the pixel data + + switch (compression) { + case BI_RGB: + // load pixel data + LoadPixelData(io, handle, dib, height, pitch, bit_count); + return dib; + + case BI_RLE4: + if (LoadPixelDataRLE4(io, handle, width, height, dib)) { + return dib; + } else { + throw "Error encountered while decoding RLE4 BMP data"; + } + break; + + case BI_RLE8: + if (LoadPixelDataRLE8(io, handle, width, height, dib)) { + return dib; + } else { + throw "Error encountered while decoding RLE8 BMP data"; + } + break; + + default: + throw "compression type not supported"; + } + } + + case 16: { + if (bih.biCompression == 3) { + DWORD bitfields[3]; + + io->read_proc(bitfields, 3 * sizeof(DWORD), 1, handle); + + dib = FreeImage_Allocate(width, alloc_height, bit_count, bitfields[0], bitfields[1], + bitfields[2], io); + } else { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI16_555_RED_MASK, + FI16_555_GREEN_MASK, FI16_555_BLUE_MASK, io); + } + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information + FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); + FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); + + if (bitmap_bits_offset + > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * 3))) { + io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); + } + + // load pixel data and swap as needed if OS is Big Endian + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + return dib; + } + + case 24: + case 32: { + if (bit_count == 32) { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); + } else { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); + } + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information + FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); + FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); + + // Skip over the optional palette + // A 24 or 32 bit DIB may contain a palette for faster color reduction + + if (bitmap_bits_offset + > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * 3))) + io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); + + // read in the bitmap bits + // load pixel data and swap as needed if OS is Big Endian + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + return dib; + } + } + } catch (const char *message) { + if (dib) + FreeImage_Unload(dib); + + FreeImage_OutputMessageProc(s_format_id, message, io); + } + + return NULL; +} + +// -------------------------------------------------------------------------- + +static FIBITMAP *LoadOS21XBMP(FreeImageIO *io, fi_handle handle, int flags, + unsigned bitmap_bits_offset) +{ + FIBITMAP *dib = NULL; + (void)flags; + try { + BITMAPINFOOS2_1X_HEADER bios2_1x; + + io->read_proc(&bios2_1x, sizeof(BITMAPINFOOS2_1X_HEADER), 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapOS21XHeader(&bios2_1x); +#endif + // keep some general information about the bitmap + + int used_colors = 0; + int width = bios2_1x.biWidth; + int height = bios2_1x.biHeight; // WARNING: height can be < 0 => check each read_proc using + // 'height' as a parameter + int alloc_height = abs(height); + int bit_count = bios2_1x.biBitCount; + int pitch = CalculatePitch(CalculateLine(width, bit_count)); + + switch (bit_count) { + case 1: + case 4: + case 8: { + used_colors = CalculateUsedPaletteEntries(bit_count); + + // allocate enough memory to hold the bitmap (header, palette, pixels) and read the + // palette + + dib = FreeImage_Allocate(width, alloc_height, bit_count, io); + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information to default values (72 dpi in english units) + FreeImage_SetDotsPerMeterX(dib, 2835); + FreeImage_SetDotsPerMeterY(dib, 2835); + + // load the palette + + RGBQUAD *pal = FreeImage_GetPalette(dib); + + for (int count = 0; count < used_colors; count++) { + FILE_BGR bgr; + + io->read_proc(&bgr, sizeof(FILE_BGR), 1, handle); + + pal[count].rgbRed = bgr.r; + pal[count].rgbGreen = bgr.g; + pal[count].rgbBlue = bgr.b; + } + + // Skip over the optional palette + // A 24 or 32 bit DIB may contain a palette for faster color reduction + + io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); + + // read the pixel data + + // load pixel data + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + return dib; + } + + case 16: { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI16_555_RED_MASK, + FI16_555_GREEN_MASK, FI16_555_BLUE_MASK, io); + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information to default values (72 dpi in english units) + FreeImage_SetDotsPerMeterX(dib, 2835); + FreeImage_SetDotsPerMeterY(dib, 2835); + + // load pixel data and swap as needed if OS is Big Endian + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + return dib; + } + + case 24: + case 32: { + if (bit_count == 32) { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); + } else { + dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, + FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); + } + + if (dib == NULL) + throw "DIB allocation failed"; + + // set resolution information to default values (72 dpi in english units) + FreeImage_SetDotsPerMeterX(dib, 2835); + FreeImage_SetDotsPerMeterY(dib, 2835); + + // Skip over the optional palette + // A 24 or 32 bit DIB may contain a palette for faster color reduction + + // load pixel data and swap as needed if OS is Big Endian + LoadPixelData(io, handle, dib, height, pitch, bit_count); + + // check if the bitmap contains transparency, if so enable it in the header + + return dib; + } + } + } catch (const char *message) { + if (dib) + FreeImage_Unload(dib); + + FreeImage_OutputMessageProc(s_format_id, message, io); + } + + return NULL; +} + +// ========================================================== +// Plugin Implementation +// ========================================================== + +// ---------------------------------------------------------- + +static FIBITMAP *DoLoadBMP(FreeImageIO *io, fi_handle handle, int flags) +{ + if (handle != NULL) { + BITMAPFILEHEADER bitmapfileheader; + DWORD type = 0; + BYTE magic[2]; + + // we use this offset value to make seemingly absolute seeks relative in the file + + long offset_in_file = io->tell_proc(handle); + + // read the magic + + io->read_proc(&magic, sizeof(magic), 1, handle); + + // compare the magic with the number we know + + // somebody put a comment here explaining the purpose of this loop + while (memcmp(&magic, "BA", 2) == 0) { + io->read_proc(&bitmapfileheader.bfSize, sizeof(DWORD), 1, handle); + io->read_proc(&bitmapfileheader.bfReserved1, sizeof(WORD), 1, handle); + io->read_proc(&bitmapfileheader.bfReserved2, sizeof(WORD), 1, handle); + io->read_proc(&bitmapfileheader.bfOffBits, sizeof(DWORD), 1, handle); + io->read_proc(&magic, sizeof(magic), 1, handle); + } + + // read the fileheader + + io->seek_proc(handle, (0 - (int)sizeof(magic)), SEEK_CUR); + io->read_proc(&bitmapfileheader, (int)sizeof(BITMAPFILEHEADER), 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapFileHeader(&bitmapfileheader); +#endif + + // read the first byte of the infoheader + + io->read_proc(&type, sizeof(DWORD), 1, handle); + io->seek_proc(handle, 0 - (int)sizeof(DWORD), SEEK_CUR); +#ifdef FREEIMAGE_BIGENDIAN + SwapLong(&type); +#endif + + // call the appropriate load function for the found bitmap type + + if (type == 40) + return LoadWindowsBMP(io, handle, flags, offset_in_file + bitmapfileheader.bfOffBits); + + if (type == 12) + return LoadOS21XBMP(io, handle, flags, offset_in_file + bitmapfileheader.bfOffBits); + + if (type <= 64) + return LoadOS22XBMP(io, handle, flags, offset_in_file + bitmapfileheader.bfOffBits); + + char buf[256]; + sprintf(buf, "unknown bmp subtype with id %d", type); + FreeImage_OutputMessageProc(s_format_id, buf, io); + } + + return NULL; +} + +template +struct SPaletteIndexer +{ + static inline uint32_t IndexOf(const uint8_t *inData, uint32_t inPos) + { + uint32_t divisor = 8 / TBitWidth; + uint32_t byte = inPos / divisor; + uint32_t modulus = inPos % divisor; + uint32_t shift = TBitWidth * modulus; + uint32_t mask = (1 << TBitWidth) - 1; + mask = mask << shift; + uint32_t byteData = inData[byte]; + return (byteData & mask) >> shift; + } +}; + +template <> +struct SPaletteIndexer<1> +{ + static inline uint32_t IndexOf(const uint8_t *inData, uint32_t inPos) + { + uint32_t byte = (inPos / 8); + uint32_t bit = 1 << (7 - (inPos % 8)); + uint32_t byteData = inData[byte]; + return (byteData & bit) ? 1 : 0; + } +}; + +template <> +struct SPaletteIndexer<8> +{ + static inline uint32_t IndexOf(const uint8_t *inData, uint32_t inPos) + { + uint32_t byte = inPos; + uint32_t bit = 0xFF; + uint32_t byteData = inData[byte]; + return byteData & bit; + } +}; + +static inline void assignQuad(uint8_t *dest, const RGBQUAD &quad) +{ + dest[0] = quad.rgbRed; + dest[1] = quad.rgbGreen; + dest[2] = quad.rgbBlue; +} + +template +inline void LoadPalettized(bool inFlipY, const RGBQUAD *palette, void *data, uint8_t *newData, + int width, int height, int components, int transparentIndex) +{ + const uint8_t *oldData = (const uint8_t *)data; + int pitch = CalculatePitch(CalculateLine(width, bitCount)); + for (uint32_t h = 0; h < (uint32_t)height; ++h) { + uint32_t relHeight = h; + if (inFlipY) + relHeight = ((uint32_t)height) - h - 1; + for (uint32_t w = 0; w < (uint32_t)width; ++w) { + const uint8_t *dataLine = oldData + pitch * h; + uint32_t pos = width * relHeight + w; + uint32_t paletteIndex = SPaletteIndexer::IndexOf(dataLine, w); + const RGBQUAD &theQuad = palette[paletteIndex]; + uint8_t *writePtr = newData + (pos * components); + assignQuad(writePtr, theQuad); + if (paletteIndex == (uint32_t)transparentIndex && components == 4) { + writePtr[3] = 0; + } + } + } +} + +inline int firstHighBit(int data) +{ + if (data == 0) + return 0; + int idx = 0; + while ((data % 2) == 0) { + data = data >> 1; + ++idx; + } + return idx; +} + +struct SMaskData +{ + uint32_t mask; + uint32_t shift; + uint32_t max; + + SMaskData(int inMask) + { + mask = inMask; + shift = firstHighBit(mask); + max = mask >> shift; + } + + inline uint8_t MapColor(uint32_t color) const + { + uint32_t intermediateValue = (color & mask) >> shift; + return (uint8_t)((intermediateValue * 255) / max); + } +}; + +template +struct ColorAccess +{ +}; + +template <> +struct ColorAccess<16> +{ + static uint32_t GetPixelWidth() { return 2; } + static uint32_t GetColor(const char8_t *src) + { + return (uint32_t) * reinterpret_cast(src); + } +}; + +template <> +struct ColorAccess<24> +{ + static uint32_t GetPixelWidth() { return 3; } + static uint32_t GetColor(const char8_t *src) + { + return (uint32_t)(*reinterpret_cast(src) & 0xFFFFFF); + } +}; + +template <> +struct ColorAccess<32> +{ + static uint32_t GetPixelWidth() { return 4; } + static uint32_t GetColor(const char8_t *src) + { + return *reinterpret_cast(src); + } +}; + +template +inline void LoadMasked(bool inFlipY, QT3DSI32 *inMasks, void *data, uint8_t *newData, int width, + int height) +{ + const char8_t *oldData = (const char8_t *)data; + SMaskData rMask(inMasks[0]); + SMaskData gMask(inMasks[1]); + SMaskData bMask(inMasks[2]); + for (int h = 0; h < height; ++h) { + int relHeight = h; + if (inFlipY) + relHeight = height - h - 1; + for (int w = 0; w < width; ++w) { + int pos = width * relHeight + w; + const char8_t *readPtr = oldData + (pos * ColorAccess::GetPixelWidth()); + uint8_t *writePtr = newData + (pos * 3); + uint32_t colorVal = ColorAccess::GetColor(readPtr); + writePtr[0] = rMask.MapColor(colorVal); + writePtr[1] = gMask.MapColor(colorVal); + writePtr[2] = bMask.MapColor(colorVal); + } + } +} + +void SLoadedTexture::FreeImagePostProcess(bool inFlipY) +{ + // We always convert 32 bit RGBA + if (m_ExtendedFormat != ExtendedTextureFormats::NoExtendedFormat) { + + QT3DSU32 stride = 3 * width; + format = NVRenderTextureFormats::RGB8; + components = 3; + if (m_ExtendedFormat == ExtendedTextureFormats::Palettized && m_TransparentPaletteIndex > -1 + && m_TransparentPaletteIndex < 256) { + stride = 4 * width; + components = 4; + format = NVRenderTextureFormats::RGBA8; + } + QT3DSU32 byteSize = height * stride; + uint8_t *newData = + (uint8_t *)m_Allocator.allocate(byteSize, "texture data", __FILE__, __LINE__); + if (format == NVRenderTextureFormats::RGBA8) + memSet(newData, 255, byteSize); + switch (m_ExtendedFormat) { + case ExtendedTextureFormats::Palettized: { + RGBQUAD *palette = (RGBQUAD *)m_Palette; + switch (m_BitCount) { + case 1: + LoadPalettized<1>(inFlipY, palette, data, newData, width, height, components, + m_TransparentPaletteIndex); + break; + case 2: + LoadPalettized<2>(inFlipY, palette, data, newData, width, height, components, + m_TransparentPaletteIndex); + break; + case 4: + LoadPalettized<4>(inFlipY, palette, data, newData, width, height, components, + m_TransparentPaletteIndex); + break; + case 8: + LoadPalettized<8>(inFlipY, palette, data, newData, width, height, components, + m_TransparentPaletteIndex); + break; + default: + QT3DS_ASSERT(false); + memSet(newData, 0, byteSize); + break; + } + } break; + case ExtendedTextureFormats::CustomRGB: { + switch (m_BitCount) { + case 16: + LoadMasked<16>(inFlipY, m_CustomMasks, data, newData, width, height); + break; + case 24: + LoadMasked<24>(inFlipY, m_CustomMasks, data, newData, width, height); + break; + case 32: + LoadMasked<32>(inFlipY, m_CustomMasks, data, newData, width, height); + break; + default: + QT3DS_ASSERT(false); + memSet(newData, 0, byteSize); + break; + } + } break; + default: + QT3DS_ASSERT(false); + memSet(newData, 0, byteSize); + break; + } + m_Allocator.deallocate(data); + if (m_Palette) + m_Allocator.deallocate(m_Palette); + data = newData; + m_Palette = NULL; + m_BitCount = 0; + this->dataSizeInBytes = byteSize; + m_ExtendedFormat = ExtendedTextureFormats::NoExtendedFormat; + } +} + +SLoadedTexture *SLoadedTexture::LoadBMP(ISeekableIOStream &inStream, bool inFlipY, + NVFoundationBase &inFnd, + qt3ds::render::NVRenderContextType renderContextType) +{ + Q_UNUSED(renderContextType) + FreeImageIO theIO(inFnd.getAllocator(), inFnd); + SLoadedTexture *retval = DoLoadBMP(&theIO, &inStream, 0); + if (retval) + retval->FreeImagePostProcess(inFlipY); + return retval; +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.cpp new file mode 100644 index 0000000..19d41d1 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.cpp @@ -0,0 +1,695 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2016 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 "Qt3DSRenderLoadedTextureDDS.h" +#include "Qt3DSRenderLoadedTextureFreeImageCompat.h" + +using namespace qt3ds::render; + +namespace qt3ds { +namespace render { + + static int s_exception_string; + + //================================================================================ + // DXT data-layout structure definitions. + typedef struct + { + QT3DSU16 col0; // 16-bit 565 interpolant endpoints + QT3DSU16 col1; + QT3DSU8 row[4]; // 4x4 * 2bpp color-index == 4 bytes. + } DXTColBlock; + + typedef struct + { + QT3DSU16 row[4]; // 4x4 * 4bpp alpha == 8 bytes. (pure 4-bit alpha values) + } DXT3AlphaBlock; + + typedef struct + { + QT3DSU8 alpha0; // 8-bit alpha interpolant endpoints + QT3DSU8 alpha1; + QT3DSU8 row[6]; // 4x4 * 3bpp alpha-index == 48bits == 6 bytes. + } DXT5AlphaBlock; + + typedef struct + { + QT3DSU8 red; + QT3DSU8 green; + QT3DSU8 blue; + QT3DSU8 alpha; + } Color8888; + +//================================================================================ +// Various DDS file defines + +#define DDSD_CAPS 0x00000001l +#define DDSD_HEIGHT 0x00000002l +#define DDSD_WIDTH 0x00000004l +#define DDSD_PIXELFORMAT 0x00001000l +#define DDS_ALPHAPIXELS 0x00000001l +#define DDS_FOURCC 0x00000004l +#define DDS_PITCH 0x00000008l +#define DDS_COMPLEX 0x00000008l +#define DDS_RGB 0x00000040l +#define DDS_TEXTURE 0x00001000l +#define DDS_MIPMAPCOUNT 0x00020000l +#define DDS_LINEARSIZE 0x00080000l +#define DDS_VOLUME 0x00200000l +#define DDS_MIPMAP 0x00400000l +#define DDS_DEPTH 0x00800000l + +#define DDS_CUBEMAP 0x00000200L +#define DDS_CUBEMAP_POSITIVEX 0x00000400L +#define DDS_CUBEMAP_NEGATIVEX 0x00000800L +#define DDS_CUBEMAP_POSITIVEY 0x00001000L +#define DDS_CUBEMAP_NEGATIVEY 0x00002000L +#define DDS_CUBEMAP_POSITIVEZ 0x00004000L +#define DDS_CUBEMAP_NEGATIVEZ 0x00008000L + +#define FOURCC_DXT1 0x31545844 //(MAKEFOURCC('D','X','T','1')) +#define FOURCC_DXT3 0x33545844 //(MAKEFOURCC('D','X','T','3')) +#define FOURCC_DXT5 0x35545844 //(MAKEFOURCC('D','X','T','5')) + +#define DDS_MAGIC_FLIPPED 0x0F7166ED + + //================================================================================ + // DDS file format structures. + typedef struct _DDS_PIXELFORMAT + { + QT3DSU32 dwSize; + QT3DSU32 dwFlags; + QT3DSU32 dwFourCC; + QT3DSU32 dwRGBBitCount; + QT3DSU32 dwRBitMask; + QT3DSU32 dwGBitMask; + QT3DSU32 dwBBitMask; + QT3DSU32 dwABitMask; + } DDS_PIXELFORMAT; + + typedef struct _DDS_HEADER + { + QT3DSU32 dwSize; + QT3DSU32 dwFlags; + QT3DSU32 dwHeight; + QT3DSU32 dwWidth; + QT3DSU32 dwPitchOrLinearSize; + QT3DSU32 dwDepth; + QT3DSU32 dwMipMapCount; + QT3DSU32 dwReserved1[11]; + DDS_PIXELFORMAT ddspf; + QT3DSU32 dwCaps1; + QT3DSU32 dwCaps2; + QT3DSU32 dwReserved2[3]; + } DDS_HEADER; + + //================================================================================ + // helper functions + //================================================================================ + + // helper macros. + static inline void NvSwapChar(QT3DSU8 &a, QT3DSU8 &b) + { + QT3DSU8 tmp; + tmp = a; + a = b; + b = tmp; + } + + static inline void NvSwapShort(QT3DSU16 &a, QT3DSU16 &b) + { + QT3DSU16 tmp; + tmp = a; + a = b; + b = tmp; + } + + //================================================================================ + //================================================================================ + static void flip_blocks_dxtc1(DXTColBlock *line, QT3DSI32 numBlocks) + { + DXTColBlock *curblock = line; + QT3DSI32 i; + + for (i = 0; i < numBlocks; i++) { + NvSwapChar(curblock->row[0], curblock->row[3]); + NvSwapChar(curblock->row[1], curblock->row[2]); + curblock++; + } + } + + //================================================================================ + //================================================================================ + static void flip_blocks_dxtc3(DXTColBlock *line, QT3DSI32 numBlocks) + { + DXTColBlock *curblock = line; + DXT3AlphaBlock *alphablock; + QT3DSI32 i; + + for (i = 0; i < numBlocks; i++) { + alphablock = (DXT3AlphaBlock *)curblock; + + NvSwapShort(alphablock->row[0], alphablock->row[3]); + NvSwapShort(alphablock->row[1], alphablock->row[2]); + curblock++; + + NvSwapChar(curblock->row[0], curblock->row[3]); + NvSwapChar(curblock->row[1], curblock->row[2]); + curblock++; + } + } + + static void flip_dxt5_alpha(DXT5AlphaBlock *block) + { + QT3DSI8 gBits[4][4]; + + const QT3DSU32 mask = 0x00000007; // bits = 00 00 01 11 + QT3DSU32 bits = 0; + memcpy(&bits, &block->row[0], sizeof(QT3DSI8) * 3); + + gBits[0][0] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[0][1] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[0][2] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[0][3] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[1][0] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[1][1] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[1][2] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[1][3] = (QT3DSI8)(bits & mask); + + bits = 0; + memcpy(&bits, &block->row[3], sizeof(QT3DSI8) * 3); + + gBits[2][0] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[2][1] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[2][2] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[2][3] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[3][0] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[3][1] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[3][2] = (QT3DSI8)(bits & mask); + bits >>= 3; + gBits[3][3] = (QT3DSI8)(bits & mask); + + bits = (gBits[3][0] << 0) | (gBits[3][1] << 3) | (gBits[3][2] << 6) | (gBits[3][3] << 9) + | (gBits[2][0] << 12) | (gBits[2][1] << 15) | (gBits[2][2] << 18) | (gBits[2][3] << 21); + memcpy(&block->row[0], &bits, 3); + + bits = (gBits[1][0] << 0) | (gBits[1][1] << 3) | (gBits[1][2] << 6) | (gBits[1][3] << 9) + | (gBits[0][0] << 12) | (gBits[0][1] << 15) | (gBits[0][2] << 18) | (gBits[0][3] << 21); + memcpy(&block->row[3], &bits, 3); + } + + static void flip_blocks_dxtc5(DXTColBlock *line, QT3DSI32 numBlocks) + { + DXTColBlock *curblock = line; + DXT5AlphaBlock *alphablock; + QT3DSI32 i; + + for (i = 0; i < numBlocks; i++) { + alphablock = (DXT5AlphaBlock *)curblock; + + flip_dxt5_alpha(alphablock); + curblock++; + + NvSwapChar(curblock->row[0], curblock->row[3]); + NvSwapChar(curblock->row[1], curblock->row[2]); + curblock++; + } + } + + static void flip_data_vertical(FreeImageIO *io, QT3DSI8 *image, QT3DSI32 width, QT3DSI32 height, + Qt3DSDDSImage *info) + { + if (info->compressed) { + QT3DSI32 linesize, j; + DXTColBlock *top; + DXTColBlock *bottom; + QT3DSI8 *tmp; + void (*flipblocks)(DXTColBlock *, QT3DSI32) = NULL; + QT3DSI32 xblocks = width / 4; + QT3DSI32 yblocks = height / 4; + QT3DSI32 blocksize; + + switch (info->format) { + case qt3ds::render::NVRenderTextureFormats::RGBA_DXT1: + blocksize = 8; + flipblocks = &flip_blocks_dxtc1; + break; + case qt3ds::render::NVRenderTextureFormats::RGBA_DXT3: + blocksize = 16; + flipblocks = &flip_blocks_dxtc3; + break; + case qt3ds::render::NVRenderTextureFormats::RGBA_DXT5: + blocksize = 16; + flipblocks = &flip_blocks_dxtc5; + break; + default: + return; + } + + linesize = xblocks * blocksize; + tmp = (QT3DSI8 *)QT3DS_ALLOC(io->m_Allocator, linesize, "flip_data_vertical compressed"); + + for (j = 0; j < (yblocks >> 1); j++) { + top = (DXTColBlock *)(void *)(image + j * linesize); + bottom = (DXTColBlock *)(void *)(image + (((yblocks - j) - 1) * linesize)); + + (*flipblocks)(top, xblocks); + (*flipblocks)(bottom, xblocks); + + memcpy(tmp, bottom, linesize); + memcpy(bottom, top, linesize); + memcpy(top, tmp, linesize); + } + + // Catch the middle row of blocks if there is one + // The loop above will skip the middle row + if (yblocks & 0x01) { + DXTColBlock *middle = (DXTColBlock *)(void *)(image + (yblocks >> 1) * linesize); + (*flipblocks)(middle, xblocks); + } + + QT3DS_FREE(io->m_Allocator, tmp); + } else { + QT3DSI32 linesize = width * info->bytesPerPixel; + QT3DSI32 j; + QT3DSI8 *top; + QT3DSI8 *bottom; + QT3DSI8 *tmp; + + // much simpler - just compute the line length and swap each row + tmp = (QT3DSI8 *)QT3DS_ALLOC(io->m_Allocator, linesize, "flip_data_vertical"); + ; + + for (j = 0; j < (height >> 1); j++) { + top = (QT3DSI8 *)(image + j * linesize); + bottom = (QT3DSI8 *)(image + (((height - j) - 1) * linesize)); + + memcpy(tmp, bottom, linesize); + memcpy(bottom, top, linesize); + memcpy(top, tmp, linesize); + } + + QT3DS_FREE(io->m_Allocator, tmp); + } + } + + static QT3DSI32 size_image(QT3DSI32 width, QT3DSI32 height, const Qt3DSDDSImage *image) + { + if (image->compressed) { + return ((width + 3) / 4) * ((height + 3) / 4) + * (image->format == qt3ds::render::NVRenderTextureFormats::RGBA_DXT1 ? 8 : 16); + } else { + return width * height * image->bytesPerPixel; + } + } + + static QT3DSI32 total_image_data_size(Qt3DSDDSImage *image) + { + QT3DSI32 i, j, index = 0, size = 0, w, h; + QT3DSI32 cubeCount = image->cubemap ? 6 : 1; + + for (j = 0; j < cubeCount; j++) { + w = image->width; + h = image->height; + + for (i = 0; i < image->numMipmaps; i++) // account for base plus each mip + { + image->size[index] = size_image(w, h, image); + image->mipwidth[index] = w; + image->mipheight[index] = h; + size += image->size[index]; + if (w != 1) { + w >>= 1; + } + if (h != 1) { + h >>= 1; + } + + index++; + } + } + + return (size); + } + + void *Qt3DSDDSAllocDataBlock(FreeImageIO *io, Qt3DSDDSImage *image) + { + if (image) { + QT3DSI32 i; + QT3DSI32 size = total_image_data_size(image); + image->dataBlock = + QT3DS_ALLOC(io->m_Allocator, size, + "Qt3DSDDSAllocDataBlock"); // no need to calloc, as we fill every bit... + if (image->dataBlock == NULL) { + return NULL; + } + + image->data[0] = image->dataBlock; + + QT3DSI32 planes = image->numMipmaps * (image->cubemap ? 6 : 1); + + for (i = 1; i < planes; i++) // account for base plus each mip + { + image->data[i] = + (void *)(((size_t)(image->data[i - 1])) + (size_t)image->size[i - 1]); + } + + return (image->dataBlock); // in case caller wants to sanity check... + } + return (NULL); + } + + static FIBITMAP *DoLoadDDS(FreeImageIO *io, IInStream &inStream, QT3DSI32 flipVertical) + { + FIBITMAP *dib = NULL; + DDS_HEADER ddsh; + QT3DSI8 filecode[4]; + Qt3DSDDSImage *image = NULL; + bool needsBGRASwap = false; + ; + bool isAllreadyFlipped = false; + + try { + // check file code + inStream.Read(filecode, 4); + if (memcmp(filecode, "DDS ", 4)) { + throw "Invalid DDS file"; + } + + image = (Qt3DSDDSImage *)QT3DS_ALLOC(io->m_Allocator, sizeof(Qt3DSDDSImage), "DoLoadDDS"); + if (image == NULL) { + throw "Qt3DSDDSImage allocation failed"; + } + memset(image, 0, sizeof(Qt3DSDDSImage)); + + // read in DDS header + inStream.Read(&ddsh, 1); + + // check if image is a cubempap + if (ddsh.dwCaps2 & DDS_CUBEMAP) { + const QT3DSI32 allFaces = DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_POSITIVEY + | DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEX | DDS_CUBEMAP_NEGATIVEY + | DDS_CUBEMAP_NEGATIVEZ; + + if ((ddsh.dwCaps2 & allFaces) != allFaces) { + throw "Not all cubemap faces defined - not supported"; + } + + image->cubemap = 1; + } else { + image->cubemap = 0; + } + + // check if image is a volume texture + if ((ddsh.dwCaps2 & DDS_VOLUME) && (ddsh.dwDepth > 0)) { + throw "Volume textures not supported"; + } + + // allocated the memory for the structure we return + dib = QT3DS_NEW(io->m_Allocator, SLoadedTexture)(io->m_Allocator); + if (dib == NULL) { + throw "DIB allocation failed"; + } + + // figure out what the image format is + if (ddsh.ddspf.dwFlags & DDS_FOURCC) { + switch (ddsh.ddspf.dwFourCC) { + case FOURCC_DXT1: + image->format = qt3ds::render::NVRenderTextureFormats::RGBA_DXT1; + image->components = 3; + image->compressed = 1; + image->alpha = 0; // Ugh - for backwards compatibility + dib->format = qt3ds::render::NVRenderTextureFormats::RGB_DXT1; + break; + case FOURCC_DXT3: + image->format = qt3ds::render::NVRenderTextureFormats::RGBA_DXT3; + image->components = 4; + image->compressed = 1; + image->alpha = 1; + dib->format = qt3ds::render::NVRenderTextureFormats::RGBA_DXT3; + break; + case FOURCC_DXT5: + image->format = qt3ds::render::NVRenderTextureFormats::RGBA_DXT5; + image->components = 4; + image->compressed = 1; + image->alpha = 1; + dib->format = qt3ds::render::NVRenderTextureFormats::RGBA_DXT5; + break; + default: + throw "Unsupported FOURCC code"; + } + } else { + // Check for a supported pixel format + if ((ddsh.ddspf.dwRGBBitCount == 32) && (ddsh.ddspf.dwRBitMask == 0x000000FF) + && (ddsh.ddspf.dwGBitMask == 0x0000FF00) + && (ddsh.ddspf.dwBBitMask == 0x00FF0000) + && (ddsh.ddspf.dwABitMask == 0xFF000000)) { + // We support D3D's A8B8G8R8, which is actually RGBA in linear + // memory, equivalent to GL's RGBA + image->format = qt3ds::render::NVRenderTextureFormats::RGBA8; + image->components = 4; + image->componentFormat = qt3ds::render::NVRenderComponentTypes::QT3DSU8; + image->bytesPerPixel = 4; + image->alpha = 1; + image->compressed = 0; + dib->format = qt3ds::render::NVRenderTextureFormats::RGBA8; + } else if ((ddsh.ddspf.dwRGBBitCount == 32) && (ddsh.ddspf.dwRBitMask == 0x00FF0000) + && (ddsh.ddspf.dwGBitMask == 0x0000FF00) + && (ddsh.ddspf.dwBBitMask == 0x000000FF) + && (ddsh.ddspf.dwABitMask == 0xFF000000)) { + // We support D3D's A8R8G8B8, which is actually BGRA in linear + // memory, need to be + image->format = qt3ds::render::NVRenderTextureFormats::RGBA8; + image->components = 4; + image->componentFormat = qt3ds::render::NVRenderComponentTypes::QT3DSU8; + image->bytesPerPixel = 4; + image->alpha = 1; + image->compressed = 0; + needsBGRASwap = true; + dib->format = qt3ds::render::NVRenderTextureFormats::RGBA8; + } else if ((ddsh.ddspf.dwRGBBitCount == 16) && (ddsh.ddspf.dwRBitMask == 0x0000F800) + && (ddsh.ddspf.dwGBitMask == 0x000007E0) + && (ddsh.ddspf.dwBBitMask == 0x0000001F) + && (ddsh.ddspf.dwABitMask == 0x00000000)) { + // We support D3D's R5G6B5, which is actually RGB in linear + // memory. It is equivalent to GL's GL_UNSIGNED_SHORT_5_6_5 + image->format = qt3ds::render::NVRenderTextureFormats::RGB8; + image->components = 3; + image->alpha = 0; + image->componentFormat = qt3ds::render::NVRenderComponentTypes::QT3DSU16; + image->bytesPerPixel = 2; + image->compressed = 0; + dib->format = qt3ds::render::NVRenderTextureFormats::RGB8; + } else if ((ddsh.ddspf.dwRGBBitCount == 8) && (ddsh.ddspf.dwRBitMask == 0x00000000) + && (ddsh.ddspf.dwGBitMask == 0x00000000) + && (ddsh.ddspf.dwBBitMask == 0x00000000) + && (ddsh.ddspf.dwABitMask == 0x000000FF)) { + // We support D3D's A8 + image->format = qt3ds::render::NVRenderTextureFormats::Alpha8; + image->components = 1; + image->alpha = 1; + image->componentFormat = qt3ds::render::NVRenderComponentTypes::QT3DSU8; + image->bytesPerPixel = 1; + image->compressed = 0; + dib->format = qt3ds::render::NVRenderTextureFormats::Alpha8; + } else if ((ddsh.ddspf.dwRGBBitCount == 8) && (ddsh.ddspf.dwRBitMask == 0x000000FF) + && (ddsh.ddspf.dwGBitMask == 0x00000000) + && (ddsh.ddspf.dwBBitMask == 0x00000000) + && (ddsh.ddspf.dwABitMask == 0x00000000)) { + // We support D3D's L8 (flagged as 8 bits of red only) + image->format = qt3ds::render::NVRenderTextureFormats::Luminance8; + image->components = 1; + image->alpha = 0; + image->componentFormat = qt3ds::render::NVRenderComponentTypes::QT3DSU8; + image->bytesPerPixel = 1; + image->compressed = 0; + dib->format = qt3ds::render::NVRenderTextureFormats::Luminance8; + } else if ((ddsh.ddspf.dwRGBBitCount == 16) && (ddsh.ddspf.dwRBitMask == 0x000000FF) + && (ddsh.ddspf.dwGBitMask == 0x00000000) + && (ddsh.ddspf.dwBBitMask == 0x00000000) + && (ddsh.ddspf.dwABitMask == 0x0000FF00)) { + // We support D3D's A8L8 (flagged as 8 bits of red and 8 bits of alpha) + image->format = qt3ds::render::NVRenderTextureFormats::LuminanceAlpha8; + image->components = 2; + image->alpha = 1; + image->componentFormat = qt3ds::render::NVRenderComponentTypes::QT3DSU8; + image->bytesPerPixel = 2; + image->compressed = 0; + dib->format = qt3ds::render::NVRenderTextureFormats::LuminanceAlpha8; + } else { + throw "not a DXTC or supported RGB(A) format image"; + } + } + + // detect flagging to indicate this texture was stored in a y-inverted fashion + if (!(ddsh.dwFlags & DDS_LINEARSIZE)) { + if (ddsh.dwPitchOrLinearSize == DDS_MAGIC_FLIPPED) { + isAllreadyFlipped = true; + } + } + + flipVertical = (isAllreadyFlipped != (flipVertical ? true : false)) ? 1 : 0; + + // store primary surface width/height/numMipmaps + image->width = ddsh.dwWidth; + image->height = ddsh.dwHeight; + image->numMipmaps = ddsh.dwFlags & DDS_MIPMAPCOUNT ? ddsh.dwMipMapCount : 1; + + if (image->numMipmaps > QT3DS_DDS_MAX_MIPMAPS) { + throw "Too many mipmaps: max 16"; + } + + // allocate the meta datablock for all mip storage. + Qt3DSDDSAllocDataBlock(io, image); + if (image->dataBlock == NULL) { + throw "Failed to allocate memory for image data storage"; + } + + dib->width = image->width; + dib->height = image->height; + dib->dds = image; + + QT3DSI32 faces = image->cubemap ? 6 : 1; + + QT3DSI32 index = 0; + for (QT3DSI32 j = 0; j < faces; j++) { + // load all surfaces for the image + QT3DSI32 width = image->width; + QT3DSI32 height = image->height; + + for (QT3DSI32 i = 0; i < image->numMipmaps; i++) { + // Get the size, read in the data. + inStream.Read( + NVDataRef((QT3DSU8 *)image->data[index], (QT3DSU32)image->size[index])); + + // Flip in Y for OpenGL if needed + if (flipVertical) + flip_data_vertical(io, (QT3DSI8 *)image->data[index], width, height, image); + + // shrink to next power of 2 + width >>= 1; + height >>= 1; + + if (!width) + width = 1; + + if (!height) + height = 1; + + // make sure DXT isn't <4 on a side... + if (image->compressed) { + if (width < 4) + width = 4; + if (height < 4) + height = 4; + } + + index++; + } + } + + if (needsBGRASwap) { + QT3DSI32 index = 0; + QT3DSI32 k; + + for (k = 0; k < faces; k++) { + QT3DSI32 width = image->width; + QT3DSI32 height = image->height; + + for (QT3DSI32 i = 0; i < image->numMipmaps; i++) { + QT3DSI8 *data = (QT3DSI8 *)(image->data[index]); + QT3DSI32 pixels = width * height; + QT3DSI32 j; + + for (j = 0; j < pixels; j++) { + QT3DSI8 temp = data[0]; + data[0] = data[2]; + data[2] = temp; + + data += 4; + } + + // shrink to next power of 2 + width >>= 1; + height >>= 1; + + if (!width) + width = 1; + + if (!height) + height = 1; + + index++; + } + } + } + } catch (const char *message) { + if (image) { + if (image->dataBlock) + QT3DS_FREE(io->m_Allocator, image->dataBlock); + + QT3DS_FREE(io->m_Allocator, image); + } + if (dib) { + FreeImage_Unload(dib); + } + if (message) { + FreeImage_OutputMessageProc(s_exception_string, message, io); + } + } + + return dib; + } + + SLoadedTexture *SLoadedTexture::LoadDDS(IInStream &inStream, QT3DSI32 flipVertical, + NVFoundationBase &inFnd, + qt3ds::render::NVRenderContextType renderContextType) + { + Q_UNUSED(renderContextType) + FreeImageIO theIO(inFnd.getAllocator(), inFnd); + SLoadedTexture *retval = DoLoadDDS(&theIO, inStream, flipVertical); + + return retval; + } +} +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.h b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.h new file mode 100644 index 0000000..8490b47 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureDDS.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2016 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$ +** +****************************************************************************/ + +#pragma once +#ifndef QT3DS_RENDER_LOAD_DDS_H +#define QT3DS_RENDER_LOAD_DDS_H + +namespace qt3ds { +namespace render { + +/** The maximum number of mipmap levels (per texture or per cubemap face) */ +#define QT3DS_DDS_MAX_MIPMAPS (16) + +/** The number of cubemap faces that must exist in a cubemap-bearing DDS file */ +#define QT3DS_DDS_NUM_CUBEMAP_FACES (6) + + /** The master DDS structure for loading and saving + + This is the master DDS structure. It shouldn't be allocated by hand, + always use NVHHDDSAlloc/NVHHDDSAllocData/NVHHDDSFree to manage them properly. + */ + + struct Qt3DSDDSImage + { + /** Width of the overall texture in texels */ + int width; + /** Height of the overall texture in texels */ + int height; + /** Number of color/alpha components per texel 1-4 */ + int components; + /** The GL type of each color component (noncompressed textures only) */ + int componentFormat; + /** The number of bytes per pixel (noncompressed textures only) */ + int bytesPerPixel; + /** Nonzero if the format is DXT-compressed */ + int compressed; + /** The number of levels in the mipmap pyramid (including the base level) */ + int numMipmaps; + /** If nonzero, then the file contains 6 cubemap faces */ + int cubemap; + /** The format of the loaded texture data */ + int format; + /** The GL internal format of the loaded texture data(compressed textures only) */ + int internalFormat; + /** Nonzero if the texture data includes alpha */ + int alpha; + /** Base of the allocated block of all texel data */ + void *dataBlock; + /** Pointers to the mipmap levels for the texture or each cubemap face */ + void *data[QT3DS_DDS_MAX_MIPMAPS * QT3DS_DDS_NUM_CUBEMAP_FACES]; + /** Array of sizes of the mipmap levels for the texture or each cubemap face */ + int size[QT3DS_DDS_MAX_MIPMAPS * QT3DS_DDS_NUM_CUBEMAP_FACES]; + /** Array of widths of the mipmap levels for the texture or each cubemap face */ + int mipwidth[QT3DS_DDS_MAX_MIPMAPS * QT3DS_DDS_NUM_CUBEMAP_FACES]; + /** Array of heights of the mipmap levels for the texture or each cubemap face */ + int mipheight[QT3DS_DDS_MAX_MIPMAPS * QT3DS_DDS_NUM_CUBEMAP_FACES]; + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureFreeImageCompat.h b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureFreeImageCompat.h new file mode 100644 index 0000000..13f317b --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureFreeImageCompat.h @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_LOADED_TEXTURE_FREEIMAGE_COMPAT_H +#define QT3DS_RENDER_LOADED_TEXTURE_FREEIMAGE_COMPAT_H +#include "foundation/IOStreams.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "Qt3DSRenderLoadedTexture.h" +#include "EASTL/algorithm.h" +#include +#ifndef _MACOSX +#ifndef _INTEGRITYPLATFORM +#include +#endif +#endif + +// We use a compatibility layer so we can easily convert freeimage code to load our texture formats +// where necessary. + +namespace qt3ds { +namespace render { + using namespace qt3ds::foundation; + + typedef int32_t BOOL; + typedef uint8_t BYTE; + typedef uint16_t WORD; + typedef uint32_t DWORD; + typedef int32_t LONG; + +#define FREEIMAGE_COLORORDER_BGR 0 +#define FREEIMAGE_COLORORDER_RGB 1 + +#define FREEIMAGE_COLORORDER FREEIMAGE_COLORORDER_BGR + + typedef ISeekableIOStream *fi_handle; + + struct FreeImageIO + { + NVAllocatorCallback &m_Allocator; + NVFoundationBase &m_Foundation; + int (*read_proc)(void *data, int size, int itemSize, fi_handle handle); + void (*seek_proc)(fi_handle handle, int offset, int pos); + int (*tell_proc)(fi_handle handle); + static inline int reader(void *data, int size, int itemSize, fi_handle handle) + { + NVDataRef theData(toDataRef((QT3DSU8 *)data, (QT3DSU32)size * itemSize)); + QT3DSU32 amount = handle->Read(theData); + return (int)amount; + } + static inline void seeker(fi_handle handle, int offset, int pos) + { + SeekPosition::Enum seekPos(SeekPosition::Begin); + /* +#define SEEK_CUR 1 +#define SEEK_END 2 +#define SEEK_SET 0*/ + switch (pos) { + case 0: + seekPos = SeekPosition::Begin; + break; + case 1: + seekPos = SeekPosition::Current; + break; + case 2: + seekPos = SeekPosition::End; + break; + default: + QT3DS_ASSERT(false); + break; + } + handle->SetPosition(offset, seekPos); + } + static inline int teller(fi_handle handle) { return (int)handle->GetPosition(); } + FreeImageIO(NVAllocatorCallback &alloc, NVFoundationBase &fnd) + : m_Allocator(alloc) + , m_Foundation(fnd) + , read_proc(reader) + , seek_proc(seeker) + , tell_proc(teller) + { + } + }; + + typedef SLoadedTexture FIBITMAP; + inline BYTE *FreeImage_GetBits(FIBITMAP *bmp) { return (BYTE *)bmp->data; } + + inline int FreeImage_GetHeight(FIBITMAP *bmp) { return bmp->height; } + inline int FreeImage_GetWidth(FIBITMAP *bmp) { return bmp->width; } + +#define INPLACESWAP(x, y) eastl::swap(x, y) +#define MIN(x, y) NVMin(x, y) +#define MAX(x, y) NVMax(x, y) + +#define TRUE 1 +#define FALSE 0 + + typedef struct tagBITMAPINFOHEADER + { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; + } BITMAPINFOHEADER, *PBITMAPINFOHEADER; + + typedef struct tagRGBQUAD + { +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; +#else + BYTE rgbRed; + BYTE rgbGreen; + BYTE rgbBlue; +#endif // FREEIMAGE_COLORORDER + BYTE rgbReserved; + } RGBQUAD; + + typedef struct tagRGBTRIPLE + { +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR + BYTE rgbtBlue; + BYTE rgbtGreen; + BYTE rgbtRed; +#else + BYTE rgbtRed; + BYTE rgbtGreen; + BYTE rgbtBlue; +#endif // FREEIMAGE_COLORORDER + } RGBTRIPLE; + + typedef struct tagBITMAPINFO + { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; + } BITMAPINFO, *PBITMAPINFO; + + typedef struct tagFILE_RGBA + { + unsigned char r, g, b, a; + } FILE_RGBA; + + typedef struct tagFILE_BGRA + { + unsigned char b, g, r, a; + } FILE_BGRA; + + typedef struct tagFILE_RGB + { + unsigned char r, g, b; + } FILE_RGB; + + typedef struct tagFILE_BGR + { + unsigned char b, g, r; + } FILE_BGR; + +// Indexes for byte arrays, masks and shifts for treating pixels as words --- +// These coincide with the order of RGBQUAD and RGBTRIPLE ------------------- + +#ifndef FREEIMAGE_BIGENDIAN +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR +// Little Endian (x86 / MS Windows, Linux) : BGR(A) order +#define FI_RGBA_RED 2 +#define FI_RGBA_GREEN 1 +#define FI_RGBA_BLUE 0 +#define FI_RGBA_ALPHA 3 +#define FI_RGBA_RED_MASK 0x00FF0000 +#define FI_RGBA_GREEN_MASK 0x0000FF00 +#define FI_RGBA_BLUE_MASK 0x000000FF +#define FI_RGBA_ALPHA_MASK 0xFF000000 +#define FI_RGBA_RED_SHIFT 16 +#define FI_RGBA_GREEN_SHIFT 8 +#define FI_RGBA_BLUE_SHIFT 0 +#define FI_RGBA_ALPHA_SHIFT 24 +#else +// Little Endian (x86 / MaxOSX) : RGB(A) order +#define FI_RGBA_RED 0 +#define FI_RGBA_GREEN 1 +#define FI_RGBA_BLUE 2 +#define FI_RGBA_ALPHA 3 +#define FI_RGBA_RED_MASK 0x000000FF +#define FI_RGBA_GREEN_MASK 0x0000FF00 +#define FI_RGBA_BLUE_MASK 0x00FF0000 +#define FI_RGBA_ALPHA_MASK 0xFF000000 +#define FI_RGBA_RED_SHIFT 0 +#define FI_RGBA_GREEN_SHIFT 8 +#define FI_RGBA_BLUE_SHIFT 16 +#define FI_RGBA_ALPHA_SHIFT 24 +#endif // FREEIMAGE_COLORORDER +#else +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR +// Big Endian (PPC / none) : BGR(A) order +#define FI_RGBA_RED 2 +#define FI_RGBA_GREEN 1 +#define FI_RGBA_BLUE 0 +#define FI_RGBA_ALPHA 3 +#define FI_RGBA_RED_MASK 0x0000FF00 +#define FI_RGBA_GREEN_MASK 0x00FF0000 +#define FI_RGBA_BLUE_MASK 0xFF000000 +#define FI_RGBA_ALPHA_MASK 0x000000FF +#define FI_RGBA_RED_SHIFT 8 +#define FI_RGBA_GREEN_SHIFT 16 +#define FI_RGBA_BLUE_SHIFT 24 +#define FI_RGBA_ALPHA_SHIFT 0 +#else +// Big Endian (PPC / Linux, MaxOSX) : RGB(A) order +#define FI_RGBA_RED 0 +#define FI_RGBA_GREEN 1 +#define FI_RGBA_BLUE 2 +#define FI_RGBA_ALPHA 3 +#define FI_RGBA_RED_MASK 0xFF000000 +#define FI_RGBA_GREEN_MASK 0x00FF0000 +#define FI_RGBA_BLUE_MASK 0x0000FF00 +#define FI_RGBA_ALPHA_MASK 0x000000FF +#define FI_RGBA_RED_SHIFT 24 +#define FI_RGBA_GREEN_SHIFT 16 +#define FI_RGBA_BLUE_SHIFT 8 +#define FI_RGBA_ALPHA_SHIFT 0 +#endif // FREEIMAGE_COLORORDER +#endif // FREEIMAGE_BIGENDIAN + +#define FI_RGBA_RGB_MASK (FI_RGBA_RED_MASK | FI_RGBA_GREEN_MASK | FI_RGBA_BLUE_MASK) + +// The 16bit macros only include masks and shifts, since each color element is not byte aligned + +#define FI16_555_RED_MASK 0x7C00 +#define FI16_555_GREEN_MASK 0x03E0 +#define FI16_555_BLUE_MASK 0x001F +#define FI16_555_RED_SHIFT 10 +#define FI16_555_GREEN_SHIFT 5 +#define FI16_555_BLUE_SHIFT 0 +#define FI16_565_RED_MASK 0xF800 +#define FI16_565_GREEN_MASK 0x07E0 +#define FI16_565_BLUE_MASK 0x001F +#define FI16_565_RED_SHIFT 11 +#define FI16_565_GREEN_SHIFT 5 +#define FI16_565_BLUE_SHIFT 0 + + inline unsigned char HINIBBLE(unsigned char byte) { return byte & 0xF0; } + + inline unsigned char LOWNIBBLE(unsigned char byte) { return byte & 0x0F; } + + inline int CalculateUsedBits(int bits) + { + int bit_count = 0; + unsigned bit = 1; + + for (unsigned i = 0; i < 32; i++) { + if ((bits & bit) == bit) { + bit_count++; + } + + bit <<= 1; + } + + return bit_count; + } + + inline int CalculateLine(int width, int bitdepth) { return ((width * bitdepth) + 7) / 8; } + + inline int CalculatePitch(int line) { return (line + 3) & ~3; } + + inline int CalculateUsedPaletteEntries(int bit_count) + { + if ((bit_count >= 1) && (bit_count <= 8)) + return 1 << bit_count; + + return 0; + } + + inline unsigned char *CalculateScanLine(unsigned char *bits, unsigned pitch, int scanline) + { + return (bits + (pitch * scanline)); + } + + inline void ReplaceExtension(char *result, const char *filename, const char *extension) + { + for (size_t i = strlen(filename) - 1; i > 0; --i) { + if (filename[i] == '.') { + memcpy(result, filename, i); + result[i] = '.'; + memcpy(result + i + 1, extension, strlen(extension) + 1); + return; + } + } + + memcpy(result, filename, strlen(filename)); + result[strlen(filename)] = '.'; + memcpy(result + strlen(filename) + 1, extension, strlen(extension) + 1); + } + + inline BYTE *FreeImage_GetScanLine(FIBITMAP *bmp, int height) + { + return CalculateScanLine( + (BYTE *)bmp->data, CalculatePitch(CalculateLine(bmp->width, bmp->m_BitCount)), height); + } + +#define DLL_CALLCONV + +// ignored for now. +#define FreeImage_SetDotsPerMeterX(img, dots) +#define FreeImage_SetDotsPerMeterY(img, dots) + + inline SLoadedTexture *FreeImage_Allocate(int width, int height, int bit_count, FreeImageIO *io) + { + SLoadedTexture *theTexture = QT3DS_NEW(io->m_Allocator, SLoadedTexture)(io->m_Allocator); + int pitch = CalculatePitch(CalculateLine(width, bit_count)); + QT3DSU32 dataSize = (QT3DSU32)(height * pitch); + theTexture->dataSizeInBytes = dataSize; + theTexture->data = io->m_Allocator.allocate(dataSize, "image data", __FILE__, __LINE__); + memZero(theTexture->data, dataSize); + theTexture->width = width; + theTexture->height = height; + theTexture->m_BitCount = bit_count; + // If free image asks us for a palette, we change our format at that time. + theTexture->m_ExtendedFormat = ExtendedTextureFormats::CustomRGB; + return theTexture; + } + + inline SLoadedTexture *FreeImage_Allocate(int width, int height, int bit_count, int rmask, + int gmask, int bmask, FreeImageIO *io) + { + SLoadedTexture *retval = FreeImage_Allocate(width, height, bit_count, io); + retval->m_CustomMasks[0] = rmask; + retval->m_CustomMasks[1] = gmask; + retval->m_CustomMasks[2] = bmask; + return retval; + } + + inline RGBQUAD *FreeImage_GetPalette(SLoadedTexture *texture) + { + if (texture->m_Palette == NULL) { + texture->m_ExtendedFormat = ExtendedTextureFormats::Palettized; + QT3DSU32 memory = 256 * sizeof(RGBQUAD); + if (memory) { + texture->m_Palette = + texture->m_Allocator.allocate(memory, "texture palette", __FILE__, __LINE__); + memZero(texture->m_Palette, memory); + } + } + return (RGBQUAD *)texture->m_Palette; + } + + inline void FreeImage_Unload(SLoadedTexture *texture) { texture->release(); } + inline void FreeImage_OutputMessageProc(int, const char *message, FreeImageIO *io) + { + Q_UNUSED(io); + qCCritical(INVALID_OPERATION, "Error loading image: %s", message); + } + + inline void FreeImage_SetBackgroundColor(SLoadedTexture *texture, RGBQUAD *inColor) + { + if (inColor) { + texture->m_BackgroundColor[0] = inColor->rgbRed; + texture->m_BackgroundColor[1] = inColor->rgbGreen; + texture->m_BackgroundColor[2] = inColor->rgbBlue; + } else + memSet(texture->m_BackgroundColor, 0, 3); + } + + inline void FreeImage_SetTransparencyTable(SLoadedTexture *texture, BYTE *table, int size) + { + if (texture->m_TransparencyTable) + texture->m_Allocator.deallocate(texture->m_TransparencyTable); + texture->m_TransparencyTable = NULL; + if (table && size) { + texture->m_TransparencyTable = (uint8_t *)texture->m_Allocator.allocate( + size, "texture transparency table", __FILE__, __LINE__); + memCopy(texture->m_TransparencyTable, table, size); + } + } +} +} + +using namespace qt3ds::render; + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureGIF.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureGIF.cpp new file mode 100644 index 0000000..ece4d3a --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureGIF.cpp @@ -0,0 +1,851 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +// ========================================================== +// GIF Loader and Writer +// +// Design and implementation by +// - Ryan Rubley +// - Raphael Gaquer +// +// This file is part of FreeImage 3 +// +// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY +// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES +// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE +// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED +// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT +// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY +// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL +// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER +// THIS DISCLAIMER. +// +// Use at your own risk! +// ========================================================== +#ifdef _MSC_VER +#pragma warning(disable : 4786) // identifier was truncated to 'number' characters +#endif + +#include "Qt3DSRenderLoadedTextureFreeImageCompat.h" +#include "EASTL/vector.h" +#include "EASTL/string.h" +// ========================================================== +// Metadata declarations +// ========================================================== + +#define GIF_DISPOSAL_UNSPECIFIED 0 +#define GIF_DISPOSAL_LEAVE 1 +#define GIF_DISPOSAL_BACKGROUND 2 +#define GIF_DISPOSAL_PREVIOUS 3 + +// ========================================================== +// Constant/Typedef declarations +// ========================================================== + +struct GIFinfo +{ + BOOL read; + // only really used when reading + size_t global_color_table_offset; + int global_color_table_size; + BYTE background_color; + eastl::vector application_extension_offsets; + eastl::vector comment_extension_offsets; + eastl::vector graphic_control_extension_offsets; + eastl::vector image_descriptor_offsets; + + GIFinfo() + : read(0) + , global_color_table_offset(0) + , global_color_table_size(0) + , background_color(0) + { + } +}; + +struct PageInfo +{ + PageInfo(int d, int l, int t, int w, int h) + { + disposal_method = d; + left = (WORD)l; + top = (WORD)t; + width = (WORD)w; + height = (WORD)h; + } + int disposal_method; + WORD left, top, width, height; +}; + +// GIF defines a max of 12 bits per code +#define MAX_LZW_CODE 4096 + +class StringTable +{ +public: + StringTable(); + ~StringTable(); + void Initialize(int minCodeSize); + BYTE *FillInputBuffer(int len); + void CompressStart(int bpp, int width); + int CompressEnd(BYTE *buf); // 0-4 bytes + bool Compress(BYTE *buf, int *len); + bool Decompress(BYTE *buf, int *len); + void Done(void); + +protected: + bool m_done; + + int m_minCodeSize, m_clearCode, m_endCode, m_nextCode; + + int m_bpp, m_slack; // Compressor information + + int m_prefix; // Compressor state variable + int m_codeSize, m_codeMask; // Compressor/Decompressor state variables + int m_oldCode; // Decompressor state variable + int m_partial, m_partialSize; // Compressor/Decompressor bit buffer + + int firstPixelPassed; // A specific flag that indicates if the first pixel + // of the whole image had already been read + + eastl::string m_strings[MAX_LZW_CODE]; // This is what is really the "string table" data for the + // Decompressor + int *m_strmap; + + // input buffer + BYTE *m_buffer; + int m_bufferSize, m_bufferRealSize, m_bufferPos, m_bufferShift; + + void ClearCompressorTable(void); + void ClearDecompressorTable(void); +}; + +#define GIF_PACKED_LSD_HAVEGCT 0x80 +#define GIF_PACKED_LSD_COLORRES 0x70 +#define GIF_PACKED_LSD_GCTSORTED 0x08 +#define GIF_PACKED_LSD_GCTSIZE 0x07 +#define GIF_PACKED_ID_HAVELCT 0x80 +#define GIF_PACKED_ID_INTERLACED 0x40 +#define GIF_PACKED_ID_LCTSORTED 0x20 +#define GIF_PACKED_ID_RESERVED 0x18 +#define GIF_PACKED_ID_LCTSIZE 0x07 +#define GIF_PACKED_GCE_RESERVED 0xE0 +#define GIF_PACKED_GCE_DISPOSAL 0x1C +#define GIF_PACKED_GCE_WAITINPUT 0x02 +#define GIF_PACKED_GCE_HAVETRANS 0x01 + +#define GIF_BLOCK_IMAGE_DESCRIPTOR 0x2C +#define GIF_BLOCK_EXTENSION 0x21 +#define GIF_BLOCK_TRAILER 0x3B + +#define GIF_EXT_PLAINTEXT 0x01 +#define GIF_EXT_GRAPHIC_CONTROL 0xF9 +#define GIF_EXT_COMMENT 0xFE +#define GIF_EXT_APPLICATION 0xFF + +#define GIF_INTERLACE_PASSES 4 +static int g_GifInterlaceOffset[GIF_INTERLACE_PASSES] = { 0, 4, 2, 1 }; +static int g_GifInterlaceIncrement[GIF_INTERLACE_PASSES] = { 8, 8, 4, 2 }; + +StringTable::StringTable() +{ + m_buffer = NULL; + firstPixelPassed = 0; // Still no pixel read + // Maximum number of entries in the map is MAX_LZW_CODE * 256 + // (aka 2**12 * 2**8 => a 20 bits key) + // This Map could be optmized to only handle MAX_LZW_CODE * 2**(m_bpp) + m_strmap = (int *)new int[1 << 20]; +} + +StringTable::~StringTable() +{ + if (m_buffer != NULL) { + delete[] m_buffer; + } + if (m_strmap != NULL) { + delete[] m_strmap; + m_strmap = NULL; + } +} + +void StringTable::Initialize(int minCodeSize) +{ + m_done = false; + + m_bpp = 8; + m_minCodeSize = minCodeSize; + m_clearCode = 1 << m_minCodeSize; + if (m_clearCode > MAX_LZW_CODE) { + m_clearCode = MAX_LZW_CODE; + } + m_endCode = m_clearCode + 1; + + m_partial = 0; + m_partialSize = 0; + + m_bufferSize = 0; + ClearCompressorTable(); + ClearDecompressorTable(); +} + +BYTE *StringTable::FillInputBuffer(int len) +{ + if (m_buffer == NULL) { + m_buffer = new BYTE[len]; + m_bufferRealSize = len; + } else if (len > m_bufferRealSize) { + delete[] m_buffer; + m_buffer = new BYTE[len]; + m_bufferRealSize = len; + } + m_bufferSize = len; + m_bufferPos = 0; + m_bufferShift = 8 - m_bpp; + return m_buffer; +} + +void StringTable::CompressStart(int bpp, int width) +{ + m_bpp = bpp; + m_slack = (8 - ((width * bpp) % 8)) % 8; + + m_partial |= m_clearCode << m_partialSize; + m_partialSize += m_codeSize; + ClearCompressorTable(); +} + +int StringTable::CompressEnd(BYTE *buf) +{ + int len = 0; + + // output code for remaining prefix + m_partial |= m_prefix << m_partialSize; + m_partialSize += m_codeSize; + while (m_partialSize >= 8) { + *buf++ = (BYTE)m_partial; + m_partial >>= 8; + m_partialSize -= 8; + len++; + } + + // add the end of information code and flush the entire buffer out + m_partial |= m_endCode << m_partialSize; + m_partialSize += m_codeSize; + while (m_partialSize > 0) { + *buf++ = (BYTE)m_partial; + m_partial >>= 8; + m_partialSize -= 8; + len++; + } + + // most this can be is 4 bytes. 7 bits in m_partial to start + 12 for the + // last code + 12 for the end code = 31 bits total. + return len; +} + +bool StringTable::Compress(BYTE *buf, int *len) +{ + if (m_bufferSize == 0 || m_done) { + return false; + } + + int mask = (1 << m_bpp) - 1; + BYTE *bufpos = buf; + while (m_bufferPos < m_bufferSize) { + // get the current pixel value + char ch = (char)((m_buffer[m_bufferPos] >> m_bufferShift) & mask); + + // The next prefix is : + // | + int nextprefix = (((m_prefix) << 8) & 0xFFF00) + (ch & 0x000FF); + if (firstPixelPassed) { + + if (m_strmap[nextprefix] > 0) { + m_prefix = m_strmap[nextprefix]; + } else { + m_partial |= m_prefix << m_partialSize; + m_partialSize += m_codeSize; + // grab full bytes for the output buffer + while (m_partialSize >= 8 && bufpos - buf < *len) { + *bufpos++ = (BYTE)m_partial; + m_partial >>= 8; + m_partialSize -= 8; + } + + // add the code to the "table map" + m_strmap[nextprefix] = m_nextCode; + + // increment the next highest valid code, increase the code size + if (m_nextCode == (1 << m_codeSize)) { + m_codeSize++; + } + m_nextCode++; + + // if we're out of codes, restart the string table + if (m_nextCode == MAX_LZW_CODE) { + m_partial |= m_clearCode << m_partialSize; + m_partialSize += m_codeSize; + ClearCompressorTable(); + } + + // Only keep the 8 lowest bits (prevent problems with "negative chars") + m_prefix = ch & 0x000FF; + } + + // increment to the next pixel + if (m_bufferShift > 0 + && !(m_bufferPos + 1 == m_bufferSize && m_bufferShift <= m_slack)) { + m_bufferShift -= m_bpp; + } else { + m_bufferPos++; + m_bufferShift = 8 - m_bpp; + } + + // jump out here if the output buffer is full + if (bufpos - buf == *len) { + return true; + } + + } else { + // Specific behavior for the first pixel of the whole image + + firstPixelPassed = 1; + // Only keep the 8 lowest bits (prevent problems with "negative chars") + m_prefix = ch & 0x000FF; + + // increment to the next pixel + if (m_bufferShift > 0 + && !(m_bufferPos + 1 == m_bufferSize && m_bufferShift <= m_slack)) { + m_bufferShift -= m_bpp; + } else { + m_bufferPos++; + m_bufferShift = 8 - m_bpp; + } + + // jump out here if the output buffer is full + if (bufpos - buf == *len) { + return true; + } + } + } + + m_bufferSize = 0; + *len = (int)(bufpos - buf); + + return true; +} + +bool StringTable::Decompress(BYTE *buf, int *len) +{ + if (m_bufferSize == 0 || m_done) { + return false; + } + + BYTE *bufpos = buf; + for (; m_bufferPos < m_bufferSize; m_bufferPos++) { + m_partial |= (int)m_buffer[m_bufferPos] << m_partialSize; + m_partialSize += 8; + while (m_partialSize >= m_codeSize) { + int code = m_partial & m_codeMask; + m_partial >>= m_codeSize; + m_partialSize -= m_codeSize; + + if (code > m_nextCode || (m_nextCode == MAX_LZW_CODE && code != m_clearCode) + || code == m_endCode) { + m_done = true; + *len = (int)(bufpos - buf); + return true; + } + if (code == m_clearCode) { + ClearDecompressorTable(); + continue; + } + + // add new string to string table, if not the first pass since a clear code + if (m_oldCode != MAX_LZW_CODE) { + m_strings[m_nextCode] = + m_strings[m_oldCode] + m_strings[code == m_nextCode ? m_oldCode : code][0]; + } + + if ((int)m_strings[code].size() > *len - (bufpos - buf)) { + // out of space, stuff the code back in for next time + m_partial <<= m_codeSize; + m_partialSize += m_codeSize; + m_partial |= code; + m_bufferPos++; + *len = (int)(bufpos - buf); + return true; + } + + // output the string into the buffer + memcpy(bufpos, m_strings[code].data(), m_strings[code].size()); + bufpos += m_strings[code].size(); + + // increment the next highest valid code, add a bit to the mask if we need to increase + // the code size + if (m_oldCode != MAX_LZW_CODE && m_nextCode < MAX_LZW_CODE) { + if (++m_nextCode < MAX_LZW_CODE) { + if ((m_nextCode & m_codeMask) == 0) { + m_codeSize++; + m_codeMask |= m_nextCode; + } + } + } + + m_oldCode = code; + } + } + + m_bufferSize = 0; + *len = (int)(bufpos - buf); + + return true; +} + +void StringTable::Done(void) +{ + m_done = true; +} + +void StringTable::ClearCompressorTable(void) +{ + if (m_strmap) { + memset(m_strmap, 0xFF, sizeof(unsigned int) * (1 << 20)); + } + m_nextCode = m_endCode + 1; + + m_prefix = 0; + m_codeSize = m_minCodeSize + 1; +} + +void StringTable::ClearDecompressorTable(void) +{ + for (int i = 0; i < m_clearCode; i++) { + m_strings[i].resize(1); + m_strings[i][0] = (char)i; + } + m_nextCode = m_endCode + 1; + + m_codeSize = m_minCodeSize + 1; + m_codeMask = (1 << m_codeSize) - 1; + m_oldCode = MAX_LZW_CODE; +} + +// ========================================================== +// Plugin Interface +// ========================================================== + +static int s_format_id; + +// ========================================================== +// Plugin Implementation +// ========================================================== + +static BOOL DLL_CALLCONV Validate(FreeImageIO *io, fi_handle handle) +{ + char buf[6]; + if (io->read_proc(buf, 6, 1, handle) < 1) { + return FALSE; + } + + BOOL bResult = FALSE; + if (!strncmp(buf, "GIF", 3)) { + if (buf[3] >= '0' && buf[3] <= '9' && buf[4] >= '0' && buf[4] <= '9' && buf[5] >= 'a' + && buf[5] <= 'z') { + bResult = TRUE; + } + } + + io->seek_proc(handle, -6, SEEK_CUR); + + return bResult; +} + +// ---------------------------------------------------------- + +static void *DLL_CALLCONV Open(FreeImageIO *io, fi_handle handle) +{ + GIFinfo *info = new GIFinfo; + if (info == NULL) { + return NULL; + } + BOOL read = TRUE; + + // 25/02/2008 MDA: Not safe to memset GIFinfo structure with VS 2008 (safe iterators), + // perform initialization in constructor instead. + // memset(info, 0, sizeof(GIFinfo)); + + info->read = read; + try { + // Header + if (!Validate(io, handle)) { + throw "Not a GIF file"; + } + io->seek_proc(handle, 6, SEEK_CUR); + + // Logical Screen Descriptor + io->seek_proc(handle, 4, SEEK_CUR); + BYTE packed; + if (io->read_proc(&packed, 1, 1, handle) < 1) { + throw "EOF reading Logical Screen Descriptor"; + } + if (io->read_proc(&info->background_color, 1, 1, handle) < 1) { + throw "EOF reading Logical Screen Descriptor"; + } + io->seek_proc(handle, 1, SEEK_CUR); + + // Global Color Table + if (packed & GIF_PACKED_LSD_HAVEGCT) { + info->global_color_table_offset = io->tell_proc(handle); + info->global_color_table_size = 2 << (packed & GIF_PACKED_LSD_GCTSIZE); + io->seek_proc(handle, 3 * info->global_color_table_size, SEEK_CUR); + } + + // Scan through all the rest of the blocks, saving offsets + size_t gce_offset = 0; + BYTE block = 0; + while (block != GIF_BLOCK_TRAILER) { + if (io->read_proc(&block, 1, 1, handle) < 1) { + throw "EOF reading blocks"; + } + if (block == GIF_BLOCK_IMAGE_DESCRIPTOR) { + info->image_descriptor_offsets.push_back(io->tell_proc(handle)); + // GCE may be 0, meaning no GCE preceded this ID + info->graphic_control_extension_offsets.push_back(gce_offset); + gce_offset = 0; + + io->seek_proc(handle, 8, SEEK_CUR); + if (io->read_proc(&packed, 1, 1, handle) < 1) { + throw "EOF reading Image Descriptor"; + } + + // Local Color Table + if (packed & GIF_PACKED_ID_HAVELCT) { + io->seek_proc(handle, 3 * (2 << (packed & GIF_PACKED_ID_LCTSIZE)), SEEK_CUR); + } + + // LZW Minimum Code Size + io->seek_proc(handle, 1, SEEK_CUR); + } else if (block == GIF_BLOCK_EXTENSION) { + BYTE ext; + if (io->read_proc(&ext, 1, 1, handle) < 1) { + throw "EOF reading extension"; + } + + if (ext == GIF_EXT_GRAPHIC_CONTROL) { + // overwrite previous offset if more than one GCE found before an ID + gce_offset = io->tell_proc(handle); + } else if (ext == GIF_EXT_COMMENT) { + info->comment_extension_offsets.push_back(io->tell_proc(handle)); + } else if (ext == GIF_EXT_APPLICATION) { + info->application_extension_offsets.push_back(io->tell_proc(handle)); + } + } else if (block == GIF_BLOCK_TRAILER) { + continue; + } else { + throw "Invalid GIF block found"; + } + + // Data Sub-blocks + BYTE len; + if (io->read_proc(&len, 1, 1, handle) < 1) { + throw "EOF reading sub-block"; + } + while (len != 0) { + io->seek_proc(handle, len, SEEK_CUR); + if (io->read_proc(&len, 1, 1, handle) < 1) { + throw "EOF reading sub-block"; + } + } + } + } catch (const char *msg) { + FreeImage_OutputMessageProc(s_format_id, msg, io); + delete info; + return NULL; + } + + return info; +} + +static FIBITMAP *DLL_CALLCONV DoLoadGIF(FreeImageIO *io, fi_handle handle, int flags, void *data) +{ + if (data == NULL) { + return NULL; + } + (void)flags; + GIFinfo *info = (GIFinfo *)data; + + int page = 0; + FIBITMAP *dib = NULL; + try { + bool have_transparent = false, no_local_palette = false, interlaced = false; + int disposal_method = GIF_DISPOSAL_LEAVE, transparent_color = 0; + WORD left, top, width, height; + BYTE packed, b; + WORD w; + + // Image Descriptor + io->seek_proc(handle, (long)info->image_descriptor_offsets[page], SEEK_SET); + io->read_proc(&left, 2, 1, handle); + io->read_proc(&top, 2, 1, handle); + io->read_proc(&width, 2, 1, handle); + io->read_proc(&height, 2, 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapShort(&left); + SwapShort(&top); + SwapShort(&width); + SwapShort(&height); +#endif + io->read_proc(&packed, 1, 1, handle); + interlaced = (packed & GIF_PACKED_ID_INTERLACED) ? true : false; + no_local_palette = (packed & GIF_PACKED_ID_HAVELCT) ? false : true; + + int bpp = 8; + if (!no_local_palette) { + int size = 2 << (packed & GIF_PACKED_ID_LCTSIZE); + if (size <= 2) + bpp = 1; + else if (size <= 16) + bpp = 4; + } else if (info->global_color_table_offset != 0) { + if (info->global_color_table_size <= 2) + bpp = 1; + else if (info->global_color_table_size <= 16) + bpp = 4; + } + dib = FreeImage_Allocate(width, height, bpp, io); + if (dib == NULL) { + throw "DIB allocated failed"; + } + + // Palette + RGBQUAD *pal = FreeImage_GetPalette(dib); + if (!no_local_palette) { + int size = 2 << (packed & GIF_PACKED_ID_LCTSIZE); + + int i = 0; + while (i < size) { + io->read_proc(&pal[i].rgbRed, 1, 1, handle); + io->read_proc(&pal[i].rgbGreen, 1, 1, handle); + io->read_proc(&pal[i].rgbBlue, 1, 1, handle); + i++; + } + } else if (info->global_color_table_offset != 0) { + long pos = io->tell_proc(handle); + io->seek_proc(handle, (long)info->global_color_table_offset, SEEK_SET); + + int i = 0; + while (i < info->global_color_table_size) { + io->read_proc(&pal[i].rgbRed, 1, 1, handle); + io->read_proc(&pal[i].rgbGreen, 1, 1, handle); + io->read_proc(&pal[i].rgbBlue, 1, 1, handle); + i++; + } + + io->seek_proc(handle, pos, SEEK_SET); + } else { + // its legal to have no palette, but we're going to generate *something* + for (int i = 0; i < 256; i++) { + pal[i].rgbRed = (BYTE)i; + pal[i].rgbGreen = (BYTE)i; + pal[i].rgbBlue = (BYTE)i; + } + } + + // LZW Minimum Code Size + io->read_proc(&b, 1, 1, handle); + StringTable *stringtable = new StringTable; + stringtable->Initialize(b); + + // Image Data Sub-blocks + int x = 0, xpos = 0, y = 0, shift = 8 - bpp, mask = (1 << bpp) - 1, interlacepass = 0; + BYTE *scanline = FreeImage_GetScanLine(dib, height - 1); + BYTE buf[4096]; + io->read_proc(&b, 1, 1, handle); + while (b) { + io->read_proc(stringtable->FillInputBuffer(b), b, 1, handle); + int size = sizeof(buf); + while (stringtable->Decompress(buf, &size)) { + for (int i = 0; i < size; i++) { + scanline[xpos] |= (buf[i] & mask) << shift; + if (shift > 0) { + shift -= bpp; + } else { + xpos++; + shift = 8 - bpp; + } + if (++x >= width) { + if (interlaced) { + y += g_GifInterlaceIncrement[interlacepass]; + if (y >= height && ++interlacepass < GIF_INTERLACE_PASSES) { + y = g_GifInterlaceOffset[interlacepass]; + } + } else { + y++; + } + if (y >= height) { + stringtable->Done(); + break; + } + x = xpos = 0; + shift = 8 - bpp; + scanline = FreeImage_GetScanLine(dib, height - y - 1); + } + } + size = sizeof(buf); + } + io->read_proc(&b, 1, 1, handle); + } + + if (page == 0) { + QT3DSU32 idx; + + // Logical Screen Descriptor + io->seek_proc(handle, 6, SEEK_SET); + WORD logicalwidth, logicalheight; + io->read_proc(&logicalwidth, 2, 1, handle); + io->read_proc(&logicalheight, 2, 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapShort(&logicalwidth); + SwapShort(&logicalheight); +#endif + + // Global Color Table + if (info->global_color_table_offset != 0) { + RGBQUAD globalpalette[256]; + io->seek_proc(handle, (long)info->global_color_table_offset, SEEK_SET); + int i = 0; + while (i < info->global_color_table_size) { + io->read_proc(&globalpalette[i].rgbRed, 1, 1, handle); + io->read_proc(&globalpalette[i].rgbGreen, 1, 1, handle); + io->read_proc(&globalpalette[i].rgbBlue, 1, 1, handle); + globalpalette[i].rgbReserved = 0; + i++; + } + // background color + if (info->background_color < info->global_color_table_size) { + FreeImage_SetBackgroundColor(dib, &globalpalette[info->background_color]); + } + } + + // Application Extension + LONG loop = 1; // If no AE with a loop count is found, the default must be 1 + for (idx = 0; idx < info->application_extension_offsets.size(); idx++) { + io->seek_proc(handle, (long)info->application_extension_offsets[idx], SEEK_SET); + io->read_proc(&b, 1, 1, handle); + if (b == 11) { // All AEs start with an 11 byte sub-block to determine what type of + // AE it is + char buf[11]; + io->read_proc(buf, 11, 1, handle); + if (!memcmp(buf, "NETSCAPE2.0", 11) + || !memcmp(buf, "ANIMEXTS1.0", + 11)) { // Not everybody recognizes ANIMEXTS1.0 but it is valid + io->read_proc(&b, 1, 1, handle); + if (b == 3) { // we're supposed to have a 3 byte sub-block now + io->read_proc(&b, 1, 1, + handle); // this should be 0x01 but isn't really important + io->read_proc(&w, 2, 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapShort(&w); +#endif + loop = w; + if (loop > 0) + loop++; + break; + } + } + } + } + } + + // Graphic Control Extension + if (info->graphic_control_extension_offsets[page] != 0) { + io->seek_proc(handle, (long)(info->graphic_control_extension_offsets[page] + 1), + SEEK_SET); + io->read_proc(&packed, 1, 1, handle); + io->read_proc(&w, 2, 1, handle); +#ifdef FREEIMAGE_BIGENDIAN + SwapShort(&w); +#endif + io->read_proc(&b, 1, 1, handle); + have_transparent = (packed & GIF_PACKED_GCE_HAVETRANS) ? true : false; + disposal_method = (packed & GIF_PACKED_GCE_DISPOSAL) >> 2; + + transparent_color = b; + if (have_transparent) { + int size = 1 << bpp; + if (transparent_color <= size) { + BYTE table[256]; + memset(table, 0xFF, size); + table[transparent_color] = 0; + FreeImage_SetTransparencyTable(dib, table, size); + dib->m_TransparentPaletteIndex = b; + } + } + } + b = (BYTE)disposal_method; + + delete stringtable; + + } catch (const char *msg) { + if (dib != NULL) { + FreeImage_Unload(dib); + } + FreeImage_OutputMessageProc(s_format_id, msg, io); + return NULL; + } + + return dib; +} + +static void DLL_CALLCONV Close(void *data) +{ + if (data == NULL) { + return; + } + GIFinfo *info = (GIFinfo *)data; + delete info; +} + +SLoadedTexture *SLoadedTexture::LoadGIF(ISeekableIOStream &inStream, bool inFlipY, + NVFoundationBase &inFnd, + qt3ds::render::NVRenderContextType renderContextType) +{ + Q_UNUSED(renderContextType) + FreeImageIO theIO(inFnd.getAllocator(), inFnd); + void *gifData = Open(&theIO, &inStream); + if (gifData) { + SLoadedTexture *retval = DoLoadGIF(&theIO, &inStream, 0, gifData); + Close(gifData); + if (retval) + retval->FreeImagePostProcess(inFlipY); + return retval; + } + return NULL; +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureHDR.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureHDR.cpp new file mode 100644 index 0000000..defff29 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureHDR.cpp @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2014 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$ +** +****************************************************************************/ +// ========================================================== +// Radiance RGBE .HDR Loader +// Decodes Radiance RGBE HDR image into FP16 texture buffer. +// +// Implementation by +// Parashar Krishnamachari (parashark@nvidia.com) +// +// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY +// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES +// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE +// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED +// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT +// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY +// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL +// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER +// THIS DISCLAIMER. +// +// Use at your own risk! +// ========================================================== + +#include "Qt3DSRenderLoadedTextureFreeImageCompat.h" +#include "render/Qt3DSRenderBaseTypes.h" + +typedef unsigned char RGBE[4]; +#define R 0 +#define G 1 +#define B 2 +#define E 3 + +#define MINELEN 8 // minimum scanline length for encoding +#define MAXELEN 0x7fff // maximum scanline length for encoding + +static int s_format_id; + +static float convertComponent(int exponent, int val) +{ + float v = val / (256.0f); + float d = powf(2.0f, (float)exponent - 128.0f); + return v * d; +} + +static void decrunchScanlineOld(FreeImageIO *io, fi_handle handle, RGBE *scanline, int width) +{ + int i; + int rshift = 0; + + while (width > 0) { + io->read_proc(scanline, 4, 1, handle); + + // The older version of RLE encodes the length in the exponent. + // and marks a run with 1, 1, 1 in RGB. This is differentiated from + // a raw value of 1, 1, 1, by having a exponent of 0; + if (scanline[0][R] == 1 && scanline[0][G] == 1 && scanline[0][B] == 1) { + for (i = (scanline[0][E] << rshift); i > 0; --i) { + memcpy(&scanline[0][0], &scanline[-1][0], 4); + ++scanline; + --width; + } + rshift += 8; + } else { + ++scanline; + --width; + rshift = 0; + } + } +} + +static void decrunchScanline(FreeImageIO *io, fi_handle handle, RGBE *scanline, int width) +{ + if ((width < MINELEN) || (width > MAXELEN)) { + decrunchScanlineOld(io, handle, scanline, width); + return; + } + + char c; + io->read_proc(&c, 1, 1, handle); + if (c != 2) { + io->seek_proc(handle, -1, SEEK_CUR); + decrunchScanlineOld(io, handle, scanline, width); + return; + } + + io->read_proc(&(scanline[0][G]), 1, 1, handle); + io->read_proc(&(scanline[0][B]), 1, 1, handle); + io->read_proc(&c, 1, 1, handle); + + if (scanline[0][G] != 2 || scanline[0][B] & 128) { + scanline[0][R] = 2; + scanline[0][E] = c; + decrunchScanlineOld(io, handle, scanline + 1, width - 1); + } + + // RLE-encoded version does a separate buffer for each channel per scanline + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < width;) { + unsigned char code, val; + io->read_proc(&code, 1, 1, handle); + if (code + > 128) // RLE-encoded run... read 1 value and copy it forward for some n count. + { + code &= 127; + io->read_proc(&val, 1, 1, handle); + while (code--) + scanline[j++][i] = val; + } else // Not a run, so we read it as raw data + { + // Note -- we store each pixel in memory 4 bytes apart, so we can't just + // do one long read. + while (code--) + io->read_proc(&(scanline[j++][i]), 1, 1, handle); + } + } + } +} + +static void decodeScanlineToTexture(RGBE *scanline, int width, void *outBuf, QT3DSU32 offset, + NVRenderTextureFormats::Enum inFormat) +{ + float rgbaF32[4]; + + for (int i = 0; i < width; ++i) { + rgbaF32[R] = convertComponent(scanline[i][E], scanline[i][R]); + rgbaF32[G] = convertComponent(scanline[i][E], scanline[i][G]); + rgbaF32[B] = convertComponent(scanline[i][E], scanline[i][B]); + rgbaF32[3] = 1.0f; + + QT3DSU8 *target = reinterpret_cast(outBuf); + target += offset; + NVRenderTextureFormats::encodeToPixel( + rgbaF32, target, i * NVRenderTextureFormats::getSizeofFormat(inFormat), inFormat); + } +} + +static FIBITMAP *DoLoadHDR(FreeImageIO *io, fi_handle handle, + NVRenderTextureFormats::Enum inFormat = NVRenderTextureFormats::RGB32F) +{ + FIBITMAP *dib = NULL; + try { + if (handle != NULL) { + char str[200]; + int i; + + // Make sure it's a Radiance RGBE file + io->read_proc(str, 10, 1, handle); + if (memcmp(str, "#?RADIANCE", 10)) { + throw "Invalid HDR file"; + } + + io->seek_proc(handle, 1, SEEK_CUR); + + // Get the command string (it's not really important for us; We're always assuming + // 32bit_rle_rgbe is the format + // we're just reading it to skip ahead the correct number of bytes). + i = 0; + char c = 0, prevC; + do { + prevC = c; + io->read_proc(&c, 1, 1, handle); + str[i++] = c; + } while (!(c == 0xa && prevC == 0xa)); + + // Get the resolution string (it will be NULL-terminated for us) + char res[200]; + i = 0; + do { + io->read_proc(&c, 1, 1, handle); + res[i++] = c; + } while (c != 0xa); + res[i] = 0; + + int width, height; + if (!sscanf(res, "-Y %d +X %d", &height, &width)) { + throw "Error encountered while loading HDR stream : could not determine image " + "resolution!"; + } + int bytesPerPixel = NVRenderTextureFormats::getSizeofFormat(inFormat); + dib = FreeImage_Allocate(width, height, bytesPerPixel * 8, io); + if (dib == NULL) { + throw "DIB allocation failed"; + } + + dib->format = inFormat; + dib->components = NVRenderTextureFormats::getNumberOfComponent(inFormat); + + // Allocate a scanline worth of RGBE data + RGBE *scanline = new RGBE[width]; + if (!scanline) { + throw "Error encountered while loading HDR stream : could not buffer scanlines!"; + } + + // Go through all the scanlines + for (int y = 0; y < height; ++y) { + QT3DSU32 byteOfs = (height - 1 - y) * width * bytesPerPixel; + decrunchScanline(io, handle, scanline, width); + decodeScanlineToTexture(scanline, width, dib->data, byteOfs, inFormat); + } + } + return dib; + } catch (const char *message) { + if (dib) { + FreeImage_Unload(dib); + } + if (message) { + FreeImage_OutputMessageProc(s_format_id, message, io); + } + } + + return NULL; +} + +SLoadedTexture *SLoadedTexture::LoadHDR(ISeekableIOStream &inStream, NVFoundationBase &inFnd, + qt3ds::render::NVRenderContextType renderContextType) +{ + FreeImageIO theIO(inFnd.getAllocator(), inFnd); + SLoadedTexture *retval = nullptr; + if (renderContextType == qt3ds::render::NVRenderContextValues::GLES2) + retval = DoLoadHDR(&theIO, &inStream, NVRenderTextureFormats::RGBA8); + else + retval = DoLoadHDR(&theIO, &inStream, NVRenderTextureFormats::RGBA16F); + + + // Let's just assume we don't support this just yet. + // if ( retval ) + // retval->FreeImagePostProcess( inFlipY ); + return retval; +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.cpp new file mode 100644 index 0000000..b1d4b05 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "foundation/Qt3DSFoundation.h" +#include "foundation/IOStreams.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "Qt3DSRenderLoadedTexture.h" +#include "Qt3DSRenderLoadedTextureKTX.h" +#include "Qt3DSRenderLoadedTextureDDS.h" + +#include +#include + +using namespace qt3ds::render; +using namespace qt3ds::foundation; + +namespace qt3ds { +namespace render { + +static inline int blockSizeForTextureFormat(int format) +{ + switch (format) { + case QOpenGLTexture::RGB8_ETC1: + case QOpenGLTexture::RGB8_ETC2: + case QOpenGLTexture::SRGB8_ETC2: + case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: + case QOpenGLTexture::SRGB8_PunchThrough_Alpha1_ETC2: + case QOpenGLTexture::R11_EAC_UNorm: + case QOpenGLTexture::R11_EAC_SNorm: + case QOpenGLTexture::RGB_DXT1: + return 8; + + default: + return 16; + } +} + +static inline int runtimeFormat(quint32 internalFormat) +{ + switch (internalFormat) { + case QOpenGLTexture::RGB8_ETC1: + return NVRenderTextureFormats::RGB8_ETC1; + case QOpenGLTexture::RGB8_ETC2: + return NVRenderTextureFormats::RGB8_ETC2; + case QOpenGLTexture::SRGB8_ETC2: + return NVRenderTextureFormats::SRGB8_ETC2; + case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: + return NVRenderTextureFormats::RGB8_PunchThrough_Alpha1_ETC2; + case QOpenGLTexture::SRGB8_PunchThrough_Alpha1_ETC2: + return NVRenderTextureFormats::SRGB8_PunchThrough_Alpha1_ETC2; + case QOpenGLTexture::R11_EAC_UNorm: + return NVRenderTextureFormats::R11_EAC_UNorm; + case QOpenGLTexture::R11_EAC_SNorm: + return NVRenderTextureFormats::R11_EAC_SNorm; + case QOpenGLTexture::RGB_DXT1: + return NVRenderTextureFormats::RGB_DXT1; + case QOpenGLTexture::RGBA_DXT1: + return NVRenderTextureFormats::RGBA_DXT3; + case QOpenGLTexture::RGBA_DXT3: + return NVRenderTextureFormats::RGBA_DXT3; + case QOpenGLTexture::RGBA_DXT5: + return NVRenderTextureFormats::RGBA_DXT5; + default: + break; + } + return NVRenderTextureFormats::Unknown; +} + +static inline int imageSize(QT3DSI32 width, QT3DSI32 height, const Qt3DSDDSImage *image) +{ + return ((width + 3) / 4) * ((height + 3) / 4) + * blockSizeForTextureFormat(image->internalFormat); +} + +static inline quint32 totalImageDataSize(Qt3DSDDSImage *image) +{ + int i, j; + int index = 0; + quint32 size = 0; + int w, h; + int cubeCount = image->cubemap ? 6 : 1; + + for (j = 0; j < cubeCount; j++) { + w = image->width; + h = image->height; + + for (i = 0; i < image->numMipmaps; i++) // account for base plus each mip + { + size += 4; // image size is saved in the file + image->size[index] = imageSize(w, h, image); + image->mipwidth[index] = w; + image->mipheight[index] = h; + size += quint32(image->size[index]); + if (w != 1) + w >>= 1; + if (h != 1) + h >>= 1; + + index++; + } + } + + return (size); +} + +static inline quint32 alignedOffset(quint32 offset, quint32 byteAlign) +{ + return (offset + byteAlign - 1) & ~(byteAlign - 1); +} + +inline SLoadedTexture *loadKtx(NVAllocatorCallback &allocator, IInStream &inStream, + QT3DSI32 flipVertical) +{ + static const int KTX_IDENTIFIER_LENGTH = 12; + static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = { + '\xAB', 'K', 'T', 'X', ' ', '1', '1', '\xBB', '\r', '\n', '\x1A', '\n' + }; + static const quint32 platformEndianIdentifier = 0x04030201; + static const quint32 inversePlatformEndianIdentifier = 0x01020304; + + struct KTXHeader { + quint8 identifier[KTX_IDENTIFIER_LENGTH]; + quint32 endianness; + quint32 glType; + quint32 glTypeSize; + quint32 glFormat; + quint32 glInternalFormat; + quint32 glBaseInternalFormat; + quint32 pixelWidth; + quint32 pixelHeight; + quint32 pixelDepth; + quint32 numberOfArrayElements; + quint32 numberOfFaces; + quint32 numberOfMipmapLevels; + quint32 bytesOfKeyValueData; + }; + + KTXHeader header; + if (inStream.Read(header) != sizeof(header) + || qstrncmp(reinterpret_cast(header.identifier), + ktxIdentifier, KTX_IDENTIFIER_LENGTH) != 0 + || (header.endianness != platformEndianIdentifier + && header.endianness != inversePlatformEndianIdentifier)) { + return nullptr; + } + + const bool isInverseEndian = (header.endianness == inversePlatformEndianIdentifier); + auto decode = [isInverseEndian](quint32 val) { + return isInverseEndian ? qbswap(val) : val; + }; + + const bool isCompressed = decode(header.glType) == 0 && decode(header.glFormat) == 0 + && decode(header.glTypeSize) == 1; + if (!isCompressed) { + qWarning("Uncompressed ktx texture data is not supported"); + return nullptr; + } + + if (decode(header.numberOfArrayElements) != 0) { + qWarning("Array ktx textures not supported"); + return nullptr; + } + + if (decode(header.pixelDepth) != 0) { + qWarning("Only 2D and cube ktx textures are supported"); + return nullptr; + } + + const int bytesToSkip = int(decode(header.bytesOfKeyValueData)); + QVector skipData; + skipData.resize(bytesToSkip); + if (inStream.Read(NVDataRef(skipData.data(), bytesToSkip)) != bytesToSkip) { + qWarning("Unexpected end of ktx data"); + return nullptr; + } + + // now for each mipmap level we have (arrays and 3d textures not supported here) + // uint32 imageSize + // for each array element + // for each face + // for each z slice + // compressed data + // padding so that each face data starts at an offset that is a multiple of 4 + // padding so that each imageSize starts at an offset that is a multiple of 4 + + Qt3DSDDSImage *image = (Qt3DSDDSImage *)QT3DS_ALLOC(allocator, sizeof(Qt3DSDDSImage), "DoLoadDDS"); + + const quint32 level0Width = decode(header.pixelWidth); + const quint32 level0Height = decode(header.pixelHeight); + quint32 faceCount = decode(header.numberOfFaces); + const quint32 mipMapLevels = decode(header.numberOfMipmapLevels); + const quint32 format = decode(header.glInternalFormat); + image->numMipmaps = int(mipMapLevels); + image->cubemap = faceCount == 6 ? 6 : 0; + image->internalFormat = int(format); + image->format = runtimeFormat(format); + image->width = int(level0Width); + image->height = int(level0Height); + image->compressed = 1; + quint32 totalSize = totalImageDataSize(image); + image->dataBlock = QT3DS_ALLOC(allocator, totalSize, "Qt3DSDDSAllocDataBlock"); + if (inStream.Read(NVDataRef(reinterpret_cast(image->dataBlock), totalSize)) + != totalSize) { + QT3DS_FREE(allocator, image); + return nullptr; + } + + SLoadedTexture *result = QT3DS_NEW(allocator, SLoadedTexture)(allocator); + result->dds = image; + result->width = int(level0Width); + result->height = int(level0Height); + result->format = static_cast(image->format); + + // TODO: Proper support for cubemaps should be implemented at some point. + if (faceCount > 1) { + qWarning("Multiple faces (cubemaps) not currently supported in ktx"); + faceCount = 1; + } + + uint8_t *p = reinterpret_cast(image->dataBlock); + uint8_t *basep = p; + + for (quint32 mip = 0; mip < mipMapLevels; ++mip) { + if (p + 4 - basep > totalSize) + break; + const quint32 imageSize = *reinterpret_cast(p); + p += 4; + for (quint32 face = 0; face < faceCount; ++face) { + const quint32 nextOffset = quint32(p + imageSize - basep); + if (nextOffset > totalSize) + break; + image->data[mip] = reinterpret_cast(p); + p = basep + alignedOffset(nextOffset, 4); + } + } + + return result; +} + +SLoadedTexture *SLoadedTexture::LoadKTX(IInStream &inStream, QT3DSI32 flipVertical, + NVFoundationBase &inFnd, + qt3ds::render::NVRenderContextType renderContextType) +{ + Q_UNUSED(renderContextType) + SLoadedTexture *retval = loadKtx(inFnd.getAllocator(), inStream, flipVertical); + + return retval; +} + +} +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.h b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.h new file mode 100644 index 0000000..3a2d762 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderLoadedTextureKTX.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QT3DS_RENDER_LOAD_KTX_H +#define QT3DS_RENDER_LOAD_KTX_H + +namespace qt3ds { +namespace render { + +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.cpp new file mode 100644 index 0000000..023964f --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.cpp @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2016 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 "Qt3DSRenderPrefilterTexture.h" +#include "render/Qt3DSRenderContext.h" +#include "render/Qt3DSRenderShaderProgram.h" + +#include + +using namespace qt3ds; +using namespace qt3ds::render; +using namespace qt3ds::foundation; + +Qt3DSRenderPrefilterTexture::Qt3DSRenderPrefilterTexture(NVRenderContext *inNVRenderContext, + QT3DSI32 inWidth, QT3DSI32 inHeight, + NVRenderTexture2D &inTexture2D, + NVRenderTextureFormats::Enum inDestFormat, + NVFoundationBase &inFnd) + : m_Foundation(inFnd) + , mRefCount(0) + , m_Texture2D(inTexture2D) + , m_DestinationFormat(inDestFormat) + , m_Width(inWidth) + , m_Height(inHeight) + , m_NVRenderContext(inNVRenderContext) +{ + // Calculate mip level + int maxDim = inWidth >= inHeight ? inWidth : inHeight; + + m_MaxMipMapLevel = static_cast(logf((float)maxDim) / logf(2.0f)); + // no concept of sizeOfFormat just does'nt make sense + m_SizeOfFormat = NVRenderTextureFormats::getSizeofFormat(m_DestinationFormat); + m_NoOfComponent = NVRenderTextureFormats::getNumberOfComponent(m_DestinationFormat); +} + +Qt3DSRenderPrefilterTexture * +Qt3DSRenderPrefilterTexture::Create(NVRenderContext *inNVRenderContext, QT3DSI32 inWidth, QT3DSI32 inHeight, + NVRenderTexture2D &inTexture2D, + NVRenderTextureFormats::Enum inDestFormat, + qt3ds::NVFoundationBase &inFnd) +{ + Qt3DSRenderPrefilterTexture *theBSDFMipMap = NULL; + + if (inNVRenderContext->IsComputeSupported()) { + theBSDFMipMap = QT3DS_NEW(inFnd.getAllocator(), Qt3DSRenderPrefilterTextureCompute)( + inNVRenderContext, inWidth, inHeight, inTexture2D, inDestFormat, inFnd); + } + + if (!theBSDFMipMap) { + theBSDFMipMap = QT3DS_NEW(inFnd.getAllocator(), Qt3DSRenderPrefilterTextureCPU)( + inNVRenderContext, inWidth, inHeight, inTexture2D, inDestFormat, inFnd); + } + + if (theBSDFMipMap) + theBSDFMipMap->addRef(); + + return theBSDFMipMap; +} + +Qt3DSRenderPrefilterTexture::~Qt3DSRenderPrefilterTexture() +{ +} + +//------------------------------------------------------------------------------------ +// CPU based filtering +//------------------------------------------------------------------------------------ + +Qt3DSRenderPrefilterTextureCPU::Qt3DSRenderPrefilterTextureCPU( + NVRenderContext *inNVRenderContext, int inWidth, int inHeight, NVRenderTexture2D &inTexture2D, + NVRenderTextureFormats::Enum inDestFormat, NVFoundationBase &inFnd) + : Qt3DSRenderPrefilterTexture(inNVRenderContext, inWidth, inHeight, inTexture2D, inDestFormat, + inFnd) +{ +} + +inline int Qt3DSRenderPrefilterTextureCPU::wrapMod(int a, int base) +{ + return (a >= 0) ? a % base : (a % base) + base; +} + +inline void Qt3DSRenderPrefilterTextureCPU::getWrappedCoords(int &sX, int &sY, int width, int height) +{ + if (sY < 0) { + sX -= width >> 1; + sY = -sY; + } + if (sY >= height) { + sX += width >> 1; + sY = height - sY; + } + sX = wrapMod(sX, width); +} + +STextureData +Qt3DSRenderPrefilterTextureCPU::CreateBsdfMipLevel(STextureData &inCurMipLevel, + STextureData &inPrevMipLevel, int width, + int height) //, IPerfTimer& inPerfTimer ) +{ + STextureData retval; + int newWidth = width >> 1; + int newHeight = height >> 1; + newWidth = newWidth >= 1 ? newWidth : 1; + newHeight = newHeight >= 1 ? newHeight : 1; + + if (inCurMipLevel.data) { + retval = inCurMipLevel; + retval.dataSizeInBytes = + newWidth * newHeight * NVRenderTextureFormats::getSizeofFormat(inPrevMipLevel.format); + } else { + retval.dataSizeInBytes = + newWidth * newHeight * NVRenderTextureFormats::getSizeofFormat(inPrevMipLevel.format); + retval.format = inPrevMipLevel.format; // inLoadedImage.format; + retval.data = m_Foundation.getAllocator().allocate( + retval.dataSizeInBytes, "Bsdf Scaled Image Data", __FILE__, __LINE__); + } + + for (int y = 0; y < newHeight; ++y) { + for (int x = 0; x < newWidth; ++x) { + float accumVal[4]; + accumVal[0] = 0; + accumVal[1] = 0; + accumVal[2] = 0; + accumVal[3] = 0; + for (int sy = -2; sy <= 2; ++sy) { + for (int sx = -2; sx <= 2; ++sx) { + int sampleX = sx + (x << 1); + int sampleY = sy + (y << 1); + getWrappedCoords(sampleX, sampleY, width, height); + + // Cauchy filter (this is simply because it's the easiest to evaluate, and + // requires no complex + // functions). + float filterPdf = 1.f / (1.f + float(sx * sx + sy * sy) * 2.f); + // With FP HDR formats, we're not worried about intensity loss so much as + // unnecessary energy gain, + // whereas with LDR formats, the fear with a continuous normalization factor is + // that we'd lose + // intensity and saturation as well. + filterPdf /= (NVRenderTextureFormats::getSizeofFormat(retval.format) >= 8) + ? 4.71238898f + : 4.5403446f; + // filterPdf /= 4.5403446f; // Discrete normalization factor + // filterPdf /= 4.71238898f; // Continuous normalization factor + float curPix[4]; + QT3DSI32 byteOffset = (sampleY * width + sampleX) + * NVRenderTextureFormats::getSizeofFormat(retval.format); + if (byteOffset < 0) { + sampleY = height + sampleY; + byteOffset = (sampleY * width + sampleX) + * NVRenderTextureFormats::getSizeofFormat(retval.format); + } + + NVRenderTextureFormats::decodeToFloat(inPrevMipLevel.data, byteOffset, curPix, + retval.format); + + accumVal[0] += filterPdf * curPix[0]; + accumVal[1] += filterPdf * curPix[1]; + accumVal[2] += filterPdf * curPix[2]; + accumVal[3] += filterPdf * curPix[3]; + } + } + + QT3DSU32 newIdx = + (y * newWidth + x) * NVRenderTextureFormats::getSizeofFormat(retval.format); + + NVRenderTextureFormats::encodeToPixel(accumVal, retval.data, newIdx, retval.format); + } + } + + return retval; +} + +void Qt3DSRenderPrefilterTextureCPU::Build(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat) +{ + + m_InternalFormat = inFormat; + m_SizeOfInternalFormat = NVRenderTextureFormats::getSizeofFormat(m_InternalFormat); + m_InternalNoOfComponent = NVRenderTextureFormats::getNumberOfComponent(m_InternalFormat); + + m_Texture2D.SetTextureData(NVDataRef((QT3DSU8 *)inTextureData, inTextureDataSize), 0, + m_Width, m_Height, inFormat, m_DestinationFormat); + + STextureData theMipImage; + STextureData prevImage; + prevImage.data = inTextureData; + prevImage.dataSizeInBytes = inTextureDataSize; + prevImage.format = inFormat; + int curWidth = m_Width; + int curHeight = m_Height; + int size = NVRenderTextureFormats::getSizeofFormat(m_InternalFormat); + for (int idx = 1; idx <= m_MaxMipMapLevel; ++idx) { + theMipImage = + CreateBsdfMipLevel(theMipImage, prevImage, curWidth, curHeight); //, m_PerfTimer ); + curWidth = curWidth >> 1; + curHeight = curHeight >> 1; + curWidth = curWidth >= 1 ? curWidth : 1; + curHeight = curHeight >= 1 ? curHeight : 1; + inTextureDataSize = curWidth * curHeight * size; + + m_Texture2D.SetTextureData(toU8DataRef((char *)theMipImage.data, (QT3DSU32)inTextureDataSize), + (QT3DSU8)idx, (QT3DSU32)curWidth, (QT3DSU32)curHeight, theMipImage.format, + m_DestinationFormat); + + if (prevImage.data == inTextureData) + prevImage = STextureData(); + + STextureData temp = prevImage; + prevImage = theMipImage; + theMipImage = temp; + } + QT3DS_FREE(m_Foundation.getAllocator(), theMipImage.data); + QT3DS_FREE(m_Foundation.getAllocator(), prevImage.data); +} + +//------------------------------------------------------------------------------------ +// GL compute based filtering +//------------------------------------------------------------------------------------ + +static const char *computeUploadShader(std::string &prog, NVRenderTextureFormats::Enum inFormat, + bool binESContext) +{ + if (binESContext) { + prog += "#version 310 es\n" + "#extension GL_ARB_compute_shader : enable\n" + "precision highp float;\n" + "precision highp int;\n" + "precision mediump image2D;\n"; + } else { + prog += "#version 430\n" + "#extension GL_ARB_compute_shader : enable\n"; + } + + if (inFormat == NVRenderTextureFormats::RGBA8) { + prog += "// Set workgroup layout;\n" + "layout (local_size_x = 16, local_size_y = 16) in;\n\n" + "layout (rgba8, binding = 1) readonly uniform image2D inputImage;\n\n" + "layout (rgba16f, binding = 2) writeonly uniform image2D outputImage;\n\n" + "void main()\n" + "{\n" + " if ( gl_GlobalInvocationID.x >= gl_NumWorkGroups.x || gl_GlobalInvocationID.y " + ">= gl_NumWorkGroups.y )\n" + " return;\n" + " vec4 value = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.xy));\n" + " imageStore( outputImage, ivec2(gl_GlobalInvocationID.xy), value );\n" + "}\n"; + } else { + prog += "float convertToFloat( in uint inValue )\n" + "{\n" + " uint v = inValue & uint(0xFF);\n" + " float f = float(v)/256.0;\n" + " return f;\n" + "}\n"; + + prog += "int getMod( in int inValue, in int mod )\n" + "{\n" + " int v = mod * (inValue/mod);\n" + " return inValue - v;\n" + "}\n"; + + prog += "vec4 getRGBValue( in int byteNo, vec4 inVal, vec4 inVal1 )\n" + "{\n" + " vec4 result= vec4(0.0);\n" + " if( byteNo == 0) {\n" + " result.r = inVal.r;\n" + " result.g = inVal.g;\n" + " result.b = inVal.b;\n" + " }\n" + " else if( byteNo == 1) {\n" + " result.r = inVal.g;\n" + " result.g = inVal.b;\n" + " result.b = inVal.a;\n" + " }\n" + " else if( byteNo == 2) {\n" + " result.r = inVal.b;\n" + " result.g = inVal.a;\n" + " result.b = inVal1.r;\n" + " }\n" + " else if( byteNo == 3) {\n" + " result.r = inVal.a;\n" + " result.g = inVal1.r;\n" + " result.b = inVal1.g;\n" + " }\n" + " return result;\n" + "}\n"; + + prog += "// Set workgroup layout;\n" + "layout (local_size_x = 16, local_size_y = 16) in;\n\n" + "layout (rgba8, binding = 1) readonly uniform image2D inputImage;\n\n" + "layout (rgba16f, binding = 2) writeonly uniform image2D outputImage;\n\n" + "void main()\n" + "{\n" + " vec4 result = vec4(0.0);\n" + " if ( gl_GlobalInvocationID.x >= gl_NumWorkGroups.x || gl_GlobalInvocationID.y " + ">= gl_NumWorkGroups.y )\n" + " return;\n" + " int xpos = (int(gl_GlobalInvocationID.x)*3)/4;\n" + " int xmod = getMod(int(gl_GlobalInvocationID.x)*3, 4);\n" + " ivec2 readPos = ivec2(xpos, gl_GlobalInvocationID.y);\n" + " vec4 value = imageLoad(inputImage, readPos);\n" + " vec4 value1 = imageLoad(inputImage, ivec2(readPos.x + 1, readPos.y));\n" + " result = getRGBValue( xmod, value, value1);\n" + " imageStore( outputImage, ivec2(gl_GlobalInvocationID.xy), result );\n" + "}\n"; + } + return prog.c_str(); +} + +static const char *computeWorkShader(std::string &prog, bool binESContext) +{ + if (binESContext) { + prog += "#version 310 es\n" + "#extension GL_ARB_compute_shader : enable\n" + "precision highp float;\n" + "precision highp int;\n" + "precision mediump image2D;\n"; + } else { + prog += "#version 430\n" + "#extension GL_ARB_compute_shader : enable\n"; + } + + prog += "int wrapMod( in int a, in int base )\n" + "{\n" + " return ( a >= 0 ) ? a % base : -(a % base) + base;\n" + "}\n"; + + prog += "void getWrappedCoords( inout int sX, inout int sY, in int width, in int height )\n" + "{\n" + " if (sY < 0) { sX -= width >> 1; sY = -sY; }\n" + " if (sY >= height) { sX += width >> 1; sY = height - sY; }\n" + " sX = wrapMod( sX, width );\n" + "}\n"; + + prog += "// Set workgroup layout;\n" + "layout (local_size_x = 16, local_size_y = 16) in;\n\n" + "layout (rgba16f, binding = 1) readonly uniform image2D inputImage;\n\n" + "layout (rgba16f, binding = 2) writeonly uniform image2D outputImage;\n\n" + "void main()\n" + "{\n" + " int prevWidth = int(gl_NumWorkGroups.x) << 1;\n" + " int prevHeight = int(gl_NumWorkGroups.y) << 1;\n" + " if ( gl_GlobalInvocationID.x >= gl_NumWorkGroups.x || gl_GlobalInvocationID.y >= " + "gl_NumWorkGroups.y )\n" + " return;\n" + " vec4 accumVal = vec4(0.0);\n" + " for ( int sy = -2; sy <= 2; ++sy )\n" + " {\n" + " for ( int sx = -2; sx <= 2; ++sx )\n" + " {\n" + " int sampleX = sx + (int(gl_GlobalInvocationID.x) << 1);\n" + " int sampleY = sy + (int(gl_GlobalInvocationID.y) << 1);\n" + " getWrappedCoords(sampleX, sampleY, prevWidth, prevHeight);\n" + " if ((sampleY * prevWidth + sampleX) < 0 )\n" + " sampleY = prevHeight + sampleY;\n" + " ivec2 pos = ivec2(sampleX, sampleY);\n" + " vec4 value = imageLoad(inputImage, pos);\n" + " float filterPdf = 1.0 / ( 1.0 + float(sx*sx + sy*sy)*2.0 );\n" + " filterPdf /= 4.71238898;\n" + " accumVal[0] += filterPdf * value.r;\n" + " accumVal[1] += filterPdf * value.g;\n" + " accumVal[2] += filterPdf * value.b;\n" + " accumVal[3] += filterPdf * value.a;\n" + " }\n" + " }\n" + " imageStore( outputImage, ivec2(gl_GlobalInvocationID.xy), accumVal );\n" + "}\n"; + + return prog.c_str(); +} + +inline NVConstDataRef toRef(const char *data) +{ + size_t len = strlen(data) + 1; + return NVConstDataRef((const QT3DSI8 *)data, (QT3DSU32)len); +} + +static bool isGLESContext(NVRenderContext *context) +{ + NVRenderContextType ctxType = context->GetRenderContextType(); + + // Need minimum of GL3 or GLES3 + if (ctxType == NVRenderContextValues::GLES2 || ctxType == NVRenderContextValues::GLES3 + || ctxType == NVRenderContextValues::GLES3PLUS) { + return true; + } + + return false; +} + +#define WORKGROUP_SIZE 16 + +Qt3DSRenderPrefilterTextureCompute::Qt3DSRenderPrefilterTextureCompute( + NVRenderContext *inNVRenderContext, QT3DSI32 inWidth, QT3DSI32 inHeight, + NVRenderTexture2D &inTexture2D, NVRenderTextureFormats::Enum inDestFormat, + NVFoundationBase &inFnd) + : Qt3DSRenderPrefilterTexture(inNVRenderContext, inWidth, inHeight, inTexture2D, inDestFormat, + inFnd) + , m_BSDFProgram(NULL) + , m_UploadProgram_RGBA8(NULL) + , m_UploadProgram_RGB8(NULL) + , m_Level0Tex(NULL) + , m_TextureCreated(false) +{ +} + +Qt3DSRenderPrefilterTextureCompute::~Qt3DSRenderPrefilterTextureCompute() +{ + m_UploadProgram_RGB8 = NULL; + m_UploadProgram_RGBA8 = NULL; + m_BSDFProgram = NULL; + m_Level0Tex = NULL; +} + +void Qt3DSRenderPrefilterTextureCompute::createComputeProgram(NVRenderContext *context) +{ + std::string computeProg; + + if (!m_BSDFProgram) { + m_BSDFProgram = context + ->CompileComputeSource( + "Compute BSDF mipmap shader", + toRef(computeWorkShader(computeProg, isGLESContext(context)))) + .mShader; + } +} + +NVRenderShaderProgram *Qt3DSRenderPrefilterTextureCompute::getOrCreateUploadComputeProgram( + NVRenderContext *context, NVRenderTextureFormats::Enum inFormat) +{ + std::string computeProg; + + if (inFormat == NVRenderTextureFormats::RGB8) { + if (!m_UploadProgram_RGB8) { + m_UploadProgram_RGB8 = + context + ->CompileComputeSource( + "Compute BSDF mipmap level 0 RGB8 shader", + toRef(computeUploadShader(computeProg, inFormat, isGLESContext(context)))) + .mShader; + } + + return m_UploadProgram_RGB8; + } else { + if (!m_UploadProgram_RGBA8) { + m_UploadProgram_RGBA8 = + context + ->CompileComputeSource( + "Compute BSDF mipmap level 0 RGBA8 shader", + toRef(computeUploadShader(computeProg, inFormat, isGLESContext(context)))) + .mShader; + } + + return m_UploadProgram_RGBA8; + } +} + +void Qt3DSRenderPrefilterTextureCompute::CreateLevel0Tex(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat) +{ + NVRenderTextureFormats::Enum theFormat = inFormat; + QT3DSI32 theWidth = m_Width; + + // Since we cannot use RGB format in GL compute + // we treat it as a RGBA component format + if (inFormat == NVRenderTextureFormats::RGB8) { + // This works only with 4 byte aligned data + QT3DS_ASSERT(m_Width % 4 == 0); + theFormat = NVRenderTextureFormats::RGBA8; + theWidth = (m_Width * 3) / 4; + } + + if (m_Level0Tex == NULL) { + m_Level0Tex = m_NVRenderContext->CreateTexture2D(); + m_Level0Tex->SetTextureStorage(1, theWidth, m_Height, theFormat, theFormat, + NVDataRef((QT3DSU8 *)inTextureData, inTextureDataSize)); + } else { + m_Level0Tex->SetTextureSubData(NVDataRef((QT3DSU8 *)inTextureData, inTextureDataSize), 0, + 0, 0, theWidth, m_Height, theFormat); + } +} + +void Qt3DSRenderPrefilterTextureCompute::Build(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat) +{ + bool needMipUpload = (inFormat != m_DestinationFormat); + // re-upload data + if (!m_TextureCreated) { + m_Texture2D.SetTextureStorage( + m_MaxMipMapLevel + 1, m_Width, m_Height, m_DestinationFormat, inFormat, (needMipUpload) + ? NVDataRef() + : NVDataRef((QT3DSU8 *)inTextureData, inTextureDataSize)); + m_Texture2D.addRef(); + // create a compute shader (if not aloread done) which computes the BSDF mipmaps for this + // texture + createComputeProgram(m_NVRenderContext); + + if (!m_BSDFProgram) { + QT3DS_ASSERT(false); + return; + } + + m_TextureCreated = true; + } else if (!needMipUpload) { + m_Texture2D.SetTextureSubData(NVDataRef((QT3DSU8 *)inTextureData, inTextureDataSize), 0, + 0, 0, m_Width, m_Height, inFormat); + } + + if (needMipUpload) { + CreateLevel0Tex(inTextureData, inTextureDataSize, inFormat); + } + + NVScopedRefCounted theInputImage; + NVScopedRefCounted theOutputImage; + theInputImage = + m_NVRenderContext->CreateImage2D(&m_Texture2D, NVRenderImageAccessType::ReadWrite); + theOutputImage = + m_NVRenderContext->CreateImage2D(&m_Texture2D, NVRenderImageAccessType::ReadWrite); + + if (needMipUpload && m_Level0Tex) { + NVRenderShaderProgram *uploadProg = + getOrCreateUploadComputeProgram(m_NVRenderContext, inFormat); + if (!uploadProg) + return; + + m_NVRenderContext->SetActiveShader(uploadProg); + + NVScopedRefCounted theInputImage0; + theInputImage0 = + m_NVRenderContext->CreateImage2D(m_Level0Tex, NVRenderImageAccessType::ReadWrite); + + theInputImage0->SetTextureLevel(0); + NVRenderCachedShaderProperty theCachedinputImage0("inputImage", + *uploadProg); + theCachedinputImage0.Set(theInputImage0); + + theOutputImage->SetTextureLevel(0); + NVRenderCachedShaderProperty theCachedOutputImage("outputImage", + *uploadProg); + theCachedOutputImage.Set(theOutputImage); + + m_NVRenderContext->DispatchCompute(uploadProg, m_Width, m_Height, 1); + + // sync + NVRenderBufferBarrierFlags flags(NVRenderBufferBarrierValues::ShaderImageAccess); + m_NVRenderContext->SetMemoryBarrier(flags); + } + + int width = m_Width >> 1; + int height = m_Height >> 1; + + m_NVRenderContext->SetActiveShader(m_BSDFProgram); + + for (int i = 1; i <= m_MaxMipMapLevel; ++i) { + theOutputImage->SetTextureLevel(i); + NVRenderCachedShaderProperty theCachedOutputImage("outputImage", + *m_BSDFProgram); + theCachedOutputImage.Set(theOutputImage); + theInputImage->SetTextureLevel(i - 1); + NVRenderCachedShaderProperty theCachedinputImage("inputImage", + *m_BSDFProgram); + theCachedinputImage.Set(theInputImage); + + m_NVRenderContext->DispatchCompute(m_BSDFProgram, width, height, 1); + + width = width > 2 ? width >> 1 : 1; + height = height > 2 ? height >> 1 : 1; + + // sync + NVRenderBufferBarrierFlags flags(NVRenderBufferBarrierValues::ShaderImageAccess); + m_NVRenderContext->SetMemoryBarrier(flags); + } +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.h b/src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.h new file mode 100644 index 0000000..e633eb1 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderPrefilterTexture.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2016 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_PREFILTER_TEXTURE_H +#define QT3DS_RENDER_PREFILTER_TEXTURE_H +#include "foundation/Qt3DSAtomic.h" +#include "render/Qt3DSRenderTexture2D.h" +#include "Qt3DSRender.h" + +#include "Qt3DSTypes.h" +#include "Qt3DSRenderLoadedTexture.h" + +namespace qt3ds { +namespace render { + + class Qt3DSRenderPrefilterTexture : public NVRefCounted + { + public: + Qt3DSRenderPrefilterTexture(NVRenderContext *inNVRenderContext, QT3DSI32 inWidth, QT3DSI32 inHeight, + NVRenderTexture2D &inTexture, + NVRenderTextureFormats::Enum inDestFormat, + qt3ds::NVFoundationBase &inFnd); + virtual ~Qt3DSRenderPrefilterTexture(); + + virtual void Build(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat) = 0; + + static Qt3DSRenderPrefilterTexture *Create(NVRenderContext *inNVRenderContext, QT3DSI32 inWidth, + QT3DSI32 inHeight, NVRenderTexture2D &inTexture, + NVRenderTextureFormats::Enum inDestFormat, + qt3ds::NVFoundationBase &inFnd); + + protected: + NVFoundationBase &m_Foundation; ///< Foundation class for allocations and other base things + volatile QT3DSI32 mRefCount; ///< reference count + + NVRenderTexture2D &m_Texture2D; + NVRenderTextureFormats::Enum m_InternalFormat; + NVRenderTextureFormats::Enum m_DestinationFormat; + + QT3DSI32 m_Width; + QT3DSI32 m_Height; + QT3DSI32 m_MaxMipMapLevel; + QT3DSI32 m_SizeOfFormat; + QT3DSI32 m_SizeOfInternalFormat; + QT3DSI32 m_InternalNoOfComponent; + QT3DSI32 m_NoOfComponent; + NVRenderContext *m_NVRenderContext; + }; + + class Qt3DSRenderPrefilterTextureCPU : public Qt3DSRenderPrefilterTexture + { + public: + Qt3DSRenderPrefilterTextureCPU(NVRenderContext *inNVRenderContext, QT3DSI32 inWidth, + QT3DSI32 inHeight, NVRenderTexture2D &inTexture, + NVRenderTextureFormats::Enum inDestFormat, + qt3ds::NVFoundationBase &inFnd); + + void Build(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat) override; + + STextureData CreateBsdfMipLevel(STextureData &inCurMipLevel, STextureData &inPrevMipLevel, + QT3DSI32 width, QT3DSI32 height); + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_Foundation) + + int wrapMod(int a, int base); + void getWrappedCoords(int &sX, int &sY, int width, int height); + }; + + class Qt3DSRenderPrefilterTextureCompute : public Qt3DSRenderPrefilterTexture + { + public: + Qt3DSRenderPrefilterTextureCompute(NVRenderContext *inNVRenderContext, QT3DSI32 inWidth, + QT3DSI32 inHeight, NVRenderTexture2D &inTexture, + NVRenderTextureFormats::Enum inDestFormat, + qt3ds::NVFoundationBase &inFnd); + ~Qt3DSRenderPrefilterTextureCompute(); + + void Build(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat) override; + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_Foundation) + + private: + void CreateLevel0Tex(void *inTextureData, QT3DSI32 inTextureDataSize, + NVRenderTextureFormats::Enum inFormat); + + NVScopedRefCounted m_BSDFProgram; + NVScopedRefCounted m_UploadProgram_RGBA8; + NVScopedRefCounted m_UploadProgram_RGB8; + NVScopedRefCounted m_Level0Tex; + bool m_TextureCreated; + + void createComputeProgram(NVRenderContext *context); + NVRenderShaderProgram * + getOrCreateUploadComputeProgram(NVRenderContext *context, + NVRenderTextureFormats::Enum inFormat); + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.cpp new file mode 100644 index 0000000..25fbb41 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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 "Qt3DSRenderResourceBufferObjects.h" + +using namespace qt3ds::render; + +/* + IResourceManager& m_ResourceManager; + NVRenderFrameBuffer* m_FrameBuffer; + */ + +CResourceFrameBuffer::CResourceFrameBuffer(IResourceManager &mgr) + : m_ResourceManager(mgr) + , m_FrameBuffer(NULL) +{ +} + +CResourceFrameBuffer::~CResourceFrameBuffer() +{ + ReleaseFrameBuffer(); +} + +bool CResourceFrameBuffer::EnsureFrameBuffer() +{ + if (!m_FrameBuffer) { + m_FrameBuffer = m_ResourceManager.AllocateFrameBuffer(); + return true; + } + return false; +} + +void CResourceFrameBuffer::ReleaseFrameBuffer() +{ + if (m_FrameBuffer) { + m_ResourceManager.Release(*m_FrameBuffer); + } +} + +CResourceRenderBuffer::CResourceRenderBuffer(IResourceManager &mgr) + : m_ResourceManager(mgr) + , m_RenderBuffer(NULL) +{ +} + +CResourceRenderBuffer::~CResourceRenderBuffer() +{ + ReleaseRenderBuffer(); +} + +bool CResourceRenderBuffer::EnsureRenderBuffer(QT3DSU32 width, QT3DSU32 height, + NVRenderRenderBufferFormats::Enum storageFormat) +{ + if (m_RenderBuffer == NULL || m_Dimensions.m_Width != width || m_Dimensions.m_Height != height + || m_StorageFormat != storageFormat) { + if (m_RenderBuffer == NULL || m_StorageFormat != storageFormat) { + ReleaseRenderBuffer(); + m_RenderBuffer = m_ResourceManager.AllocateRenderBuffer(width, height, storageFormat); + } else + m_RenderBuffer->SetDimensions( + qt3ds::render::NVRenderRenderBufferDimensions(width, height)); + m_Dimensions = m_RenderBuffer->GetDimensions(); + m_StorageFormat = m_RenderBuffer->GetStorageFormat(); + return true; + } + return false; +} + +void CResourceRenderBuffer::ReleaseRenderBuffer() +{ + if (m_RenderBuffer) { + m_ResourceManager.Release(*m_RenderBuffer); + m_RenderBuffer = NULL; + } +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.h b/src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.h new file mode 100644 index 0000000..fb54c4d --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderResourceBufferObjects.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_RESOURCE_BUFFER_OBJECTS_H +#define QT3DS_RENDER_RESOURCE_BUFFER_OBJECTS_H +#include "Qt3DSRender.h" +#include "render/Qt3DSRenderContext.h" +#include "Qt3DSRenderResourceManager.h" +#include "render/Qt3DSRenderFrameBuffer.h" +#include "render/Qt3DSRenderRenderBuffer.h" + +namespace qt3ds { +namespace render { + class CResourceFrameBuffer + { + protected: + IResourceManager &m_ResourceManager; + NVRenderFrameBuffer *m_FrameBuffer; + + public: + CResourceFrameBuffer(IResourceManager &mgr); + ~CResourceFrameBuffer(); + bool EnsureFrameBuffer(); + void ReleaseFrameBuffer(); + + IResourceManager &GetResourceManager() { return m_ResourceManager; } + operator NVRenderFrameBuffer *() { return m_FrameBuffer; } + NVRenderFrameBuffer *operator->() + { + QT3DS_ASSERT(m_FrameBuffer); + return m_FrameBuffer; + } + NVRenderFrameBuffer &operator*() + { + QT3DS_ASSERT(m_FrameBuffer); + return *m_FrameBuffer; + } + }; + + class CResourceRenderBuffer + { + protected: + IResourceManager &m_ResourceManager; + NVRenderRenderBuffer *m_RenderBuffer; + qt3ds::render::NVRenderRenderBufferFormats::Enum m_StorageFormat; + qt3ds::render::NVRenderRenderBufferDimensions m_Dimensions; + + public: + CResourceRenderBuffer(IResourceManager &mgr); + ~CResourceRenderBuffer(); + bool EnsureRenderBuffer(QT3DSU32 width, QT3DSU32 height, + NVRenderRenderBufferFormats::Enum storageFormat); + void ReleaseRenderBuffer(); + + operator NVRenderRenderBuffer *() { return m_RenderBuffer; } + NVRenderRenderBuffer *operator->() + { + QT3DS_ASSERT(m_RenderBuffer); + return m_RenderBuffer; + } + NVRenderRenderBuffer &operator*() + { + QT3DS_ASSERT(m_RenderBuffer); + return *m_RenderBuffer; + } + }; +} +} +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.cpp new file mode 100644 index 0000000..3593688 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.cpp @@ -0,0 +1,436 @@ +/**************************************************************************** +** +** 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 "Qt3DSRenderResourceManager.h" +#include "render/Qt3DSRenderContext.h" +#include "render/Qt3DSRenderFrameBuffer.h" +#include "render/Qt3DSRenderRenderBuffer.h" +#include "render/Qt3DSRenderTexture2D.h" +#include "render/Qt3DSRenderTexture2DArray.h" +#include "render/Qt3DSRenderTextureCube.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSContainers.h" + +using namespace qt3ds::render; + +namespace { + +struct SResourceManager : public IResourceManager +{ + NVScopedRefCounted m_RenderContext; + // Complete list of all allocated objects + nvvector> m_AllocatedObjects; + + nvvector m_FreeFrameBuffers; + nvvector m_FreeRenderBuffers; + nvvector m_FreeTextures; + nvvector m_FreeTexArrays; + nvvector m_FreeTexCubes; + nvvector m_FreeImages; + + volatile QT3DSI32 mRefCount; + + SResourceManager(NVRenderContext &ctx) + : m_RenderContext(ctx) + , m_AllocatedObjects(ctx.GetAllocator(), "SResourceManager::m_FrameBuffers") + , m_FreeFrameBuffers(ctx.GetAllocator(), "SResourceManager::m_FreeFrameBuffers") + , m_FreeRenderBuffers(ctx.GetAllocator(), "SResourceManager::m_FreeRenderBuffers") + , m_FreeTextures(ctx.GetAllocator(), "SResourceManager::m_FreeTextures") + , m_FreeTexArrays(ctx.GetAllocator(), "SResourceManager::m_FreeTexArrays") + , m_FreeTexCubes(ctx.GetAllocator(), "SResourceManager::m_FreeTexCubes") + , m_FreeImages(ctx.GetAllocator(), "SResourceManager::m_FreeImages") + , mRefCount(0) + { + } + virtual ~SResourceManager() {} + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_RenderContext->GetAllocator()) + + NVRenderFrameBuffer *AllocateFrameBuffer() override + { + if (m_FreeFrameBuffers.empty() == true) { + NVRenderFrameBuffer *newBuffer = m_RenderContext->CreateFrameBuffer(); + m_AllocatedObjects.push_back(newBuffer); + m_FreeFrameBuffers.push_back(newBuffer); + } + NVRenderFrameBuffer *retval = m_FreeFrameBuffers.back(); + m_FreeFrameBuffers.pop_back(); + return retval; + } + void Release(NVRenderFrameBuffer &inBuffer) override + { + if (inBuffer.HasAnyAttachment()) { + // Ensure the framebuffer has no attachments. + inBuffer.Attach(NVRenderFrameBufferAttachments::Color0, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color1, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color2, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color3, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color4, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color5, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color6, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Color7, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Depth, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + inBuffer.Attach(NVRenderFrameBufferAttachments::Stencil, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + if (m_RenderContext->IsDepthStencilSupported()) + inBuffer.Attach(NVRenderFrameBufferAttachments::DepthStencil, + qt3ds::render::NVRenderTextureOrRenderBuffer()); + } +#ifdef _DEBUG + nvvector::iterator theFind = + eastl::find(m_FreeFrameBuffers.begin(), m_FreeFrameBuffers.end(), &inBuffer); + QT3DS_ASSERT(theFind == m_FreeFrameBuffers.end()); +#endif + m_FreeFrameBuffers.push_back(&inBuffer); + } + + virtual NVRenderRenderBuffer * + AllocateRenderBuffer(QT3DSU32 inWidth, QT3DSU32 inHeight, + NVRenderRenderBufferFormats::Enum inBufferFormat) override + { + // Look for one of this specific size and format. + QT3DSU32 existingMatchIdx = m_FreeRenderBuffers.size(); + for (QT3DSU32 idx = 0, end = existingMatchIdx; idx < end; ++idx) { + NVRenderRenderBuffer *theBuffer = m_FreeRenderBuffers[idx]; + qt3ds::render::NVRenderRenderBufferDimensions theDims = theBuffer->GetDimensions(); + NVRenderRenderBufferFormats::Enum theFormat = theBuffer->GetStorageFormat(); + if (theDims.m_Width == inWidth && theDims.m_Height == inHeight + && theFormat == inBufferFormat) { + // Replace idx with last for efficient erasure (that reorders the vector). + m_FreeRenderBuffers.replace_with_last(idx); + return theBuffer; + } else if (theFormat == inBufferFormat) + existingMatchIdx = idx; + } + // If a specific exact match couldn't be found, just use the buffer with + // the same format and resize it. + if (existingMatchIdx < m_FreeRenderBuffers.size()) { + NVRenderRenderBuffer *theBuffer = m_FreeRenderBuffers[existingMatchIdx]; + m_FreeRenderBuffers.replace_with_last(existingMatchIdx); + theBuffer->SetDimensions(qt3ds::render::NVRenderRenderBufferDimensions(inWidth, inHeight)); + return theBuffer; + } + + NVRenderRenderBuffer *theBuffer = + m_RenderContext->CreateRenderBuffer(inBufferFormat, inWidth, inHeight); + m_AllocatedObjects.push_back(theBuffer); + return theBuffer; + } + void Release(NVRenderRenderBuffer &inBuffer) override + { +#ifdef _DEBUG + nvvector::iterator theFind = + eastl::find(m_FreeRenderBuffers.begin(), m_FreeRenderBuffers.end(), &inBuffer); + QT3DS_ASSERT(theFind == m_FreeRenderBuffers.end()); +#endif + m_FreeRenderBuffers.push_back(&inBuffer); + } + NVRenderTexture2D *SetupAllocatedTexture(NVRenderTexture2D &inTexture) + { + inTexture.SetMinFilter(NVRenderTextureMinifyingOp::Linear); + inTexture.SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return &inTexture; + } + NVRenderTexture2D *AllocateTexture2D(QT3DSU32 inWidth, QT3DSU32 inHeight, + NVRenderTextureFormats::Enum inTextureFormat, + QT3DSU32 inSampleCount, bool immutable) override + { + bool inMultisample = + inSampleCount > 1 && m_RenderContext->AreMultisampleTexturesSupported(); + for (QT3DSU32 idx = 0, end = m_FreeTextures.size(); idx < end; ++idx) { + NVRenderTexture2D *theTexture = m_FreeTextures[idx]; + STextureDetails theDetails = theTexture->GetTextureDetails(); + if (theDetails.m_Width == inWidth && theDetails.m_Height == inHeight + && inTextureFormat == theDetails.m_Format + && theTexture->GetSampleCount() == inSampleCount) { + m_FreeTextures.replace_with_last(idx); + return SetupAllocatedTexture(*theTexture); + } + } + // else resize an existing texture. This is very expensive + // note that MSAA textures are not resizable ( in GLES ) + /* + if ( !m_FreeTextures.empty() && !inMultisample ) + { + NVRenderTexture2D* theTexture = m_FreeTextures.back(); + m_FreeTextures.pop_back(); + + // note we could re-use a former MSAA texture + // this causes a entiere destroy of the previous texture object + theTexture->SetTextureData( NVDataRef(), 0, inWidth, inHeight, inTextureFormat + ); + + return SetupAllocatedTexture( *theTexture ); + }*/ + // else create a new texture. + NVRenderTexture2D *theTexture = m_RenderContext->CreateTexture2D(); + + if (inMultisample) + theTexture->SetTextureDataMultisample(inSampleCount, inWidth, inHeight, + inTextureFormat); + else if (immutable) + theTexture->SetTextureStorage(1, inWidth, inHeight, inTextureFormat); + else + theTexture->SetTextureData(NVDataRef(), 0, inWidth, inHeight, inTextureFormat); + + m_AllocatedObjects.push_back(theTexture); + return SetupAllocatedTexture(*theTexture); + } + void Release(NVRenderTexture2D &inBuffer) override + { +#ifdef _DEBUG + nvvector::iterator theFind = + eastl::find(m_FreeTextures.begin(), m_FreeTextures.end(), &inBuffer); + QT3DS_ASSERT(theFind == m_FreeTextures.end()); +#endif + m_FreeTextures.push_back(&inBuffer); + } + + NVRenderTexture2DArray *AllocateTexture2DArray(QT3DSU32 inWidth, QT3DSU32 inHeight, QT3DSU32 inSlices, + NVRenderTextureFormats::Enum inTextureFormat, + QT3DSU32 inSampleCount) override + { + bool inMultisample = + inSampleCount > 1 && m_RenderContext->AreMultisampleTexturesSupported(); + for (QT3DSU32 idx = 0, end = m_FreeTexArrays.size(); idx < end; ++idx) { + NVRenderTexture2DArray *theTexture = m_FreeTexArrays[idx]; + STextureDetails theDetails = theTexture->GetTextureDetails(); + if (theDetails.m_Width == inWidth && theDetails.m_Height == inHeight + && theDetails.m_Depth == inSlices && inTextureFormat == theDetails.m_Format + && theTexture->GetSampleCount() == inSampleCount) { + m_FreeTexArrays.replace_with_last(idx); + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::Linear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return theTexture; + } + } + + // else resize an existing texture. This should be fairly quick at the driver level. + // note that MSAA textures are not resizable ( in GLES ) + if (!m_FreeTexArrays.empty() && !inMultisample) { + NVRenderTexture2DArray *theTexture = m_FreeTexArrays.back(); + m_FreeTexArrays.pop_back(); + + // note we could re-use a former MSAA texture + // this causes a entiere destroy of the previous texture object + theTexture->SetTextureData(NVDataRef(), 0, inWidth, inHeight, inSlices, + inTextureFormat); + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::Linear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return theTexture; + } + + // else create a new texture. + NVRenderTexture2DArray *theTexture = NULL; + + if (!inMultisample) { + theTexture = m_RenderContext->CreateTexture2DArray(); + theTexture->SetTextureData(NVDataRef(), 0, inWidth, inHeight, inSlices, + inTextureFormat); + } else { + // Not supported yet + return NULL; + } + + m_AllocatedObjects.push_back(theTexture); + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::Linear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return theTexture; + } + + void Release(NVRenderTexture2DArray &inBuffer) override + { +#ifdef _DEBUG + nvvector::iterator theFind = + eastl::find(m_FreeTexArrays.begin(), m_FreeTexArrays.end(), &inBuffer); + QT3DS_ASSERT(theFind == m_FreeTexArrays.end()); +#endif + m_FreeTexArrays.push_back(&inBuffer); + } + + NVRenderTextureCube *AllocateTextureCube(QT3DSU32 inWidth, QT3DSU32 inHeight, + NVRenderTextureFormats::Enum inTextureFormat, + QT3DSU32 inSampleCount) override + { + bool inMultisample = + inSampleCount > 1 && m_RenderContext->AreMultisampleTexturesSupported(); + for (QT3DSU32 idx = 0, end = m_FreeTexCubes.size(); idx < end; ++idx) { + NVRenderTextureCube *theTexture = m_FreeTexCubes[idx]; + STextureDetails theDetails = theTexture->GetTextureDetails(); + if (theDetails.m_Width == inWidth && theDetails.m_Height == inHeight + && inTextureFormat == theDetails.m_Format + && theTexture->GetSampleCount() == inSampleCount) { + m_FreeTexCubes.replace_with_last(idx); + + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::Linear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return theTexture; + } + } + + // else resize an existing texture. This should be fairly quick at the driver level. + // note that MSAA textures are not resizable ( in GLES ) + if (!m_FreeTexCubes.empty() && !inMultisample) { + NVRenderTextureCube *theTexture = m_FreeTexCubes.back(); + m_FreeTexCubes.pop_back(); + + // note we could re-use a former MSAA texture + // this causes a entire destroy of the previous texture object + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubePosX, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubeNegX, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubePosY, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubeNegY, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubePosZ, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubeNegZ, + inWidth, inHeight, inTextureFormat); + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::Linear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return theTexture; + } + + // else create a new texture. + NVRenderTextureCube *theTexture = NULL; + + if (!inMultisample) { + theTexture = m_RenderContext->CreateTextureCube(); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubePosX, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubeNegX, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubePosY, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubeNegY, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubePosZ, + inWidth, inHeight, inTextureFormat); + theTexture->SetTextureData(NVDataRef(), 0, NVRenderTextureCubeFaces::CubeNegZ, + inWidth, inHeight, inTextureFormat); + } else { + // Not supported yet + return NULL; + } + + m_AllocatedObjects.push_back(theTexture); + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::Linear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + return theTexture; + } + + void Release(NVRenderTextureCube &inBuffer) override + { +#ifdef _DEBUG + nvvector::iterator theFind = + eastl::find(m_FreeTexCubes.begin(), m_FreeTexCubes.end(), &inBuffer); + QT3DS_ASSERT(theFind == m_FreeTexCubes.end()); +#endif + m_FreeTexCubes.push_back(&inBuffer); + } + + NVRenderImage2D *AllocateImage2D(NVRenderTexture2D *inTexture, + NVRenderImageAccessType::Enum inAccess) override + { + if (m_FreeImages.empty() == true) { + NVRenderImage2D *newImage = m_RenderContext->CreateImage2D(inTexture, inAccess); + if (newImage) { + m_AllocatedObjects.push_back(newImage); + m_FreeImages.push_back(newImage); + } + } + + NVRenderImage2D *retval = m_FreeImages.back(); + m_FreeImages.pop_back(); + + return retval; + } + + void Release(NVRenderImage2D &inBuffer) override + { +#ifdef _DEBUG + nvvector::iterator theFind = + eastl::find(m_FreeImages.begin(), m_FreeImages.end(), &inBuffer); + QT3DS_ASSERT(theFind == m_FreeImages.end()); +#endif + m_FreeImages.push_back(&inBuffer); + } + + NVRenderContext &GetRenderContext() override { return *m_RenderContext; } + + void RemoveObjectAllocation(NVRefCounted *obj) { + for (QT3DSU32 idx = 0, end = m_AllocatedObjects.size(); idx < end; ++idx) { + if (obj == m_AllocatedObjects[idx]) { + m_AllocatedObjects.replace_with_last(idx); + break; + } + } + } + + void DestroyFreeSizedResources() + { + for (int idx = m_FreeRenderBuffers.size() - 1; idx >= 0; --idx) { + NVRenderRenderBuffer *obj = m_FreeRenderBuffers[idx]; + m_FreeRenderBuffers.replace_with_last(idx); + RemoveObjectAllocation(obj); + } + for (int idx = m_FreeTextures.size() - 1; idx >= 0; --idx) { + NVRenderTexture2D *obj = m_FreeTextures[idx]; + m_FreeTextures.replace_with_last(idx); + RemoveObjectAllocation(obj); + } + for (int idx = m_FreeTexArrays.size() - 1; idx >= 0; --idx) { + NVRenderTexture2DArray *obj = m_FreeTexArrays[idx]; + m_FreeTexArrays.replace_with_last(idx); + RemoveObjectAllocation(obj); + } + for (int idx = m_FreeTexCubes.size() - 1; idx >= 0; --idx) { + NVRenderTextureCube *obj = m_FreeTexCubes[idx]; + m_FreeTexCubes.replace_with_last(idx); + RemoveObjectAllocation(obj); + } + } +}; +} + +IResourceManager &IResourceManager::CreateResourceManager(NVRenderContext &inContext) +{ + return *QT3DS_NEW(inContext.GetAllocator(), SResourceManager)(inContext); +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.h b/src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.h new file mode 100644 index 0000000..675d644 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderResourceManager.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_RESOURCE_MANAGER_H +#define QT3DS_RENDER_RESOURCE_MANAGER_H +#include "Qt3DSRender.h" +#include "foundation/Qt3DSRefCounted.h" +#include "render/Qt3DSRenderBaseTypes.h" + +namespace qt3ds { +namespace render { + /** + * Implements simple pooling of render resources + */ + class IResourceManager : public NVRefCounted + { + protected: + virtual ~IResourceManager() {} + + public: + virtual NVRenderFrameBuffer *AllocateFrameBuffer() = 0; + virtual void Release(NVRenderFrameBuffer &inBuffer) = 0; + virtual NVRenderRenderBuffer * + AllocateRenderBuffer(QT3DSU32 inWidth, QT3DSU32 inHeight, + NVRenderRenderBufferFormats::Enum inBufferFormat) = 0; + virtual void Release(NVRenderRenderBuffer &inBuffer) = 0; + virtual NVRenderTexture2D *AllocateTexture2D(QT3DSU32 inWidth, QT3DSU32 inHeight, + NVRenderTextureFormats::Enum inTextureFormat, + QT3DSU32 inSampleCount = 1, + bool immutable = false) = 0; + virtual void Release(NVRenderTexture2D &inBuffer) = 0; + virtual NVRenderTexture2DArray * + AllocateTexture2DArray(QT3DSU32 inWidth, QT3DSU32 inHeight, QT3DSU32 inSlices, + NVRenderTextureFormats::Enum inTextureFormat, + QT3DSU32 inSampleCount = 1) = 0; + virtual void Release(NVRenderTexture2DArray &inBuffer) = 0; + virtual NVRenderTextureCube * + AllocateTextureCube(QT3DSU32 inWidth, QT3DSU32 inHeight, + NVRenderTextureFormats::Enum inTextureFormat, + QT3DSU32 inSampleCount = 1) = 0; + virtual void Release(NVRenderTextureCube &inBuffer) = 0; + virtual NVRenderImage2D *AllocateImage2D(NVRenderTexture2D *inTexture, + NVRenderImageAccessType::Enum inAccess) = 0; + virtual void Release(NVRenderImage2D &inBuffer) = 0; + + virtual NVRenderContext &GetRenderContext() = 0; + virtual void DestroyFreeSizedResources() = 0; + + static IResourceManager &CreateResourceManager(NVRenderContext &inContext); + }; +} +} + +#endif diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.cpp new file mode 100644 index 0000000..e877cbc --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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 "Qt3DSRenderResourceTexture2D.h" + +using namespace qt3ds::render; + +CResourceTexture2D::CResourceTexture2D(IResourceManager &mgr, NVRenderTexture2D *inTexture) + : m_ResourceManager(mgr) + , m_Texture(inTexture) +{ + if (inTexture) + m_TextureDetails = inTexture->GetTextureDetails(); +} + +CResourceTexture2D::CResourceTexture2D(IResourceManager &mgr, QT3DSU32 width, QT3DSU32 height, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples) + : m_ResourceManager(mgr) + , m_Texture(NULL) +{ + EnsureTexture(width, height, inFormat, inSamples); +} + +CResourceTexture2D::~CResourceTexture2D() +{ + ReleaseTexture(); +} + +// Returns true if the texture was allocated, false if nothing changed (no allocation). +bool CResourceTexture2D::TextureMatches(QT3DSU32 width, QT3DSU32 height, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples) +{ + return m_Texture && m_TextureDetails.m_Width == width && m_TextureDetails.m_Height == height + && m_TextureDetails.m_Format == inFormat && m_TextureDetails.m_SampleCount == inSamples; +} + +bool CResourceTexture2D::EnsureTexture(QT3DSU32 width, QT3DSU32 height, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples) +{ + if (TextureMatches(width, height, inFormat, inSamples)) + return false; + + if (m_Texture && inSamples > 1) { + // we cannot resize MSAA textures though release first + ReleaseTexture(); + } + + if (!m_Texture) + m_Texture = m_ResourceManager.AllocateTexture2D(width, height, inFormat, inSamples); + else { + // multisampled textures are immuteable + QT3DS_ASSERT(inSamples == 1); + m_Texture->SetTextureData(NVDataRef(), 0, width, height, inFormat); + } + + m_TextureDetails = m_Texture->GetTextureDetails(); + return true; +} + +void CResourceTexture2D::ReleaseTexture() +{ + if (m_Texture) { + m_ResourceManager.Release(*m_Texture); + ForgetTexture(); + } +} + +void CResourceTexture2D::ForgetTexture() +{ + m_Texture = NULL; +} + +void CResourceTexture2D::StealTexture(CResourceTexture2D &inOther) +{ + ReleaseTexture(); + m_Texture = inOther.m_Texture; + m_TextureDetails = inOther.m_TextureDetails; + inOther.m_Texture = NULL; +} + +CResourceTexture2DArray::CResourceTexture2DArray(IResourceManager &mgr) + : m_ResourceManager(mgr) + , m_Texture(NULL) +{ +} + +CResourceTexture2DArray::CResourceTexture2DArray(IResourceManager &mgr, QT3DSU32 width, QT3DSU32 height, + QT3DSU32 slices, + NVRenderTextureFormats::Enum inFormat, + QT3DSU32 inSamples) + : m_ResourceManager(mgr) + , m_Texture(NULL) +{ + EnsureTexture(width, height, slices, inFormat, inSamples); +} + +CResourceTexture2DArray::~CResourceTexture2DArray() +{ + ReleaseTexture(); +} + +bool CResourceTexture2DArray::TextureMatches(QT3DSU32 width, QT3DSU32 height, QT3DSU32 slices, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples) +{ + return m_Texture && m_TextureDetails.m_Depth == slices && m_TextureDetails.m_Width == width + && m_TextureDetails.m_Height == height && m_TextureDetails.m_Format == inFormat + && m_TextureDetails.m_SampleCount == inSamples; +} + +bool CResourceTexture2DArray::EnsureTexture(QT3DSU32 width, QT3DSU32 height, QT3DSU32 slices, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples) +{ + if (TextureMatches(width, height, slices, inFormat, inSamples)) + return false; + + if (m_Texture && inSamples > 1) { + // we cannot resize MSAA textures though release first + ReleaseTexture(); + } + + if (!m_Texture) + m_Texture = + m_ResourceManager.AllocateTexture2DArray(width, height, slices, inFormat, inSamples); + else { + // multisampled textures are immuteable + QT3DS_ASSERT(inSamples == 1); + m_Texture->SetTextureData(NVDataRef(), 0, width, height, slices, inFormat); + } + + m_TextureDetails = m_Texture->GetTextureDetails(); + return true; +} + +void CResourceTexture2DArray::ReleaseTexture() +{ + if (m_Texture) { + m_ResourceManager.Release(*m_Texture); + m_Texture = NULL; + } +} + +void CResourceTexture2DArray::StealTexture(CResourceTexture2DArray &inOther) +{ + ReleaseTexture(); + m_Texture = inOther.m_Texture; + m_TextureDetails = inOther.m_TextureDetails; + inOther.m_Texture = NULL; +} diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.h b/src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.h new file mode 100644 index 0000000..eb54713 --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderResourceTexture2D.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_RENDER_RESOURCE_TEXTURE_2D_H +#define QT3DS_RENDER_RESOURCE_TEXTURE_2D_H +#include "Qt3DSRender.h" +#include "render/Qt3DSRenderContext.h" +#include "render/Qt3DSRenderTexture2D.h" +#include "render/Qt3DSRenderTexture2DArray.h" +#include "Qt3DSRenderResourceManager.h" + +namespace qt3ds { +namespace render { + class CResourceTexture2D + { + protected: + IResourceManager &m_ResourceManager; + NVRenderTexture2D *m_Texture; + STextureDetails m_TextureDetails; + + public: + CResourceTexture2D(IResourceManager &mgr, NVRenderTexture2D *inTexture = NULL); + // create and allocate the texture right away. + CResourceTexture2D(IResourceManager &mgr, QT3DSU32 width, QT3DSU32 height, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples = 1); + ~CResourceTexture2D(); + // Returns true if the texture matches the specs, false if the texture needs to be + // reallocated + bool TextureMatches(QT3DSU32 width, QT3DSU32 height, NVRenderTextureFormats::Enum inFormat, + QT3DSU32 inSamples = 1); + + // Returns true if the texture was allocated, false if nothing changed (no allocation). + // Note this is the exact opposite of TextureMatches. + bool EnsureTexture(QT3DSU32 width, QT3DSU32 height, NVRenderTextureFormats::Enum inFormat, + QT3DSU32 inSamples = 1); + + // Force release the texture. + void ReleaseTexture(); + NVRenderTexture2D &operator*() + { + QT3DS_ASSERT(m_Texture); + return *m_Texture; + } + NVRenderTexture2D *operator->() + { + QT3DS_ASSERT(m_Texture); + return m_Texture; + } + operator NVRenderTexture2D *() { return m_Texture; } + NVRenderTexture2D *GetTexture() { return m_Texture; } + void ForgetTexture(); + // Enforces single ownership rules. + void StealTexture(CResourceTexture2D &inOther); + }; + + class CResourceTexture2DArray + { + protected: + IResourceManager &m_ResourceManager; + qt3ds::render::NVRenderTexture2DArray *m_Texture; + STextureDetails m_TextureDetails; + + public: + CResourceTexture2DArray(IResourceManager &mgr); + // create and allocate the texture right away. + CResourceTexture2DArray(IResourceManager &mgr, QT3DSU32 width, QT3DSU32 height, QT3DSU32 slices, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples = 1); + ~CResourceTexture2DArray(); + // Returns true if the texture matches the specs, false if the texture needs to be + // reallocated + bool TextureMatches(QT3DSU32 width, QT3DSU32 height, QT3DSU32 slices, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples = 1); + + // Returns true if the texture was allocated, false if nothing changed (no allocation). + // Note this is the exact opposite of TextureMatches. + bool EnsureTexture(QT3DSU32 width, QT3DSU32 height, QT3DSU32 slices, + NVRenderTextureFormats::Enum inFormat, QT3DSU32 inSamples = 1); + + // Force release the texture. + void ReleaseTexture(); + qt3ds::render::NVRenderTexture2DArray &operator*() + { + QT3DS_ASSERT(m_Texture); + return *m_Texture; + } + qt3ds::render::NVRenderTexture2DArray *operator->() + { + QT3DS_ASSERT(m_Texture); + return m_Texture; + } + operator qt3ds::render::NVRenderTexture2DArray *() { return m_Texture; } + qt3ds::render::NVRenderTexture2DArray *GetTexture() { return m_Texture; } + // Enforces single ownership rules. + void StealTexture(CResourceTexture2DArray &inOther); + }; +} +} + +#endif -- cgit v1.2.3