Explorar o código

Deferred destruction of game objects
Managed Component Update() methods gets called

Marko Pintera %!s(int64=11) %!d(string=hai) anos
pai
achega
734f25c36c

+ 4 - 1
BansheeCore/Include/BsComponent.h

@@ -33,8 +33,11 @@ namespace BansheeEngine
 		 * @brief	Removes the component from parent SceneObject and deletes it. All
 		 * 			the references to this component will be marked as destroyed and you
 		 * 			will get an exception if you try to use them.
+		 *
+		 * @param	[in] immediate	If true the destruction will be performed immediately, otherwise
+		 *							it will be delayed until the end of the current frame (preferred option).
 		 */
-		void destroy();
+		void destroy(bool immediate = false);
 
 	protected:
 		friend class SceneObject;

+ 5 - 0
BansheeCore/Include/BsCoreSceneManager.h

@@ -23,6 +23,11 @@ namespace BansheeEngine
 		 */
 		HSceneObject getRootNode() const { return mRootNode; }
 
+		/**
+		 * @brief	Destroys all scene objects in the scene.
+		 */
+		void clearScene();
+
 		/**
 		 * @brief	Called every frame.
 		 *

+ 11 - 0
BansheeCore/Include/BsGameObjectManager.h

@@ -69,6 +69,16 @@ namespace BansheeEngine
 		 */
 		void remapId(UINT64 oldId, UINT64 newId);
 
+		/**
+		 * @brief	Queues the object to be destroyed at the end of a GameObject update cycle.
+		 */
+		void queueForDestroy(const GameObjectHandleBase& object);
+
+		/**
+		 * @brief	Destroys any GameObjects that were queued for destruction.
+		 */
+		void destroyQueuedObjects();
+
 		/************************************************************************/
 		/* 							DESERIALIZATION                      		*/
 		/************************************************************************/
@@ -123,6 +133,7 @@ namespace BansheeEngine
 	private:
 		UINT64 mNextAvailableID; // 0 is not a valid ID
 		Map<UINT64, GameObjectHandleBase> mObjects;
+		Map<UINT64, GameObjectHandleBase> mQueuedForDestroy;
 
 		GameObject* mActiveDeserializedObject;
 		bool mIsDeserializationActive;

+ 24 - 4
BansheeCore/Include/BsSceneObject.h

@@ -38,8 +38,12 @@ namespace BansheeEngine
 
 		/**
 		 * @brief	Destroys this object and any of its held components.
+		 *
+		 * @param [in]	immediate	If true, the object will be deallocated and become unusable
+		 *							right away. Otherwise the deallocation will be delayed to the end of
+		 *							frame (preferred method).
 		 */
-		void destroy();
+		void destroy(bool immediate = false);
 
 		/**
 		 * @copydoc	GameObject::_setInstanceData
@@ -54,7 +58,17 @@ namespace BansheeEngine
 		SceneObject(const String& name);
 
 		static HSceneObject createInternal(const String& name);
-		void destroyInternal();
+
+		/**
+		 * @brief	Destroys this object and any of its held components.
+		 *
+		 * @param [in]	immediate	If true, the object will be deallocated and become unusable
+		 *							right away. Otherwise the deallocation will be delayed to the end of
+		 *							frame (preferred method).
+		 *
+		 * @note	Unlike "destroy", does not remove the object from its parent.
+		 */
+		void destroyInternal(bool immediate = false);
 
 	private:
 		HSceneObject mThisHandle;
@@ -468,15 +482,21 @@ namespace BansheeEngine
 		 * @brief	Removes the component from this object, and deallocates it.
 		 *
 		 * @param [in]	component	The component to destroy.
+		 * @param [in]	immediate	If true, the component will be deallocated and become unusable
+		 *							right away. Otherwise the deallocation will be delayed to the end of
+		 *							frame (preferred method).
 		 */
-		void destroyComponent(const HComponent& component);
+		void destroyComponent(const HComponent& component, bool immediate = false);
 
 		/**
 		 * @brief	Removes the component from this object, and deallocates it.
 		 *
 		 * @param [in]	component	The component to destroy.
+		 * @param [in]	immediate	If true, the component will be deallocated and become unusable
+		 *							right away. Otherwise the deallocation will be delayed to the end of
+		 *							frame (preferred method).
 		 */
-		void destroyComponent(Component* component);
+		void destroyComponent(Component* component, bool immediate = false);
 
 		/**
 		 * @brief	Returns all components on this object.

+ 2 - 2
BansheeCore/Source/BsComponent.cpp

@@ -15,9 +15,9 @@ namespace BansheeEngine
 
 	}
 
-	void Component::destroy()
+	void Component::destroy(bool immediate)
 	{
-		SO()->destroyComponent(this);
+		SO()->destroyComponent(this, immediate);
 	}
 
 	RTTITypeBase* Component::getRTTIStatic()

+ 11 - 2
BansheeCore/Source/BsCoreSceneManager.cpp

@@ -1,6 +1,7 @@
 #include "BsCoreSceneManager.h"
 #include "BsSceneObject.h"
 #include "BsComponent.h"
+#include "BsGameObjectManager.h"
 
 namespace BansheeEngine
 {
@@ -11,8 +12,14 @@ namespace BansheeEngine
 
 	CoreSceneManager::~CoreSceneManager()
 	{
-		if(mRootNode != nullptr)
-			mRootNode->destroy();
+		if (mRootNode != nullptr && !mRootNode.isDestroyed())
+			mRootNode->destroy(true);
+	}
+
+	void CoreSceneManager::clearScene()
+	{
+		GameObjectManager::instance().destroyQueuedObjects();
+		mRootNode->destroy(true);
 	}
 
 	void CoreSceneManager::_update()
@@ -35,6 +42,8 @@ namespace BansheeEngine
 			for(UINT32 i = 0; i < currentGO->getNumChildren(); i++)
 				todo.push(currentGO->getChild(i));
 		}
+
+		GameObjectManager::instance().destroyQueuedObjects();
 	}
 
 	void CoreSceneManager::registerNewSO(const HSceneObject& node) 

+ 23 - 1
BansheeCore/Source/BsGameObjectManager.cpp

@@ -10,7 +10,9 @@ namespace BansheeEngine
 	}
 
 	GameObjectManager::~GameObjectManager()
-	{ }
+	{
+		destroyQueuedObjects();
+	}
 
 	GameObjectHandleBase GameObjectManager::getObject(UINT64 id) const 
 	{ 
@@ -49,6 +51,26 @@ namespace BansheeEngine
 		mObjects.erase(oldId);
 	}
 
+	void GameObjectManager::queueForDestroy(const GameObjectHandleBase& object)
+	{
+		if (object.isDestroyed())
+			return;
+
+		UINT64 instanceId = object->getInstanceId();
+		mQueuedForDestroy[instanceId] = object;
+	}
+
+	void GameObjectManager::destroyQueuedObjects()
+	{
+		for (auto& objPair : mQueuedForDestroy)
+		{
+			unregisterObject(objPair.second);
+			objPair.second.destroy();
+		}
+
+		mQueuedForDestroy.clear();
+	}
+
 	GameObjectHandleBase GameObjectManager::registerObject(const std::shared_ptr<GameObject>& object)
 	{
 		object->initialize(object, mNextAvailableID);

+ 35 - 15
BansheeCore/Source/BsSceneObject.cpp

@@ -23,7 +23,7 @@ namespace BansheeEngine
 		if(!mThisHandle.isDestroyed())
 		{
 			LOGWRN("Object is being deleted without being destroyed first?");
-			destroyInternal();
+			destroyInternal(true);
 		}
 	}
 
@@ -47,7 +47,7 @@ namespace BansheeEngine
 		return sceneObject;
 	}
 
-	void SceneObject::destroy()
+	void SceneObject::destroy(bool immediate)
 	{
 		// Parent is our owner, so when his reference to us is removed, delete might be called.
 		// So make sure this is the last thing we do.
@@ -57,27 +57,39 @@ namespace BansheeEngine
 				mParent->removeChild(mThisHandle);
 		}
 
-		destroyInternal();
+		destroyInternal(immediate);
 	}
 
-	void SceneObject::destroyInternal()
+	void SceneObject::destroyInternal(bool immediate)
 	{
 		for(auto iter = mChildren.begin(); iter != mChildren.end(); ++iter)
-			(*iter)->destroyInternal();
+			(*iter)->destroyInternal(immediate);
 
 		mChildren.clear();
 
 		for(auto iter = mComponents.begin(); iter != mComponents.end(); ++iter)
 		{
 			gCoreSceneManager().notifyComponentRemoved((*iter));
-			GameObjectManager::instance().unregisterObject(*iter);
-			(*iter).destroy();
+			(*iter)->onDestroyed();
+
+			if (immediate)
+			{
+				GameObjectManager::instance().unregisterObject(*iter);
+				(*iter).destroy();
+			}
+			else
+				GameObjectManager::instance().queueForDestroy(*iter);
 		}
 
 		mComponents.clear();
 
-		GameObjectManager::instance().unregisterObject(mThisHandle);
-		mThisHandle.destroy();
+		if (immediate)
+		{
+			GameObjectManager::instance().unregisterObject(mThisHandle);
+			mThisHandle.destroy();
+		}
+		else
+			GameObjectManager::instance().queueForDestroy(mThisHandle);
 	}
 
 	void SceneObject::_setInstanceData(GameObjectInstanceDataPtr& other)
@@ -447,7 +459,7 @@ namespace BansheeEngine
 		return HComponent();
 	}
 
-	void SceneObject::destroyComponent(const HComponent& component)
+	void SceneObject::destroyComponent(const HComponent& component, bool immediate)
 	{
 		if(component == nullptr)
 		{
@@ -460,17 +472,25 @@ namespace BansheeEngine
 		if(iter != mComponents.end())
 		{
 			gCoreSceneManager().notifyComponentRemoved((*iter));
-			GameObjectManager::instance().unregisterObject(component);
-
 			(*iter)->onDestroyed();
-			(*iter).destroy();
+			
 			mComponents.erase(iter);
+
+			if (immediate)
+			{
+				GameObjectManager::instance().unregisterObject(component);
+				(*iter).destroy();
+			}
+			else
+			{
+				GameObjectManager::instance().queueForDestroy(component);
+			}
 		}
 		else
 			LOGDBG("Trying to remove a component that doesn't exist on this SceneObject.");
 	}
 
-	void SceneObject::destroyComponent(Component* component)
+	void SceneObject::destroyComponent(Component* component, bool immediate)
 	{
 		auto iterFind = std::find_if(mComponents.begin(), mComponents.end(), 
 			[component](const HComponent& x) 
@@ -483,7 +503,7 @@ namespace BansheeEngine
 
 		if(iterFind != mComponents.end())
 		{
-			destroyComponent(*iterFind);
+			destroyComponent(*iterFind, immediate);
 		}
 	}
 

+ 1 - 1
BansheeEditor/Source/BsCmdRecordSO.cpp

@@ -59,7 +59,7 @@ namespace BansheeEngine
 		GameObjectManager::instance().setDeserializationMode(GODM_RestoreExternal | GODM_UseNewIds);
 
 		if (!mSceneObject.isDestroyed())
-			mSceneObject->destroy();
+			mSceneObject->destroy(true);
 
 		MemorySerializer serializer;
 		std::shared_ptr<SceneObject> restored = std::static_pointer_cast<SceneObject>(serializer.decode(mSerializedObject, mSerializedObjectSize));

+ 1 - 1
BansheeEngine/Include/BsGUIMaterialManager.h

@@ -72,7 +72,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Releases all internal materials, whether they are used or not.
 		 */
-		void forceReleaseAllMaterials();
+		void clearMaterials();
 	private:
 		/**
 		 * @brief	Contains data about a GUI material and its usage count.

+ 5 - 0
BansheeEngine/Include/BsGUIWidget.h

@@ -154,6 +154,11 @@ namespace BansheeEngine
 		 * @copydoc	Component::update
 		 */
 		virtual void update();
+
+		/**
+		 * @copydoc	Component::onDestroyed
+		 */
+		virtual void onDestroyed();
 	private:
 		GUIWidget(const GUIWidget& other) { }
 

+ 7 - 1
BansheeEngine/Source/BsApplication.cpp

@@ -8,6 +8,8 @@
 #include "BsScriptManager.h"
 #include "BsProfilingManager.h"
 #include "BsVirtualInput.h"
+#include "BsSceneManager.h"
+#include "BsSceneObject.h"
 #include "BsCursor.h"
 
 namespace BansheeEngine
@@ -49,6 +51,10 @@ namespace BansheeEngine
 
 	Application::~Application()
 	{
+		// Need to clear all objects before I unload any plugins, as they
+		// could have allocated parts or all of those objects.
+		SceneManager::instance().clearScene(); 
+
 #if BS_VER == BS_VER_DEV
 		shutdownPlugin(mSBansheeEnginePlugin);
 		unloadPlugin(mSBansheeEnginePlugin);
@@ -59,7 +65,7 @@ namespace BansheeEngine
 
 		Cursor::shutDown();
 
-		GUIMaterialManager::instance().forceReleaseAllMaterials();
+		GUIMaterialManager::instance().clearMaterials();
 
 		OverlayManager::shutDown();
 		GUIManager::shutDown();

+ 1 - 1
BansheeEngine/Source/BsGUIMaterialManager.cpp

@@ -164,7 +164,7 @@ namespace BansheeEngine
 		}
 	}
 
-	void GUIMaterialManager::forceReleaseAllMaterials()
+	void GUIMaterialManager::clearMaterials()
 	{
 		mTextMaterials.clear();
 		mImageMaterials.clear();

+ 6 - 3
BansheeEngine/Source/BsGUIWidget.cpp

@@ -39,8 +39,11 @@ namespace BansheeEngine
 	}
 
 	GUIWidget::~GUIWidget()
+	{ }
+
+	void GUIWidget::onDestroyed()
 	{
-		if(mTarget != nullptr)
+		if (mTarget != nullptr)
 		{
 			GUIManager::instance().unregisterWidget(this);
 
@@ -50,12 +53,12 @@ namespace BansheeEngine
 		// Iterate over all elements in this way because each
 		// GUIElement::destroy call internally unregisters the element
 		// from the widget, and modifies the mElements array
-		while(mElements.size() > 0)
+		while (mElements.size() > 0)
 		{
 			GUIElement::destroy(mElements[0]);
 		}
 
-		for(auto& area : mAreas)
+		for (auto& area : mAreas)
 		{
 			GUIArea::destroyInternal(area);
 		}

+ 7 - 3
SBansheeEngine/Include/BsManagedComponent.h

@@ -17,6 +17,8 @@ namespace BansheeEngine
 		const String& getManagedFullTypeName() const { return mFullTypeName; }
 
 	private:
+		typedef void(__stdcall *UpdateThunkDef) (MonoObject*, MonoException**);
+
 		MonoObject* mManagedInstance;
 		MonoReflectionType* mRuntimeType;
 		uint32_t mManagedHandle;
@@ -25,6 +27,8 @@ namespace BansheeEngine
 		String mTypeName;
 		String mFullTypeName;
 
+		UpdateThunkDef mUpdateThunk;
+
 		/************************************************************************/
 		/* 							COMPONENT OVERRIDES                    		*/
 		/************************************************************************/
@@ -35,12 +39,12 @@ namespace BansheeEngine
 		/** Standard constructor.
         */
 		ManagedComponent(const HSceneObject& parent, MonoReflectionType* runtimeType);
-		void construct(MonoObject* object, MonoReflectionType* runtimeType);
+		void construct(MonoObject* object, MonoReflectionType* runtimeType, MonoClass* monoClass);
 
 		void onDestroyed();
 
 	public:
-		virtual void update() {}
+		virtual void update();
 
 		/************************************************************************/
 		/* 								RTTI		                     		*/
@@ -51,6 +55,6 @@ namespace BansheeEngine
 		virtual RTTITypeBase* getRTTI() const;
 
 	protected:
-		ManagedComponent() {} // Serialization only
+		ManagedComponent(); // Serialization only
 	};
 }

+ 3 - 1
SBansheeEngine/Include/BsManagedComponentRTTI.h

@@ -91,7 +91,9 @@ namespace BansheeEngine
 
 			assert(componentHandle != nullptr); // It must exist as every component belongs to its parent SO
 
-			mc->construct(serializableObject->getManagedInstance(), runtimeType);
+			MonoClass* managedClass = MonoManager::instance().findClass(monoClass);
+
+			mc->construct(serializableObject->getManagedInstance(), runtimeType, managedClass);
 			ScriptComponent* nativeInstance = ScriptGameObjectManager::instance().createScriptComponent(componentHandle);
 		}
 

+ 30 - 3
SBansheeEngine/Source/BsManagedComponent.cpp

@@ -2,12 +2,18 @@
 #include "BsManagedComponentRTTI.h"
 #include "BsMonoManager.h"
 #include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "BsMonoMethod.h"
 #include "BsDebug.h"
 
 namespace BansheeEngine
 {
+	ManagedComponent::ManagedComponent()
+		:mUpdateThunk(nullptr)
+	{ }
+
 	ManagedComponent::ManagedComponent(const HSceneObject& parent, MonoReflectionType* runtimeType)
-		:Component(parent), mManagedInstance(nullptr), mRuntimeType(runtimeType)
+		:Component(parent), mManagedInstance(nullptr), mRuntimeType(runtimeType), mUpdateThunk(nullptr)
 	{
 		MonoType* monoType = mono_reflection_type_get_type(mRuntimeType);
 		::MonoClass* monoClass = mono_type_get_class(monoType);
@@ -24,16 +30,37 @@ namespace BansheeEngine
 
 		setName(mTypeName);
 
-		construct(managedClass->createInstance(), runtimeType);
+		construct(managedClass->createInstance(), runtimeType, managedClass);
 	}
 
-	void ManagedComponent::construct(MonoObject* object, MonoReflectionType* runtimeType)
+	void ManagedComponent::construct(MonoObject* object, MonoReflectionType* runtimeType, MonoClass* monoClass)
 	{
 		mFullTypeName = mNamespace + "." + mTypeName;
 
 		mManagedInstance = object;
 		mRuntimeType = runtimeType;
 		mManagedHandle = mono_gchandle_new(mManagedInstance, false);
+
+		if (monoClass != nullptr)
+		{
+			MonoMethod* updateMethod = monoClass->getMethod("Update", 0);
+			if (updateMethod != nullptr)
+				mUpdateThunk = (UpdateThunkDef)updateMethod->getThunk();
+		}
+	}
+
+	void ManagedComponent::update()
+	{
+		if (mUpdateThunk != nullptr && mManagedInstance != nullptr)
+		{
+			MonoException* exception = nullptr;
+
+			// Note: Not calling virtual methods. Can be easily done if needed but for now doing this
+			// for some extra speed.
+			mUpdateThunk(mManagedInstance, &exception); 
+
+			MonoUtil::throwIfException(exception);
+		}
 	}
 
 	void ManagedComponent::onDestroyed()

+ 1 - 0
SBansheeEngine/Source/BsScriptComponent.cpp

@@ -3,6 +3,7 @@
 #include "BsScriptMeta.h"
 #include "BsMonoField.h"
 #include "BsMonoClass.h"
+#include "BsMonoMethod.h"
 #include "BsMonoManager.h"
 #include "BsMonoUtil.h"
 #include "BsScriptSceneObject.h"

+ 5 - 5
SBansheeEngine/Source/BsScriptGameObjectManager.cpp

@@ -46,19 +46,19 @@ namespace BansheeEngine
 
 	ScriptSceneObject* ScriptGameObjectManager::createScriptSceneObject(MonoObject* existingInstance, const HSceneObject& sceneObject)
 	{
-		auto findIter = mScriptGameObjects.find(sceneObject->getInstanceId());
+		auto findIter = mScriptGameObjects.find(sceneObject.getInstanceId());
 		if(findIter != mScriptGameObjects.end())
 			BS_EXCEPT(InvalidStateException, "Script SceneObject for this SceneObject already exists.");
 
 		ScriptSceneObject* nativeInstance = new (bs_alloc<ScriptSceneObject>()) ScriptSceneObject(existingInstance, sceneObject);
-		mScriptGameObjects[sceneObject->getInstanceId()] = nativeInstance;
+		mScriptGameObjects[sceneObject.getInstanceId()] = nativeInstance;
 
 		return nativeInstance;
 	}
 
 	ScriptComponent* ScriptGameObjectManager::getScriptComponent(const GameObjectHandle<ManagedComponent>& component) const
 	{
-		auto findIter = mScriptGameObjects.find(component->getInstanceId());
+		auto findIter = mScriptGameObjects.find(component.getInstanceId());
 		if(findIter != mScriptGameObjects.end())
 			return static_cast<ScriptComponent*>(findIter->second);
 
@@ -67,7 +67,7 @@ namespace BansheeEngine
 
 	ScriptSceneObject* ScriptGameObjectManager::getScriptSceneObject(const HSceneObject& sceneObject) const
 	{
-		auto findIter = mScriptGameObjects.find(sceneObject->getInstanceId());
+		auto findIter = mScriptGameObjects.find(sceneObject.getInstanceId());
 		if(findIter != mScriptGameObjects.end())
 			return static_cast<ScriptSceneObject*>(findIter->second);
 
@@ -85,7 +85,7 @@ namespace BansheeEngine
 
 	void ScriptGameObjectManager::destroyScriptGameObject(ScriptGameObjectBase* gameObject)
 	{
-		UINT64 idx = gameObject->getNativeHandle()->getInstanceId();
+		UINT64 idx = gameObject->getNativeHandle().getInstanceId();
 		mScriptGameObjects.erase(idx);
 
 		bs_delete(gameObject);

+ 23 - 17
SceneView.txt

@@ -2,26 +2,30 @@
  GIZMO TODO:
   - Make a C# wrapper for Renderable
 
-The bounds I retrieve from GUIUtility are relative to the entire window, not just the current widget. Another reason to move editor widgets to their own GUIWidget.
-Passing a Vector2I (and I'm assuming any script) through a thunk crashes the runtime
-
-Set up Application::getPrimaryViewport so it returns a valid viewport otherwise C# Camera won't work properly
-
-REFACTOR material getParams* and related classes. Those params should update all gpu program params that share that variable, not just the first found
+Need to defer destruction of Components until all component updates have finished. Otherwise I risk destroying a component referenced by some
+other script since the order to Update() methods isn't clearly defined.
+ - Add [ExecPriority] attribute for Update() method
+ - Make sure Component update is called
+ - Do I handle it gracefully if user tries to access destroyed component or scene object?
+  - I don't, at least not with SceneObject. I will try to access HSceneObject handle even if its
+    destroyed. Add a check.
+
+Refactor GizmoManager and HandleManager so they accept a CameraHandler
+Port SceneCameraController to C#
+ - Will need a C# Cursor and SceneObject C# interface will also likely need to be completed (SceneObject doesn't have Destroy() among other things)
+Port RenderTexture to C# and likely extend Texture2D so it accepts renderable flags. While at it add TextureCube and Texture3D as well.
+Ensure that EditorWindow resize callback works properly.
+ - Perhaps add OnResizeEnd callback that can be used for updating the render texture
+Create a C# wrapper around ProjectSettings
+Make a Script version of SceneEditorWindow
+ - This would replace SceneEditorWidget so it would initialize scene grid and call
+    update on handle manager and scene grid, as well as apply ProjectSettings to them.
+ 
+Move handle is buggy as hell - It moves in wrong direction sometimes, sometimes it skips, other times collision seems to be wrong
 Need a way to drag and drop items from Scene tree view to Scene view
 When dragging a handle make sure it works when cursor leaves the scene view
 Also make sure that handle manager receives mouse up event if its done outside of scene view
-When selecting/deselecting stuff handle display is delayed
-
-Create the scene widget completely in C#?
- - Port RenderTexture, SceneCameraController, ProjectSettings, SceneGrid
- - Will need to track when widget resizes so I can resize the render target
- - Handle manager update will originate from the widget
-  - Actually it could still be done from C++
- - How will scene grid rendering be handled? Currently it's done from a render() callback
-  - It's still going to have a C++ representation, just call the callback there
- - I will still need a C++ version of scene widget (not in script code but in editor code) because handle/gizmo manager reference it
- 
+
 AFTER I have scene widget in C#:
  - Finish up C# Handles class so it returns proper values
  - Ensure fixed handle size and handle snapping works
@@ -38,6 +42,8 @@ LATER:
  - Need a way to render text for gizmos and handles, and in scene in general
  - Add drag to select
  - Need a better system to catch broken shaders. DX11 just draws nothing with depth, DX9 draws all white.
+ - The bounds I retrieve from GUIUtility are relative to the entire window, not just the current widget. Another reason to move editor widgets to their own GUIWidget.
+ - Set up Application::getPrimaryViewport so it returns a valid viewport otherwise C# Camera won't work properly
 
 ---------------------------------------------------------------------
 Render textures in C#: