Преглед изворни кода

More work on reflection probes

BearishSun пре 8 година
родитељ
комит
6e1c80700f

+ 2 - 0
Source/BansheeCore/Include/BsCorePrerequisites.h

@@ -451,6 +451,7 @@ namespace bs
 		class RenderWindowManager;
 		class RenderStateManager;
 		class HardwareBufferManager;
+		class ReflectionProbe;
 	}
 }
 
@@ -571,6 +572,7 @@ namespace bs
 		TID_MorphChannel = 1130,
 		TID_ReflectionProbe = 1131,
 		TID_CReflectionProbe = 1132,
+		TID_CachedTextureData = 1133,
 
 		// Moved from Engine layer
 		TID_CCamera = 30000,

+ 7 - 0
Source/BansheeCore/Include/BsReflectionProbe.h

@@ -97,6 +97,9 @@ namespace bs
 		/**	Sets whether the probe should be used or not. */
 		void setIsActive(bool active) { mIsActive = active; _markCoreDirty(); }
 
+		/** Returns an identifier that uniquely identifies the probe. */
+		const String& getUUID() const { return mUUID; }
+
 		/** 
 		 * Marks the simulation thread object as dirty and notifies the system its data should be synced with its core 
 		 * thread counterpart. 
@@ -113,6 +116,7 @@ namespace bs
 		ReflectionProbeType mType; /**< Type of probe that determines how are the rest of the parameters interpreted. */
 		float mRadius; /**< Radius used for sphere reflection probes. */
 		Vector3 mExtents; /**< Extents used by box & plane reflection probes. */
+		String mUUID; /**< Identifier that uniquely identifies the probe. */
 
 		bool mIsActive; /**< Whether the light should be rendered or not. */
 		Sphere mBounds; /**< Sphere that bounds the light area of influence. */
@@ -138,6 +142,9 @@ namespace bs
 		/** Gets the custom texture assigned through setCustomTexture(). */
 		TextureType getCustomTexture() const { return mCustomTexture; }
 
+		/** Forces the reflection probe to regenerate its texture. Call is ignored if the probe uses a custom texture. */
+		void generate();
+
 	protected:
 		TextureType mCustomTexture;
 	};

+ 1 - 0
Source/BansheeCore/Include/BsReflectionProbeRTTI.h

@@ -23,6 +23,7 @@ namespace bs
 			BS_RTTI_MEMBER_PLAIN(mRadius, 3)
 			BS_RTTI_MEMBER_PLAIN(mExtents, 4)
 			BS_RTTI_MEMBER_REFL(mCustomTexture, 5)
+			BS_RTTI_MEMBER_PLAIN(mUUID, 6)
 		BS_END_RTTI_MEMBERS
 	public:
 		ReflectionProbeRTTI()

+ 15 - 0
Source/BansheeCore/Source/BsReflectionProbe.cpp

@@ -6,6 +6,7 @@
 #include "BsFrameAlloc.h"
 #include "BsTexture.h"
 #include "BsRenderer.h"
+#include "BsUUID.h"
 
 namespace bs
 {
@@ -43,6 +44,13 @@ namespace bs
 		: ReflectionProbeBase(type, radius, extents)
 	{ }
 
+	template <bool Core>
+	void TReflectionProbe<Core>::generate()
+	{
+		if (mCustomTexture != nullptr)
+			_markCoreDirty();
+	}
+
 	template class TReflectionProbe<true>;
 	template class TReflectionProbe<false>;
 
@@ -69,6 +77,7 @@ namespace bs
 		ReflectionProbe* probe = new (bs_alloc<ReflectionProbe>()) ReflectionProbe(ReflectionProbeType::Sphere, radius, Vector3::ZERO);
 		SPtr<ReflectionProbe> probePtr = bs_core_ptr<ReflectionProbe>(probe);
 		probePtr->_setThisPtr(probePtr);
+		probePtr->mUUID = UUIDGenerator::generateRandom();
 		probePtr->initialize();
 
 		return probePtr;
@@ -79,6 +88,7 @@ namespace bs
 		ReflectionProbe* probe = new (bs_alloc<ReflectionProbe>()) ReflectionProbe(ReflectionProbeType::Box, 0.0f, extents);
 		SPtr<ReflectionProbe> probePtr = bs_core_ptr<ReflectionProbe>(probe);
 		probePtr->_setThisPtr(probePtr);
+		probePtr->mUUID = UUIDGenerator::generateRandom();
 		probePtr->initialize();
 
 		return probePtr;
@@ -89,6 +99,7 @@ namespace bs
 		ReflectionProbe* probe = new (bs_alloc<ReflectionProbe>()) ReflectionProbe(ReflectionProbeType::Plane, 0.0f, extents);
 		SPtr<ReflectionProbe> probePtr = bs_core_ptr<ReflectionProbe>(probe);
 		probePtr->_setThisPtr(probePtr);
+		probePtr->mUUID = UUIDGenerator::generateRandom();
 		probePtr->initialize();
 
 		return probePtr;
@@ -107,6 +118,7 @@ namespace bs
 	{
 		ct::ReflectionProbe* probe = new (bs_alloc<ct::ReflectionProbe>()) ct::ReflectionProbe(mType, mRadius, mExtents);
 		SPtr<ct::ReflectionProbe> probePtr = bs_shared_ptr<ct::ReflectionProbe>(probe);
+		probePtr->mUUID = mUUID;
 		probePtr->_setThisPtr(probePtr);
 
 		return probePtr;
@@ -124,6 +136,7 @@ namespace bs
 		size += rttiGetElemSize(getCoreDirtyFlags());
 		size += rttiGetElemSize(mBounds);
 		size += rttiGetElemSize(sizeof(SPtr<ct::Texture>));
+		size += rttiGetElemSize(mUUID);
 
 		UINT8* buffer = allocator->alloc(size);
 
@@ -136,6 +149,7 @@ namespace bs
 		dataPtr = rttiWriteElem(mIsActive, dataPtr);
 		dataPtr = rttiWriteElem(getCoreDirtyFlags(), dataPtr);
 		dataPtr = rttiWriteElem(mBounds, dataPtr);
+		dataPtr = rttiWriteElem(mUUID, dataPtr);
 
 		SPtr<ct::Texture>* customTexture = new (dataPtr)SPtr<ct::Texture>();
 		if (mCustomTexture.isLoaded(false))
@@ -211,6 +225,7 @@ namespace bs
 		dataPtr = rttiReadElem(mIsActive, dataPtr);
 		dataPtr = rttiReadElem(dirtyFlags, dataPtr);
 		dataPtr = rttiReadElem(mBounds, dataPtr);
+		dataPtr = rttiReadElem(mUUID, dataPtr);
 
 		SPtr<Texture>* texture = (SPtr<Texture>*)dataPtr;
 

+ 4 - 1
Source/BansheeCore/Source/BsResources.cpp

@@ -515,7 +515,10 @@ namespace bs
 			if(overwrite)
 				FileSystem::remove(filePath);
 			else
-				BS_EXCEPT(InvalidParametersException, "Another file exists at the specified location.");
+			{
+				LOGERR("Another file exists at the specified location. Not saving.");
+				return;
+			}
 		}
 
 		if (!resource->mKeepSourceData)

+ 3 - 1
Source/BansheeEngine/CMakeSources.cmake

@@ -88,6 +88,7 @@ set(BS_BANSHEEENGINE_INC_RENDERER
 	"Include/BsRenderQueue.h"
 	"Include/BsRendererUtility.h"
 	"Include/BsStandardPostProcessSettings.h"	
+	"Include/BsReflectionCubemapCache.h"
 )
 
 set(BS_BANSHEEENGINE_SRC_RTTI
@@ -174,7 +175,8 @@ set(BS_BANSHEEENGINE_SRC_RENDERER
 	"Source/BsRendererMaterialManager.cpp"
 	"Source/BsRenderQueue.cpp"
 	"Source/BsRendererUtility.cpp"
-	"Source/BsStandardPostProcessSettings.cpp"	
+	"Source/BsStandardPostProcessSettings.cpp"
+	"Source/BsReflectionCubemapCache.cpp"
 )
 
 set(BS_BANSHEEENGINE_SRC_INPUT

+ 60 - 0
Source/BansheeEngine/Include/BsReflectionCubemapCache.h

@@ -0,0 +1,60 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsParamBlocks.h"
+#include "BsRendererMaterial.h"
+#include "BsModule.h"
+
+namespace bs { namespace ct
+{
+	/** @addtogroup Renderer-Engine-Internal
+	 *  @{
+	 */
+
+	/** 
+	 * Keeps track of all reflection probes and maintains a cache of their textures so they may be generated during
+	 * development time, and then just loaded during runtime.
+	 */
+	class BS_EXPORT ReflectionCubemapCache : public Module<ReflectionCubemapCache>
+	{
+	public:
+		~ReflectionCubemapCache();
+
+		/** Initializes the cache with a path at which it can find the saved textures. */
+		void initCache(const Path& path);
+
+		/** Notifies the manager that the probe with the provided UUID is dirty and needs to be re-created. */
+		void notifyDirty(const String& uuid);
+
+		/** Checks if the texture with the specified UUID is marked as dirty, or if it hasn't been cached yet at all. */
+		bool isDirty(const String& uuid) const;
+
+		/** Sets a cached texture and associates it with the provided UUID. */
+		void setCachedTexture(const String& uuid, const SPtr<Texture>& texture);
+
+		/** Returns a texture that was assigned to the specified UUID when createCachedTexture() was called. */
+		SPtr<Texture> getCachedTexture(const String& uuid) const;
+
+		/** Unloads in-memory data for a texture mapped with the specified UUID. */
+		void unloadCachedTexture(const String& uuid);
+
+		/** Saves all cached textures in a folder at the provided path. */
+		void saveCache(const Path& path);
+
+	private:
+		/** Information about a single loaded texture. */
+		struct TextureInfo
+		{
+			SPtr<Texture> texture;
+			bool dirty;
+			bool needsSaving;
+		};
+
+		Path mDataPath;
+		mutable UnorderedMap<String, TextureInfo> mTextureInfos;
+	};
+
+	/** @} */
+}}

+ 4 - 1
Source/BansheeEngine/Source/BsApplication.cpp

@@ -21,6 +21,7 @@
 #include "BsPlatform.h"
 #include "BsEngineShaderIncludeHandler.h"
 #include "BsEngineConfig.h"
+#include "BsReflectionCubemapCache.h"
 
 namespace bs
 {
@@ -42,6 +43,7 @@ namespace bs
 		ShortcutManager::shutDown();
 		GUIManager::shutDown();
 		SpriteManager::shutDown();
+		ct::ReflectionCubemapCache::shutDown();
 		BuiltinResources::shutDown();
 		RendererMaterialManager::shutDown();
 		VirtualInput::shutDown();
@@ -56,6 +58,7 @@ namespace bs
 
 		VirtualInput::startUp();
 		BuiltinResources::startUp();
+		ct::ReflectionCubemapCache::startUp();
 		RendererMaterialManager::startUp();
 		RendererManager::instance().initialize();
 		SpriteManager::startUp();
@@ -199,4 +202,4 @@ namespace bs
 	{
 		return static_cast<Application&>(Application::instance());
 	}
-}
+}

+ 219 - 0
Source/BansheeEngine/Source/BsReflectionCubemapCache.cpp

@@ -0,0 +1,219 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsReflectionCubemapCache.h"
+#include "BsFileSystem.h"
+#include "BsIReflectable.h"
+#include "BsRTTIType.h"
+#include "BsFileSerializer.h"
+
+namespace bs { namespace ct
+{
+	struct CachedTextureData : IReflectable
+	{
+		TextureType type;
+		UINT32 numFaces;
+		UINT32 numMips;
+		Vector<SPtr<PixelData>> pixelData;
+
+		/************************************************************************/
+		/* 								RTTI		                     		*/
+		/************************************************************************/
+	public:
+		friend class CachedTextureDataRTTI;
+		static RTTITypeBase* getRTTIStatic();
+		RTTITypeBase* getRTTI() const override;
+	};
+
+	class CachedTextureDataRTTI : public RTTIType <CachedTextureData, IReflectable, CachedTextureDataRTTI>
+	{
+	private:
+		BS_BEGIN_RTTI_MEMBERS
+			BS_RTTI_MEMBER_PLAIN(type, 0)
+			BS_RTTI_MEMBER_PLAIN(numFaces, 1)
+			BS_RTTI_MEMBER_PLAIN(numMips, 2)
+			BS_RTTI_MEMBER_REFLPTR_ARRAY(pixelData, 3)
+		BS_END_RTTI_MEMBERS
+	public:
+		CachedTextureDataRTTI()
+			:mInitMembers(this)
+		{ }
+
+		const String& getRTTIName() override
+		{
+			static String name = "CachedTextureData";
+			return name;
+		}
+
+		UINT32 getRTTIId() override
+		{
+			return TID_CachedTextureData;
+		}
+
+		SPtr<IReflectable> newRTTIObject() override
+		{
+			return bs_shared_ptr_new<CachedTextureData>();
+		}
+	};
+
+	RTTITypeBase* CachedTextureData::getRTTIStatic()
+	{
+		return CachedTextureDataRTTI::instance();
+	}
+
+	RTTITypeBase* CachedTextureData::getRTTI() const
+	{
+		return getRTTIStatic();
+	}
+
+	ReflectionCubemapCache::~ReflectionCubemapCache()
+	{
+		for (auto& entry : mTextureInfos)
+		{
+			if(entry.second.needsSaving)
+			{
+				LOGWRN("Shutting down reflection cubemap cache manager but not all textures were saved on disk before "
+					   "shutdown.");
+				break;
+			}
+		}
+	}
+
+	void ReflectionCubemapCache::initCache(const Path& path)
+	{
+		mDataPath = path;
+
+		auto visitFile = [&](const Path& filePath) -> bool
+		{
+			String uuid = filePath.getFilename(false);
+
+			TextureInfo& texInfo = mTextureInfos[uuid];
+			texInfo.texture = nullptr;
+			texInfo.needsSaving = false;
+			texInfo.dirty = false;
+
+			return true;
+		};
+
+		FileSystem::iterate(path, visitFile, nullptr, false);
+	}
+
+	void ReflectionCubemapCache::notifyDirty(const String& uuid)
+	{
+		auto iterFind = mTextureInfos.find(uuid);
+		if (iterFind != mTextureInfos.end())
+			iterFind->second.dirty = true;
+	}
+
+	bool ReflectionCubemapCache::isDirty(const String& uuid) const
+	{
+		auto iterFind = mTextureInfos.find(uuid);
+		if (iterFind != mTextureInfos.end())
+			return iterFind->second.dirty;
+
+		return true;
+	}
+
+	void ReflectionCubemapCache::setCachedTexture(const String& uuid, const SPtr<Texture>& texture)
+	{
+		TextureInfo& texInfo = mTextureInfos[uuid];
+		texInfo.texture = texture;
+		texInfo.needsSaving = true;
+		texInfo.dirty = false;
+	}
+
+	SPtr<Texture> ReflectionCubemapCache::getCachedTexture(const String& uuid) const
+	{
+		auto iterFind = mTextureInfos.find(uuid);
+		if (iterFind == mTextureInfos.end())
+			return nullptr;
+
+		TextureInfo& texInfo = iterFind->second;
+		if (texInfo.texture != nullptr)
+			return texInfo.texture;
+
+		Path filePath = mDataPath + uuid + ".asset";
+		if (!FileSystem::exists(filePath))
+			return nullptr;
+
+		FileDecoder fd(filePath);
+		SPtr<CachedTextureData> textureData = std::static_pointer_cast<CachedTextureData>(fd.decode());
+
+		if (textureData == nullptr || textureData->pixelData.size() == 0 || textureData->pixelData[0] == nullptr)
+			return nullptr;
+
+		TEXTURE_DESC desc;
+		desc.type = textureData->type;
+		desc.format = textureData->pixelData[0]->getFormat();
+		desc.width = textureData->pixelData[0]->getWidth();
+		desc.height = textureData->pixelData[0]->getHeight();
+		desc.depth = textureData->pixelData[0]->getDepth();
+
+		if (desc.type == TEX_TYPE_CUBE_MAP)
+			desc.numArraySlices = textureData->numFaces / 6;
+		else
+			desc.numArraySlices = textureData->numFaces;
+
+		desc.numMips = textureData->numMips;
+		
+		SPtr<Texture> texture = Texture::create(desc);
+		for(UINT32 face = 0; face < textureData->numFaces; face++)
+		{
+			for(UINT32 mip = 0; mip < textureData->numMips; mip++)
+			{
+				UINT32 srcIdx = face * textureData->numMips + mip;
+				if (srcIdx >= textureData->pixelData.size())
+					continue;
+
+				texture->writeData(*textureData->pixelData[srcIdx], mip, face, true);
+			}
+		}
+
+		texInfo.texture = texture;
+		return texture;
+	}
+
+	void ReflectionCubemapCache::unloadCachedTexture(const String& uuid)
+	{
+		auto iterFind = mTextureInfos.find(uuid);
+		if (iterFind != mTextureInfos.end())
+		{
+			// Not allowed to unload if it requires saving (should only happen during development time)
+			if (!iterFind->second.needsSaving)
+				iterFind->second.texture = nullptr;
+		}
+	}
+
+	void ReflectionCubemapCache::saveCache(const Path& path)
+	{
+		for(auto& entry : mTextureInfos)
+		{
+			TextureInfo& texInfo = entry.second;
+			if (!texInfo.needsSaving)
+				continue;
+
+			auto& texProps = texInfo.texture->getProperties();
+			
+			SPtr<CachedTextureData> textureData = bs_shared_ptr_new<CachedTextureData>();
+			textureData->type = texProps.getTextureType();
+			textureData->numFaces = texProps.getNumFaces();
+			textureData->numMips = texProps.getNumMipmaps() + 1;
+
+			for (UINT32 face = 0; face < textureData->numFaces; face++)
+			{
+				for (UINT32 mip = 0; mip < textureData->numMips; mip++)
+				{
+					SPtr<PixelData> pixelData = texProps.allocBuffer(face, mip);
+					texInfo.texture->readData(*pixelData, mip, face);
+
+					textureData->pixelData.push_back(pixelData);
+				}
+			}
+
+			Path filePath = path + entry.first + ".asset";
+			FileEncoder fe(filePath);
+			fe.encode(textureData.get());
+
+			texInfo.needsSaving = false;
+		}
+	}
+}}

+ 30 - 0
Source/BansheeMono/Include/BsMonoArray.h

@@ -135,6 +135,30 @@ namespace bs
 			return ScriptArray(*T::getMetaData()->scriptClass, size);
 		}
 
+		template<>
+		inline ScriptArray ScriptArray_create<UINT8>(UINT32 size)
+		{
+			return ScriptArray(MonoUtil::getByteClass(), size);
+		}
+
+		template<>
+		inline ScriptArray ScriptArray_create<INT8>(UINT32 size)
+		{
+			return ScriptArray(MonoUtil::getSByteClass(), size);
+		}
+
+		template<>
+		inline ScriptArray ScriptArray_create<UINT16>(UINT32 size)
+		{
+			return ScriptArray(MonoUtil::getUINT16Class(), size);
+		}
+
+		template<>
+		inline ScriptArray ScriptArray_create<INT16>(UINT32 size)
+		{
+			return ScriptArray(MonoUtil::getINT16Class(), size);
+		}
+
 		template<>
 		inline ScriptArray ScriptArray_create<UINT32>(UINT32 size)
 		{
@@ -177,6 +201,12 @@ namespace bs
 			return ScriptArray(MonoUtil::getFloatClass(), size);
 		}
 
+		template<>
+		inline ScriptArray ScriptArray_create<double>(UINT32 size)
+		{
+			return ScriptArray(MonoUtil::getDoubleClass(), size);
+		}
+
 		template<>
 		inline ScriptArray ScriptArray_create<bool>(UINT32 size)
 		{

+ 3 - 3
Source/BansheeUtility/Include/BsDebug.h

@@ -90,13 +90,13 @@ namespace bs
 	BS_UTILITY_EXPORT Debug& gDebug();
 
 /** Shortcut for logging a message in the debug channel. */
-#define LOGDBG(x) bs::gDebug().logDebug((x) + String("\n\n\t\t in ") + __PRETTY_FUNCTION__ + " [" + __FILE__ + ":" + toString(__LINE__) + "]");
+#define LOGDBG(x) bs::gDebug().logDebug((x) + String("\n\t\t in ") + __PRETTY_FUNCTION__ + " [" + __FILE__ + ":" + toString(__LINE__) + "]\n");
 
 /** Shortcut for logging a message in the warning channel. */
-#define LOGWRN(x) bs::gDebug().logWarning((x) + String("\n\n\t\t in ") + __PRETTY_FUNCTION__ + " [" + __FILE__ + ":" + toString(__LINE__) + "]");
+#define LOGWRN(x) bs::gDebug().logWarning((x) + String("\n\t\t in ") + __PRETTY_FUNCTION__ + " [" + __FILE__ + ":" + toString(__LINE__) + "]\n");
 
 /** Shortcut for logging a message in the error channel. */
-#define LOGERR(x) bs::gDebug().logError((x) + String("\n\n\t\t in ") + __PRETTY_FUNCTION__ + " [" + __FILE__ + ":" + toString(__LINE__) + "]");
+#define LOGERR(x) bs::gDebug().logError((x) + String("\n\t\t in ") + __PRETTY_FUNCTION__ + " [" + __FILE__ + ":" + toString(__LINE__) + "]\n");
 
 /** Shortcut for logging a verbose message in the debug channel. Verbose messages can be ignored unlike other log messages. */
 #define LOGDBG_VERBOSE(x)

+ 2 - 2
Source/RenderBeast/CMakeSources.cmake

@@ -11,8 +11,8 @@ set(BS_RENDERBEAST_INC_NOFILTER
 	"Include/BsPostProcessing.h"
 	"Include/BsRendererCamera.h"
 	"Include/BsRendererObject.h"
-	"Include/BsReflectionCubemap.h"
 	"Include/BsLightGrid.h"
+	"Include/BsReflectionProbes.h"
 )
 
 set(BS_RENDERBEAST_SRC_NOFILTER
@@ -27,8 +27,8 @@ set(BS_RENDERBEAST_SRC_NOFILTER
 	"Source/BsPostProcessing.cpp"
 	"Source/BsRendererCamera.cpp"
 	"Source/BsRendererObject.cpp"
-	"Source/BsReflectionCubemap.cpp"
 	"Source/BsLightGrid.cpp"
+	"Source/BsReflectionProbes.cpp"
 )
 
 source_group("Header Files" FILES ${BS_RENDERBEAST_INC_NOFILTER})

+ 4 - 2
Source/RenderBeast/Include/BsReflectionCubemap.h → Source/RenderBeast/Include/BsReflectionProbes.h

@@ -35,8 +35,8 @@ namespace bs { namespace ct
 		GpuParamTexture mInputTexture;
 	};
 
-	/** Helper class that handles reflection cubemap generation. */
-	class ReflectionCubemap
+	/** Helper class that handles generation and processing of textures used for reflection probes. */
+	class ReflectionProbes
 	{
 	public:
 		/**
@@ -44,6 +44,8 @@ namespace bs { namespace ct
 		 * evaluating specular reflections.
 		 */
 		static void filterCubemapForSpecular(const SPtr<Texture>& cubemap);
+
+		static const UINT32 REFLECTION_CUBEMAP_SIZE;
 	};
 
 	/** @} */

+ 31 - 2
Source/RenderBeast/Include/BsRenderBeast.h

@@ -65,6 +65,18 @@ namespace bs
 			const RendererAnimationData& animData;
 		};
 
+		/** Information about an active reflection probe. */
+		struct ReflProbeInfo
+		{
+			ReflectionProbe* probe;
+			UINT32 arrayIdx;
+			SPtr<Texture> texture;
+			bool customTexture : 1;
+			bool textureDirty : 1;
+			bool arrayDirty : 1;
+			bool errorFlagged : 1;
+		};
+
 	public:
 		RenderBeast();
 		~RenderBeast() { }
@@ -118,6 +130,15 @@ namespace bs
 		/** @copydoc Renderer::notifyRenderableRemoved */
 		void notifyRenderableRemoved(Renderable* renderable) override;
 
+		/** @copydoc Renderer::notifyReflectionProbeAdded */
+		void notifyReflectionProbeAdded(ReflectionProbe* probe) override;
+
+		/** @copydoc Renderer::notifyReflectionProbeUpdated */
+		void notifyReflectionProbeUpdated(ReflectionProbe* probe) override;
+
+		/** @copydoc Renderer::notifyReflectionProbeRemoved */
+		void notifyReflectionProbeRemoved(ReflectionProbe* probe) override;
+
 		/** 
 		 * Updates (or adds) renderer specific data for the specified camera. Should be called whenever camera properties
 		 * change. 
@@ -180,12 +201,12 @@ namespace bs
 		/** 
 		 * Captures the scene at the specified location into a cubemap. 
 		 * 
+		 * @param[in]	cubemap		Cubemap to store the results in.
 		 * @param[in]	position	Position to capture the scene at.
 		 * @param[in]	hdr			If true scene will be captured in a format that supports high dynamic range.
-		 * @param[in]	size		Cubemap face width/height in pixels.
 		 * @param[in]	frameInfo	Global information about the the frame currently being rendered.
 		 */
-		SPtr<Texture> captureSceneCubeMap(const Vector3& position, bool hdr, UINT32 size, const FrameInfo& frameInfo);
+		void captureSceneCubeMap(const SPtr<Texture>& cubemap, const Vector3& position, bool hdr, const FrameInfo& frameInfo);
 
 		/**	Creates data used by the renderer on the core thread. */
 		void initializeCore();
@@ -193,6 +214,9 @@ namespace bs
 		/**	Destroys data used by the renderer on the core thread. */
 		void destroyCore();
 
+		/** Updates reflection probes, rendering ones that are dirty and updating the global probe cubemap array. */
+		void updateReflectionProbes(const FrameInfo& frameInfo);
+
 		/**
 		 * Checks all sampler overrides in case material sampler states changed, and updates them.
 		 *
@@ -216,6 +240,11 @@ namespace bs
 		Vector<Sphere> mPointLightWorldBounds;
 		Vector<Sphere> mSpotLightWorldBounds;
 
+		Vector<ReflProbeInfo> mReflProbes;
+		Vector<Sphere> mReflProbeWorldBounds;
+		Vector<bool> mCubemapArrayUsedSlots;
+		SPtr<Texture> mCubemapArrayTex;
+
 		SPtr<RenderBeastOptions> mCoreOptions;
 
 		DefaultMaterial* mDefaultMaterial;

+ 4 - 2
Source/RenderBeast/Source/BsReflectionCubemap.cpp → Source/RenderBeast/Source/BsReflectionProbes.cpp

@@ -1,6 +1,6 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#include "BsReflectionCubemap.h"
+#include "BsReflectionProbes.h"
 #include "BsTexture.h"
 #include "BsGpuParamsSet.h"
 #include "BsRendererCamera.h"
@@ -37,7 +37,9 @@ namespace bs { namespace ct
 		gRendererUtility().drawScreenQuad();
 	}
 
-	void ReflectionCubemap::filterCubemapForSpecular(const SPtr<Texture>& cubemap)
+	const UINT32 ReflectionProbes::REFLECTION_CUBEMAP_SIZE = 256;
+
+	void ReflectionProbes::filterCubemapForSpecular(const SPtr<Texture>& cubemap)
 	{
 		static ReflectionCubemapFilterMat filterMaterial;
 

+ 184 - 20
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -31,7 +31,9 @@
 #include "BsGpuBuffer.h"
 #include "BsGpuParamsSet.h"
 #include "BsRendererExtension.h"
-#include "BsReflectionCubemap.h"
+#include "BsReflectionCubemapCache.h"
+#include "BsReflectionProbe.h"
+#include "BsReflectionProbes.h"
 #include "BsMeshData.h"
 #include "BsLightGrid.h"
 
@@ -446,6 +448,89 @@ namespace bs { namespace ct
 		updateCameraData(camera, true);
 	}
 
+	void RenderBeast::notifyReflectionProbeAdded(ReflectionProbe* probe)
+	{
+		UINT32 probeId = (UINT32)mReflProbes.size();
+		probe->setRendererId(probeId);
+
+		mReflProbes.push_back(ReflProbeInfo());
+		ReflProbeInfo& probeInfo = mReflProbes.back();
+		probeInfo.probe = probe;
+		probeInfo.arrayIdx = -1;
+		probeInfo.texture = probe->getCustomTexture();
+		probeInfo.customTexture = probeInfo.texture != nullptr;
+		probeInfo.textureDirty = ReflectionCubemapCache::instance().isDirty(probe->getUUID());
+		probeInfo.arrayDirty = true;
+		probeInfo.errorFlagged = false;
+
+		mReflProbeWorldBounds.push_back(probe->getBounds());
+
+		// Find a spot in cubemap array
+		if(probe->getType() != ReflectionProbeType::Plane)
+		{
+			UINT32 numArrayEntries = (UINT32)mCubemapArrayUsedSlots.size();
+			for(UINT32 i = 0; i < numArrayEntries; i++)
+			{
+				if(!mCubemapArrayUsedSlots[i])
+				{
+					probeInfo.arrayIdx = i;
+					mCubemapArrayUsedSlots[i] = true;
+					break;
+				}
+			}
+
+			// No empty slot was found
+			if (probeInfo.arrayIdx == -1)
+			{
+				probeInfo.arrayIdx = numArrayEntries;
+				mCubemapArrayUsedSlots.push_back(true);
+			}
+		}
+	}
+
+	void RenderBeast::notifyReflectionProbeUpdated(ReflectionProbe* probe)
+	{
+		// Should only get called if transform changes, any other major changes and ReflProbeInfo entry gets rebuild
+		UINT32 probeId = probe->getRendererId();
+		mReflProbeWorldBounds[probeId] = probe->getBounds();
+
+		ReflProbeInfo& probeInfo = mReflProbes[probeId];
+		probeInfo.arrayDirty = true;
+
+		if (!probeInfo.customTexture)
+		{
+			ReflectionCubemapCache::instance().notifyDirty(probe->getUUID());
+			probeInfo.textureDirty = true;
+		}
+	}
+
+	void RenderBeast::notifyReflectionProbeRemoved(ReflectionProbe* probe)
+	{
+		UINT32 probeId = probe->getRendererId();
+		UINT32 arrayIdx = mReflProbes[probeId].arrayIdx;
+
+		ReflectionProbe* lastProbe = mReflProbes.back().probe;
+		UINT32 lastProbeId = lastProbe->getRendererId();
+
+		if (probeId != lastProbeId)
+		{
+			// Swap current last element with the one we want to erase
+			std::swap(mReflProbes[probeId], mReflProbes[lastProbeId]);
+			std::swap(mReflProbeWorldBounds[probeId], mReflProbeWorldBounds[lastProbeId]);
+
+			probe->setRendererId(probeId);
+		}
+
+		// Last element is the one we want to erase
+		mRadialLights.erase(mRadialLights.end() - 1);
+		mPointLightWorldBounds.erase(mPointLightWorldBounds.end() - 1);
+
+		if (arrayIdx != -1)
+			mCubemapArrayUsedSlots[arrayIdx] = false;
+
+		ReflectionCubemapCache::instance().unloadCachedTexture(probe->getUUID());
+	}
+
 	SPtr<PostProcessSettings> RenderBeast::createPostProcessSettings() const
 	{
 		return bs_shared_ptr_new<StandardPostProcessSettings>();
@@ -675,8 +760,8 @@ namespace bs { namespace ct
 		
 		FrameInfo frameInfo(delta, animData);
 
-		//if (dbgSkyTex == nullptr)
-		//	dbgSkyTex = captureSceneCubeMap(Vector3(0, 2, 0), true, 1024, frameInfo);
+		// Update reflection probes
+		updateReflectionProbes(frameInfo);
 
 		// Gather all views
 		Vector<RendererCamera*> views;
@@ -1080,18 +1165,101 @@ namespace bs { namespace ct
 				element.morphVertexDeclaration);
 	}
 
-	SPtr<Texture> RenderBeast::captureSceneCubeMap(const Vector3& position, bool hdr, UINT32 size, 
-												   const FrameInfo& frameInfo)
+	void RenderBeast::updateReflectionProbes(const FrameInfo& frameInfo)
 	{
-		TEXTURE_DESC cubeMapDesc;
-		cubeMapDesc.type = TEX_TYPE_CUBE_MAP;
-		cubeMapDesc.format = hdr ? PF_FLOAT16_RGBA : PF_R8G8B8A8;
-		cubeMapDesc.width = size;
-		cubeMapDesc.height = size;
-		cubeMapDesc.numMips = PixelUtil::getMaxMipmaps(size, size, 1, cubeMapDesc.format);
-		cubeMapDesc.usage = TU_RENDERTARGET;
+		UINT32 numProbes = (UINT32)mReflProbes.size();
+
+		bs_frame_mark();
+		{		
+			bool forceArrayUpdate = false;
+			if(mCubemapArrayTex == nullptr || mCubemapArrayTex->getProperties().getNumArraySlices() < numProbes)
+			{
+				TEXTURE_DESC cubeMapDesc;
+				cubeMapDesc.type = TEX_TYPE_CUBE_MAP;
+				cubeMapDesc.format = PF_FLOAT16_RGBA;
+				cubeMapDesc.width = ReflectionProbes::REFLECTION_CUBEMAP_SIZE;
+				cubeMapDesc.height = ReflectionProbes::REFLECTION_CUBEMAP_SIZE;
+				cubeMapDesc.numMips = PixelUtil::getMaxMipmaps(cubeMapDesc.width, cubeMapDesc.height, 1, cubeMapDesc.format);
+				cubeMapDesc.numArraySlices = numProbes + 4; // Keep a few empty entries
+
+				mCubemapArrayTex = Texture::create(cubeMapDesc);
 
-		SPtr<Texture> cubemap = Texture::create(cubeMapDesc);
+				forceArrayUpdate = true;
+			}
+
+			auto& cubemapArrayProps = mCubemapArrayTex->getProperties();
+
+			FrameQueue<UINT32> emptySlots;
+			for (UINT32 i = 0; i < numProbes; i++)
+			{
+				ReflProbeInfo& probeInfo = mReflProbes[i];
+				if (!probeInfo.customTexture)
+				{
+					if (probeInfo.probe->getType() != ReflectionProbeType::Plane)
+					{
+						if (probeInfo.texture == nullptr)
+							probeInfo.texture = ReflectionCubemapCache::instance().getCachedTexture(probeInfo.probe->getUUID());
+
+						if (probeInfo.texture == nullptr || probeInfo.textureDirty)
+						{
+							TEXTURE_DESC cubemapDesc;
+							cubemapDesc.type = TEX_TYPE_CUBE_MAP;
+							cubemapDesc.format = PF_FLOAT16_RGBA;
+							cubemapDesc.width = ReflectionProbes::REFLECTION_CUBEMAP_SIZE;
+							cubemapDesc.height = ReflectionProbes::REFLECTION_CUBEMAP_SIZE;
+							cubemapDesc.numMips = PixelUtil::getMaxMipmaps(cubemapDesc.width, cubemapDesc.height, 1, cubemapDesc.format);
+
+							probeInfo.texture = Texture::create(cubemapDesc);
+
+							captureSceneCubeMap(probeInfo.texture, probeInfo.probe->getPosition(), true, frameInfo);
+							ReflectionProbes::filterCubemapForSpecular(probeInfo.texture);
+
+							ReflectionCubemapCache::instance().setCachedTexture(probeInfo.probe->getUUID(), probeInfo.texture);
+						}
+					}
+				}
+
+				probeInfo.textureDirty = false;
+
+				if(probeInfo.probe->getType() != ReflectionProbeType::Plane && (probeInfo.arrayDirty || forceArrayUpdate))
+				{
+					auto& srcProps = probeInfo.texture->getProperties();
+					bool isValid = srcProps.getWidth() == ReflectionProbes::REFLECTION_CUBEMAP_SIZE && 
+						srcProps.getHeight() == ReflectionProbes::REFLECTION_CUBEMAP_SIZE &&
+						srcProps.getNumMipmaps() == cubemapArrayProps.getNumMipmaps() &&
+						srcProps.getTextureType() == TEX_TYPE_CUBE_MAP;
+
+					if(!isValid)
+					{
+						if (!probeInfo.errorFlagged)
+						{
+							String errMsg = StringUtil::format("Cubemap texture invalid to use as a reflection cubemap. " 
+								"Check texture size (must be {0}x{0}) and mip-map count", 
+								ReflectionProbes::REFLECTION_CUBEMAP_SIZE);
+
+							LOGERR(errMsg);
+							probeInfo.errorFlagged = true;
+						}
+					}
+					else
+					{
+						for(UINT32 face = 0; face < 6; face++)
+							for(UINT32 mip = 0; mip <= srcProps.getNumMipmaps(); mip++)
+								probeInfo.texture->copy(mCubemapArrayTex, face, mip, probeInfo.arrayIdx * 6 + face, mip);
+					}
+
+					probeInfo.arrayDirty = false;
+				}
+
+				// Note: Consider pruning the reflection cubemap array if empty slot count becomes too high
+			}
+		}
+		bs_frame_clear();
+	}
+
+	void RenderBeast::captureSceneCubeMap(const SPtr<Texture>& cubemap, const Vector3& position, bool hdr, const FrameInfo& frameInfo)
+	{
+		auto& texProps = cubemap->getProperties();
 
 		Matrix4 projTransform = Matrix4::projectionPerspective(Degree(90.0f), 1.0f, 0.05f, 1000.0f);
 		ConvexVolume localFrustum(projTransform);
@@ -1104,9 +1272,9 @@ namespace bs { namespace ct
 		viewDesc.target.clearStencilValue = 0;
 
 		viewDesc.target.nrmViewRect = Rect2(0, 0, 1.0f, 1.0f);
-		viewDesc.target.viewRect = Rect2I(0, 0, size, size);
-		viewDesc.target.targetWidth = size;
-		viewDesc.target.targetHeight = size;
+		viewDesc.target.viewRect = Rect2I(0, 0, texProps.getWidth(), texProps.getHeight());
+		viewDesc.target.targetWidth = texProps.getWidth();
+		viewDesc.target.targetHeight = texProps.getHeight();
 		viewDesc.target.numSamples = 1;
 
 		viewDesc.isOverlay = false;
@@ -1210,10 +1378,6 @@ namespace bs { namespace ct
 
 		RendererCamera* viewPtrs[] = { &views[0], &views[1], &views[2], &views[3], &views[4], &views[5] };
 		renderViews(viewPtrs, 6, frameInfo);
-
-		ReflectionCubemap::filterCubemapForSpecular(cubemap);
-
-		return cubemap;
 	}
 
 	void RenderBeast::refreshSamplerOverrides(bool force)