Browse Source

Work on the deferred decals

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
25bb9a5dfc

BIN
samples/assets/column.ankimesh


+ 2 - 2
samples/assets/columnroom-material.ankimdl

@@ -2,8 +2,8 @@
 <model>
 	<modelPatches>
 		<modelPatch>
-			<mesh>samples/assets/column.ankimesh</mesh>
-			<material>samples/assets/room-material.ankimtl</material>
+			<mesh>assets/column.ankimesh</mesh>
+			<material>assets/room-material.ankimtl</material>
 		</modelPatch>
 	</modelPatches>
 </model>

BIN
samples/assets/room.ankimesh


+ 2 - 2
samples/assets/roomroom-material.ankimdl

@@ -2,8 +2,8 @@
 <model>
 	<modelPatches>
 		<modelPatch>
-			<mesh>samples/assets/room.ankimesh</mesh>
-			<material>samples/assets/room-material.ankimtl</material>
+			<mesh>assets/room.ankimesh</mesh>
+			<material>assets/room-material.ankimtl</material>
 		</modelPatch>
 	</modelPatches>
 </model>

+ 34 - 7
samples/assets/scene.lua

@@ -5,7 +5,7 @@ local node
 local inst
 local lcomp
 
-node = scene:newSector("sector0", "samples/assets/sector.ankimesh")
+node = scene:newSector("sector0", "assets/sector.ankimesh")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(0, 0, 0, 0))
 rot = Mat3x4.new()
@@ -14,7 +14,7 @@ trf:setRotation(rot)
 trf:setScale(1.16458)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("roomroom-materialnone0", "samples/assets/roomroom-material.ankimdl")
+node = scene:newModelNode("roomroom-materialnone0", "assets/roomroom-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(0, 0, 0, 0))
 rot = Mat3x4.new()
@@ -23,9 +23,36 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("columnroom-materialnone1", "samples/assets/columnroom-material.ankimdl")
+node = scene:newModelNode("columnroom-materialnone1", "assets/columnroom-material.ankimdl")
 trf = Transform.new()
-trf:setOrigin(Vec4.new(5.35225, 5.06618, 5.43441, 0))
+trf:setOrigin(Vec4.new(4.90187, 5.43441, -4.52919, 0))
+rot = Mat3x4.new()
+rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
+trf:setRotation(rot)
+trf:setScale(1)
+node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
+
+node = scene:newModelNode("columnroom-materialnone2", "assets/columnroom-material.ankimdl")
+trf = Transform.new()
+trf:setOrigin(Vec4.new(-4.59369, 5.43441, -4.49454, 0))
+rot = Mat3x4.new()
+rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
+trf:setRotation(rot)
+trf:setScale(1)
+node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
+
+node = scene:newModelNode("columnroom-materialnone3", "assets/columnroom-material.ankimdl")
+trf = Transform.new()
+trf:setOrigin(Vec4.new(-4.61101, 5.43441, 4.79029, 0))
+rot = Mat3x4.new()
+rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
+trf:setRotation(rot)
+trf:setScale(1)
+node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
+
+node = scene:newModelNode("columnroom-materialnone4", "assets/columnroom-material.ankimdl")
+trf = Transform.new()
+trf:setOrigin(Vec4.new(4.95098, 5.43441, 4.79029, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
 trf:setRotation(rot)
@@ -38,7 +65,7 @@ lcomp:setDiffuseColor(Vec4.new(2, 2, 2, 1))
 lcomp:setSpecularColor(Vec4.new(2, 2, 2, 1))
 lcomp:setRadius(12.77)
 trf = Transform.new()
-trf:setOrigin(Vec4.new(0.0680842, -0.0302386, 9.57987, 0))
+trf:setOrigin(Vec4.new(0.0680842, 9.57987, 0.0302386, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 4.63287e-05, 1, 0, 0, -1, 4.63287e-05, 0)
 trf:setRotation(rot)
@@ -50,9 +77,9 @@ node = scene:newPerspectiveCamera("Camera")
 scene:setActiveCamera(node:getSceneNodeBase())
 node:setAll(1.5708, 1.0 / getMainRenderer():getAspectRatio() * 1.5708, 0.1, 100)
 trf = Transform.new()
-trf:setOrigin(Vec4.new(5.65394, -5.98675, 3.80237, 0))
+trf:setOrigin(Vec4.new(-0.0971127, 3.80237, 5.98675, 0))
 rot = Mat3x4.new()
-rot:setAll(1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0)
+rot:setAll(1, 0, 0, 0, 0, 1, -4.63724e-05, 0, 0, 4.63724e-05, 1, 0)
 trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)

BIN
samples/assets/sector.ankimesh


BIN
samples/assets/simple_scene.blend


+ 20 - 1
samples/simple_scene/Main.cpp

@@ -17,7 +17,7 @@ Error MyApp::init()
 	// Init the super class
 	Config config;
 	config.set("fullscreenDesktopResolution", true);
-	config.set("dataPaths", ".:..");
+	config.set("dataPaths", ".:..:../assets");
 	config.set("debugContext", 0);
 	ANKI_CHECK(App::init(config, allocAligned, nullptr));
 
@@ -45,6 +45,7 @@ Error MyApp::userMainLoop(Bool& quit)
 	quit = false;
 
 	SceneGraph& scene = getSceneGraph();
+	Renderer& renderer = getMainRenderer().getOffscreenRenderer();
 	Input& in = getInput();
 
 	if(in.getKey(KeyCode::ESCAPE))
@@ -56,6 +57,24 @@ Error MyApp::userMainLoop(Bool& quit)
 	// move the camera
 	static MoveComponent* mover = &scene.getActiveCamera().getComponent<MoveComponent>();
 
+	if(in.getKey(KeyCode::_1) == 1)
+	{
+		mover = &scene.getActiveCamera().getComponent<MoveComponent>();
+	}
+
+	if(in.getKey(KeyCode::F1) == 1)
+	{
+		renderer.getDbg().setEnabled(!renderer.getDbg().getEnabled());
+	}
+	if(in.getKey(KeyCode::F2) == 1)
+	{
+		renderer.getDbg().flipFlags(DbgFlag::SPATIAL_COMPONENT);
+	}
+	if(in.getKey(KeyCode::F6) == 1)
+	{
+		renderer.getDbg().switchDepthTestEnabled();
+	}
+
 	if(in.getKey(KeyCode::UP))
 	{
 		mover->rotateLocalX(ROTATE_ANGLE);

+ 1 - 1
shaders/Blit.frag.glsl

@@ -13,5 +13,5 @@ layout(location = 0) out vec3 outColor;
 
 void main()
 {
-	outColor = textureRt(uTex, inTexCoords).rgb;
+	outColor = texture(uTex, inTexCoords).rgb;
 }

+ 1 - 5
shaders/Common.glsl

@@ -25,7 +25,7 @@
 #endif
 
 precision DEFAULT_FLOAT_PRECISION float;
-precision DEFAULT_FLOAT_PRECISION int;
+precision DEFAULT_INT_PRECISION int;
 
 const float EPSILON = 0.000001;
 const float PI = 3.14159265358979323846;
@@ -33,10 +33,6 @@ const uint UBO_MAX_SIZE = 16384u;
 
 const uint MAX_U32 = 0xFFFFFFFFu;
 
-// Read from a render target texture
-//#define textureRt(tex_, texc_) texture(tex_, texc_)
-#define textureRt(tex_, texc_) textureLod(tex_, texc_, 0.0)
-
 // Common locations
 #define POSITION_LOCATION 0
 #define TEXTURE_COORDINATE_LOCATION 1

+ 13 - 5
shaders/Is.frag.glsl

@@ -22,6 +22,8 @@ layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_msRt1;
 layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_msRt2;
 layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2D u_msDepthRt;
 
+layout(ANKI_TEX_BINDING(1, 0)) uniform sampler2D u_diffDecalTex;
+
 layout(location = 0) in vec2 in_texCoord;
 layout(location = 1) flat in int in_instanceId;
 layout(location = 2) in vec2 in_projectionParams;
@@ -33,7 +35,7 @@ const uint TILE_COUNT = TILE_COUNT_X * TILE_COUNT_Y;
 // Return frag pos in view space
 vec3 getFragPosVSpace()
 {
-	float depth = textureRt(u_msDepthRt, in_texCoord).r;
+	float depth = texture(u_msDepthRt, in_texCoord, 0.0).r;
 
 	vec3 fragPos;
 	fragPos.z = u_lightingUniforms.projectionParams.z / (u_lightingUniforms.projectionParams.w + depth);
@@ -61,12 +63,18 @@ void debugIncorrectColor(inout vec3 c)
 }
 
 // Compute the colors of a decal.
-void computeDecalColors(in Decal decal, in vec3 fragPos, out vec3 diffuseColor)
+void appendDecalColors(in Decal decal, in vec3 fragPos, inout vec3 diffuseColor)
 {
 	vec4 texCoords4 = decal.texProjectionMat * vec4(fragPos, 1.0);
-	vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
+	vec2 texCoords2 = texCoords4.xy / texCoords4.w;
+
+	// Clamp the tex coords. Expect a border in the texture atlas
+	texCoords2 = clamp(texCoords2, 0.0, 1.0);
+
+	texCoords2 = texCoords2 * decal.uv.zw + decal.uv.xy;
+	vec4 dcol = texture(u_diffDecalTex, texCoords2);
 
-	diffuseColor = texCoords3;
+	diffuseColor = mix(diffuseColor, dcol.rgb, dcol.a);
 }
 
 void readIndirect(in uint idxOffset,
@@ -163,7 +171,7 @@ void main()
 	{
 		Decal decal = u_decals[u_lightIndices[idxOffset++]];
 
-		computeDecalColors(decal, fragPos, diffCol);
+		appendDecalColors(decal, fragPos, diffCol);
 	}
 
 	// Point lights

+ 2 - 2
shaders/Pps.frag.glsl

@@ -95,11 +95,11 @@ vec3 sharpen(in sampler2D tex, in vec2 texCoords)
 
 vec3 erosion(in sampler2D tex, in vec2 texCoords)
 {
-	vec3 minValue = textureRt(tex, texCoords).rgb;
+	vec3 minValue = texture(tex, texCoords, 0.0).rgb;
 
 	for(int i = 0; i < 8; i++)
 	{
-		vec3 tmpCol = textureRt(tex, texCoords + KERNEL[i]).rgb;
+		vec3 tmpCol = texture(tex, texCoords + KERNEL[i], 0.0).rgb;
 		minValue = min(tmpCol, minValue);
 	}
 

+ 4 - 4
shaders/Sslf.frag.glsl

@@ -29,11 +29,11 @@ vec3 textureDistorted(in sampler2D tex,
 	in vec3 distortion) // per-channel distortion factor
 {
 #if ENABLE_CHROMATIC_DISTORTION
-	return vec3(textureRt(tex, texcoord + direction * distortion.r).r,
-		textureRt(tex, texcoord + direction * distortion.g).g,
-		textureRt(tex, texcoord + direction * distortion.b).b);
+	return vec3(texture(tex, texcoord + direction * distortion.r).r,
+		texture(tex, texcoord + direction * distortion.g).g,
+		texture(tex, texcoord + direction * distortion.b).b);
 #else
-	return textureRt(tex, texcoord).rgb;
+	return texture(tex, texcoord).rgb;
 #endif
 }
 

+ 2 - 2
shaders/VariableSamplingBlurGeneric.frag.glsl

@@ -117,12 +117,12 @@ layout(location = 0) out COL_TYPE out_fragColor;
 void main()
 {
 	// Get the first
-	COL_TYPE col = textureRt(uTex, in_texCoord).TEX_FETCH;
+	COL_TYPE col = texture(uTex, in_texCoord).TEX_FETCH;
 
 	// Get the rest of the samples
 	for(uint i = 0; i < SAMPLES - 1; i++)
 	{
-		col += textureRt(uTex, in_texCoord + KERNEL[i]).TEX_FETCH;
+		col += texture(uTex, in_texCoord + KERNEL[i]).TEX_FETCH;
 	}
 
 	out_fragColor = col / float(SAMPLES);

+ 1 - 1
src/anki/core/Config.cpp

@@ -71,7 +71,7 @@ Config::Config()
 	newOption("gr.uniformPerFrameMemorySize", 1024 * 1024 * 16);
 	newOption("gr.storagePerFrameMemorySize", 1024 * 1024 * 16);
 	newOption("gr.vertexPerFrameMemorySize", 1024 * 1024 * 10);
-	newOption("gr.transferPerFrameMemorySize", 1024 * 1024 * 1);
+	newOption("gr.transferPerFrameMemorySize", 1024 * 1024 * 128);
 	newOption("gr.transferPersistentMemorySize", (4096 / 4) * (4096 / 4) * 16 * 4);
 
 	//

+ 7 - 0
src/anki/gr/ResourceGroup.h

@@ -57,6 +57,11 @@ public:
 	Array<BufferBinding, MAX_VERTEX_ATTRIBUTES> m_vertexBuffers;
 	BufferBinding m_indexBuffer;
 	I8 m_indexSize = -1; ///< Index size in bytes. 2 or 4
+
+	U64 computeHash() const;
+
+private:
+	void appendBufferBinding(const BufferBinding& b, U64 arr[], U& count) const;
 };
 
 /// Resource group.
@@ -86,3 +91,5 @@ private:
 /// @}
 
 } // end namespace anki
+
+#include <anki/gr/ResourceGroup.inl.h>

+ 101 - 0
src/anki/gr/ResourceGroup.inl.h

@@ -0,0 +1,101 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/gr/ResourceGroup.h>
+
+namespace anki
+{
+
+inline void ResourceGroupInitInfo::appendBufferBinding(const BufferBinding& b, U64 arr[], U& count) const
+{
+	arr[count++] = (b.m_buffer) ? b.m_buffer->getUuid() : 0;
+	arr[count++] = b.m_offset;
+	arr[count++] = b.m_range;
+	arr[count++] = b.m_uploadedMemory;
+	arr[count++] = static_cast<U64>(b.m_usage);
+}
+
+inline U64 ResourceGroupInitInfo::computeHash() const
+{
+	const U TEX_NUMBERS = MAX_TEXTURE_BINDINGS * 4;
+	const U BUFF_NUMBERS = (MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS + MAX_VERTEX_ATTRIBUTES + 1) * 5;
+	const U IMAGE_NUMBERS = MAX_IMAGE_BINDINGS * 3;
+	const U INDEX_SIZE_NUMBERS = 1;
+	Array<U64, TEX_NUMBERS + BUFF_NUMBERS + IMAGE_NUMBERS + INDEX_SIZE_NUMBERS> numbers;
+	U count = 0;
+
+	for(const auto& tex : m_textures)
+	{
+		if(tex.m_texture)
+		{
+			numbers[count++] = tex.m_texture->getUuid();
+			if(tex.m_sampler)
+			{
+				numbers[count++] = tex.m_sampler->getUuid();
+			}
+			numbers[count++] = static_cast<U64>(tex.m_usage);
+			numbers[count++] = static_cast<U64>(tex.m_aspect);
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	for(const auto& b : m_uniformBuffers)
+	{
+		if(b.m_buffer || b.m_uploadedMemory)
+		{
+			appendBufferBinding(b, &numbers[0], count);
+		}
+		else
+		{
+			break;
+		}
+	}
+	for(const auto& b : m_storageBuffers)
+	{
+		if(b.m_buffer || b.m_uploadedMemory)
+		{
+			appendBufferBinding(b, &numbers[0], count);
+		}
+		else
+		{
+			break;
+		}
+	}
+	for(const auto& b : m_vertexBuffers)
+	{
+		if(b.m_buffer || b.m_uploadedMemory)
+		{
+			appendBufferBinding(b, &numbers[0], count);
+		}
+		else
+		{
+			break;
+		}
+	}
+	appendBufferBinding(m_indexBuffer, &numbers[0], count);
+
+	for(const auto& img : m_images)
+	{
+		if(img.m_texture)
+		{
+			numbers[count++] = img.m_texture->getUuid();
+			numbers[count++] = img.m_level;
+			numbers[count++] = static_cast<U64>(img.m_usage);
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	numbers[count++] = static_cast<U64>(m_indexSize);
+
+	return anki::computeHash(&numbers[0], count * sizeof(U64), 458);
+}
+
+} // end namespace anki

+ 6 - 0
src/anki/math/Vec.h

@@ -150,6 +150,12 @@ public:
 		return TVec2<T>(y(), x());
 	}
 
+	TVec2<T> zw() const
+	{
+		static_assert(N == 4, "Wrong vector");
+		return TVec2<T>(z(), w());
+	}
+
 	TVec3<T> xxx() const
 	{
 		static_assert(N > 2, "Wrong vector");

+ 1 - 1
src/anki/misc/Xml.cpp

@@ -184,7 +184,7 @@ Error XmlElement::getVec4(Vec4& out) const
 
 	if(!err && arr.getSize() != 4)
 	{
-		ANKI_LOGE("Expecting 4 elements for Vec3");
+		ANKI_LOGE("Expecting 4 elements for Vec4");
 		err = ErrorCode::USER_DATA;
 	}
 

+ 12 - 5
src/anki/renderer/Dbg.cpp

@@ -10,11 +10,7 @@
 #include <anki/renderer/Pps.h>
 #include <anki/renderer/DebugDrawer.h>
 #include <anki/resource/ShaderResource.h>
-#include <anki/scene/SceneGraph.h>
-#include <anki/scene/FrustumComponent.h>
-#include <anki/scene/MoveComponent.h>
-#include <anki/scene/Sector.h>
-#include <anki/scene/Light.h>
+#include <anki/Scene.h>
 #include <anki/util/Logger.h>
 #include <anki/util/Enum.h>
 #include <anki/misc/ConfigSet.h>
@@ -65,6 +61,7 @@ Error Dbg::lazyInit()
 	fbInit.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
 	fbInit.m_depthStencilAttachment.m_texture = m_r->getMs().m_depthRt;
 	fbInit.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::LOAD;
+	fbInit.m_depthStencilAttachment.m_aspect = DepthStencilAspectMask::DEPTH;
 
 	m_fb = getGrManager().newInstance<Framebuffer>(fbInit);
 
@@ -152,6 +149,16 @@ Error Dbg::run(RenderingContext& ctx)
 			});
 			(void)err;
 		}
+
+		// Decal
+		if(m_flags.get(DbgFlag::DECAL_COMPONENT))
+		{
+			Error err = node.iterateComponentsOfType<DecalComponent>([&](DecalComponent& psc) -> Error {
+				sceneDrawer.draw(psc);
+				return ErrorCode::NONE;
+			});
+			(void)err;
+		}
 	});
 
 	if(m_flags.get(DbgFlag::PHYSICS))

+ 3 - 2
src/anki/renderer/Dbg.h

@@ -23,8 +23,9 @@ enum class DbgFlag : U16
 	SPATIAL_COMPONENT = 1 << 0,
 	FRUSTUM_COMPONENT = 1 << 1,
 	SECTOR_COMPONENT = 1 << 2,
-	PHYSICS = 1 << 3,
-	ALL = SPATIAL_COMPONENT | FRUSTUM_COMPONENT | SECTOR_COMPONENT | PHYSICS
+	DECAL_COMPONENT = 1 << 3,
+	PHYSICS = 1 << 4,
+	ALL = SPATIAL_COMPONENT | FRUSTUM_COMPONENT | SECTOR_COMPONENT | DECAL_COMPONENT | PHYSICS
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(DbgFlag, inline)
 

+ 14 - 17
src/anki/renderer/DebugDrawer.cpp

@@ -524,23 +524,6 @@ void SceneDebugDrawer::draw(const PortalComponent& c) const
 	m_dbg->end();
 }
 
-void SceneDebugDrawer::drawPath(const Path& path) const
-{
-	/*const U count = path.getPoints().size();
-
-	m_dbg->setColor(Vec3(1.0, 1.0, 0.0));
-
-	m_dbg->begin();
-
-	for(U i = 0; i < count - 1; i++)
-	{
-			m_dbg->pushBackVertex(path.getPoints()[i].getPosition());
-			m_dbg->pushBackVertex(path.getPoints()[i + 1].getPosition());
-	}
-
-	m_dbg->end();*/
-}
-
 void SceneDebugDrawer::draw(const ReflectionProxyComponent& proxy) const
 {
 	m_dbg->setModelMatrix(Mat4::getIdentity());
@@ -558,4 +541,18 @@ void SceneDebugDrawer::draw(const ReflectionProxyComponent& proxy) const
 	m_dbg->end();
 }
 
+void SceneDebugDrawer::draw(const DecalComponent& decalc) const
+{
+	const MoveComponent& movec = decalc.getSceneNode().getComponent<MoveComponent>();
+
+	m_dbg->setColor(Vec3(0.0, 1.0, 0.0));
+
+	const Vec3& size = decalc.getVolumeSize();
+	Vec3 halfSize = size / 2.0;
+	Obb box(Vec4(0.0, 0.0, -halfSize.z(), 0.0), Mat3x4::getIdentity(), Vec4(halfSize, 0.0));
+	box.transform(movec.getWorldTransform());
+	CollisionDebugDrawer cd(m_dbg);
+	box.accept(cd);
+}
+
 } // end namespace anki

+ 2 - 5
src/anki/renderer/DebugDrawer.h

@@ -18,9 +18,6 @@ namespace anki
 
 // Forward
 class Renderer;
-class PortalComponent;
-class SectorComponent;
-class ReflectionProxyComponent;
 
 /// @addtogroup renderer
 /// @{
@@ -182,10 +179,10 @@ public:
 
 	void draw(const SectorComponent& c) const;
 
-	void drawPath(const Path& path) const;
-
 	void draw(const ReflectionProxyComponent& proxy) const;
 
+	void draw(const DecalComponent& decalc) const;
+
 private:
 	DebugDrawer* m_dbg;
 };

+ 28 - 1
src/anki/renderer/Is.cpp

@@ -180,6 +180,8 @@ Error Is::binLights(RenderingContext& ctx)
 {
 	updateCommonBlock(ctx);
 
+	TexturePtr diffDecalTex;
+
 	ANKI_CHECK(m_lightBin->bin(*ctx.m_frustumComponent,
 		getFrameAllocator(),
 		m_maxLightIds,
@@ -189,7 +191,26 @@ Error Is::binLights(RenderingContext& ctx)
 		&ctx.m_is.m_dynBufferInfo.m_uniformBuffers[PROBES_LOCATION],
 		ctx.m_is.m_dynBufferInfo.m_uniformBuffers[DECALS_LOCATION],
 		ctx.m_is.m_dynBufferInfo.m_storageBuffers[CLUSTERS_LOCATION],
-		ctx.m_is.m_dynBufferInfo.m_storageBuffers[LIGHT_IDS_LOCATION]));
+		ctx.m_is.m_dynBufferInfo.m_storageBuffers[LIGHT_IDS_LOCATION],
+		diffDecalTex));
+
+	if(diffDecalTex)
+	{
+		ResourceGroupInitInfo rcinit;
+		rcinit.m_textures[0].m_texture = diffDecalTex;
+
+		U64 hash = rcinit.computeHash();
+		if(hash != m_rcGroup1Hash)
+		{
+			m_rcGroup1Hash = hash;
+
+			m_rcGroup1 = getGrManager().newInstance<ResourceGroup>(rcinit);
+		}
+	}
+	else
+	{
+		m_rcGroup1.reset(nullptr);
+	}
 
 	return ErrorCode::NONE;
 }
@@ -202,6 +223,12 @@ void Is::run(RenderingContext& ctx)
 	cmdb->setViewport(0, 0, m_r->getWidth(), m_r->getHeight());
 	cmdb->bindPipeline(m_lightPpline);
 	cmdb->bindResourceGroup(m_rcGroup, 0, &ctx.m_is.m_dynBufferInfo);
+
+	if(m_rcGroup1)
+	{
+		cmdb->bindResourceGroup(m_rcGroup1, 1, nullptr);
+	}
+
 	cmdb->drawArrays(4, m_r->getTileCount());
 	cmdb->endRenderPass();
 }

+ 2 - 0
src/anki/renderer/Is.h

@@ -63,6 +63,8 @@ private:
 	FramebufferPtr m_fb;
 
 	ResourceGroupPtr m_rcGroup;
+	U64 m_rcGroup1Hash = 0;
+	ResourceGroupPtr m_rcGroup1;
 
 	// Light shaders
 	ShaderResourcePtr m_lightVert;

+ 21 - 2
src/anki/renderer/LightBin.cpp

@@ -315,6 +315,9 @@ public:
 	Atomic<U32> m_count = {0};
 	Atomic<U32> m_count2 = {0};
 
+	TexturePtr m_diffDecalTexAtlas;
+	SpinLock m_diffDecalTexAtlasMtx;
+
 	LightBin* m_bin = nullptr;
 };
 
@@ -359,7 +362,8 @@ Error LightBin::bin(FrustumComponent& frc,
 	TransientMemoryToken* probesToken,
 	TransientMemoryToken& decalsToken,
 	TransientMemoryToken& clustersToken,
-	TransientMemoryToken& lightIndicesToken)
+	TransientMemoryToken& lightIndicesToken,
+	TexturePtr& diffuseDecalTexAtlas)
 {
 	ANKI_TRACE_START_EVENT(RENDERER_LIGHT_BINNING);
 
@@ -482,6 +486,8 @@ Error LightBin::bin(FrustumComponent& frc,
 	// Sync
 	ANKI_CHECK(m_threadPool->waitForAllThreadsToFinish());
 
+	diffuseDecalTexAtlas = ctx.m_diffDecalTexAtlas;
+
 	ANKI_TRACE_STOP_EVENT(RENDERER_LIGHT_BINNING);
 	return ErrorCode::NONE;
 }
@@ -803,7 +809,20 @@ void LightBin::writeAndBinDecal(
 	I idx = ctx.m_decalCount.fetchAdd(1);
 	ShaderDecal& decal = ctx.m_decals[idx];
 
-	decalc.getDiffuseAtlasInfo(decal.m_uv);
+	TexturePtr diffAtlas;
+	Vec4 uv;
+	decalc.getDiffuseAtlasInfo(uv, diffAtlas);
+	decal.m_uv = Vec4(uv.x(), uv.y(), uv.z() - uv.x(), uv.w() - uv.y());
+
+	{
+		LockGuard<SpinLock> lock(ctx.m_diffDecalTexAtlasMtx);
+		if(ctx.m_diffDecalTexAtlas && ctx.m_diffDecalTexAtlas != diffAtlas)
+		{
+			ANKI_LOGF("All decals should have the same tex atlas");
+		}
+
+		ctx.m_diffDecalTexAtlas = diffAtlas;
+	}
 
 	// bias * proj_l * view_l * world_c
 	decal.m_texProjectionMat = decalc.getBiasProjectionViewMatrix() * Mat4(camMovec.getWorldTransform());

+ 2 - 1
src/anki/renderer/LightBin.h

@@ -43,7 +43,8 @@ public:
 		TransientMemoryToken* probesToken,
 		TransientMemoryToken& decalsToken,
 		TransientMemoryToken& clustersToken,
-		TransientMemoryToken& lightIndicesToken);
+		TransientMemoryToken& lightIndicesToken,
+		TexturePtr& diffuseDecalTexAtlas);
 
 	const Clusterer& getClusterer() const
 	{

+ 19 - 2
src/anki/resource/TextureAtlas.cpp

@@ -41,6 +41,22 @@ Error TextureAtlas::load(const ResourceFilename& filename)
 	ANKI_CHECK(el.getText(texFname));
 	ANKI_CHECK(getManager().loadResource<TextureResource>(texFname, m_tex));
 
+	m_size[0] = m_tex->getWidth();
+	m_size[1] = m_tex->getHeight();
+
+	//
+	// <subTextureMargin>
+	//
+	ANKI_CHECK(rootel.getChildElement("subTextureMargin", el));
+	I64 margin = 0;
+	ANKI_CHECK(el.getI64(margin));
+	if(margin >= I(m_tex->getWidth()) || margin >= I(m_tex->getHeight()) || margin < 0)
+	{
+		ANKI_LOGE("Too big margin %d", U(margin));
+		return ErrorCode::USER_DATA;
+	}
+	m_margin = margin;
+
 	//
 	// <subTextures>
 	//
@@ -67,7 +83,7 @@ Error TextureAtlas::load(const ResourceFilename& filename)
 		++subTexesCount;
 
 		ANKI_CHECK(subTexEl.getNextSiblingElement("subTexture", subTexEl));
-	} while(el);
+	} while(subTexEl);
 
 	// Allocate
 	m_subTexNames.create(getAllocator(), namesSize);
@@ -87,6 +103,7 @@ Error TextureAtlas::load(const ResourceFilename& filename)
 
 		m_subTexes[subTexesCount].m_name = names;
 
+		ANKI_CHECK(subTexEl.getChildElement("uv", el));
 		Vec4 uv;
 		ANKI_CHECK(el.getVec4(uv));
 		m_subTexes[subTexesCount].m_uv = {{uv[0], uv[1], uv[2], uv[3]}};
@@ -95,7 +112,7 @@ Error TextureAtlas::load(const ResourceFilename& filename)
 		++subTexesCount;
 
 		ANKI_CHECK(subTexEl.getNextSiblingElement("subTexture", subTexEl));
-	} while(el);
+	} while(subTexEl);
 
 	return ErrorCode::NONE;
 }

+ 7 - 0
src/anki/resource/TextureAtlas.h

@@ -21,6 +21,7 @@ namespace anki
 /// @code
 /// <textureAtlas>
 /// 	<texture>path/to/tex.ankitex</texture>
+/// 	<subTextureMargin>N</subTextureMargin>
 /// 	<subTextures>
 /// 		<subTexture>
 /// 			<name>name</name>
@@ -56,6 +57,11 @@ public:
 		return m_size[1];
 	}
 
+	U getSubTextureMargin() const
+	{
+		return m_margin;
+	}
+
 	/// Get the UV coordinates of a sub texture.
 	ANKI_USE_RESULT Error getSubTextureInfo(CString name, F32 uv[4]) const;
 
@@ -71,6 +77,7 @@ private:
 	DynamicArray<char> m_subTexNames;
 	DynamicArray<SubTex> m_subTexes;
 	Array<U32, 2> m_size;
+	U32 m_margin = 0;
 };
 /// @}
 

+ 13 - 1
src/anki/scene/DecalComponent.cpp

@@ -26,8 +26,20 @@ Error DecalComponent::setLayer(CString texAtlasFname, CString texAtlasSubtexName
 	ANKI_CHECK(getSceneGraph().getResourceManager().loadResource(texAtlasFname, l.m_atlas));
 
 	ANKI_CHECK(l.m_atlas->getSubTextureInfo(texAtlasSubtexName, &l.m_uv[0]));
-	l.m_blendFactor = blendFactor;
 
+	// Add a border to the UVs to avoid complex shader logic
+	if(l.m_atlas->getSubTextureMargin() < ATLAS_SUB_TEXTURE_MARGIN)
+	{
+		ANKI_LOGE("Need texture atlas with margin at least %u", ATLAS_SUB_TEXTURE_MARGIN);
+		return ErrorCode::USER_DATA;
+	}
+
+	Vec2 marginf = F32(ATLAS_SUB_TEXTURE_MARGIN / 2) / Vec2(l.m_atlas->getWidth(), l.m_atlas->getHeight());
+	Vec2 minUv = l.m_uv.xy() - marginf;
+	Vec2 sizeUv = (l.m_uv.zw() - l.m_uv.xy()) + 2.0f * marginf;
+	l.m_uv = Vec4(minUv.x(), minUv.y(), minUv.x() + sizeUv.x(), minUv.y() + sizeUv.y());
+
+	l.m_blendFactor = blendFactor;
 	return ErrorCode::NONE;
 }
 

+ 8 - 1
src/anki/scene/DecalComponent.h

@@ -22,6 +22,7 @@ public:
 	static const SceneComponentType CLASS_TYPE = SceneComponentType::DECAL;
 
 	static constexpr F32 FRUSTUM_NEAR_PLANE = 0.1 / 4.0;
+	static constexpr U ATLAS_SUB_TEXTURE_MARGIN = 16;
 
 	DecalComponent(SceneNode* node);
 
@@ -80,9 +81,15 @@ public:
 		return m_biasProjViewMat;
 	}
 
-	void getDiffuseAtlasInfo(Vec4& uv) const
+	void getDiffuseAtlasInfo(Vec4& uv, TexturePtr& tex) const
 	{
 		uv = m_layers[LayerType::DIFFUSE].m_uv;
+		tex = m_layers[LayerType::DIFFUSE].m_atlas->getGrTexture();
+	}
+
+	const Vec3& getVolumeSize() const
+	{
+		return m_sizes;
 	}
 
 private:

+ 4 - 0
src/anki/scene/Forward.h

@@ -14,6 +14,10 @@ class InstanceComponent;
 class MoveComponent;
 class RenderComponent;
 class SpatialComponent;
+class DecalComponent;
+class PortalComponent;
+class SectorComponent;
+class ReflectionProxyComponent;
 
 // Nodes
 class SceneNode;

+ 36 - 65
tools/texture/convert_image.py

@@ -192,52 +192,39 @@ def parse_commandline():
 			"need to define the executable explicitly")
 
 	parser.add_option("-i", "--input", dest = "inp",
-			type = "string", help = "specify the image(s) to convert. " \
-			"Seperate with :")
+			type = "string", help = "specify the image(s) to convert. Seperate with :")
 
-	parser.add_option("-o", "--output", dest = "out",
-			type = "string", help = "specify output AnKi image. ")
+	parser.add_option("-o", "--output", dest = "out", type = "string", help = "specify output AnKi image. ")
 
-	parser.add_option("-t", "--type", dest = "type",
-			type = "string", default = "2D",
+	parser.add_option("-t", "--type", dest = "type", type = "string", default = "2D",
 			help = "type of the image (2D or cube or 3D or 2DArray)")
 
-	parser.add_option("-f", "--fast", dest = "fast",
-			type = "int", action = "store", default = 0,
+	parser.add_option("-f", "--fast", dest = "fast", type = "int", action = "store", default = 0,
 			help = "run the fast version of the converters")
 
-	parser.add_option("-n", "--normal", dest = "normal",
-			type = "int", action = "store", default = 0,
+	parser.add_option("-n", "--normal", dest = "normal", type = "int", action = "store", default = 0,
 			help = "assume the texture is normal")
 
-	parser.add_option("-c", "--convert-path", dest = "convert_path",
-			type = "string", default = "/usr/bin/convert",
-			help = "the executable where convert tool is " \
-			"located. Stupid etcpack cannot get it from PATH")
+	parser.add_option("-c", "--convert-path", dest = "convert_path", type = "string", default = "/usr/bin/convert",
+			help = "the executable where convert tool is located. Stupid etcpack cannot get it from PATH")
 
-	parser.add_option("--no-alpha", dest = "no_alpha",
-			type = "int", action = "store", default = 0,
+	parser.add_option("--no-alpha", dest = "no_alpha", type = "int", action = "store", default = 0,
 			help = "remove alpha channel")
 
-	parser.add_option("--store-uncompressed", dest = "store_uncompressed",
-			type = "int", action = "store", default = 0,
+	parser.add_option("--store-uncompressed", dest = "store_uncompressed", type = "int", action = "store", default = 0,
 			help = "store or not uncompressed data")
 
-	parser.add_option("--store-compressed", dest = "store_compressed",
-			type = "int", action = "store", default = 1,
+	parser.add_option("--store-compressed", dest = "store_compressed", type = "int", action = "store", default = 1,
 			help = "store or not compressed data")
 
-	parser.add_option("--to-linear-rgb", dest = "to_linear_rgb",
-			type = "int", action = "store", default = 0,
-			help = "assume the input textures are sRGB. If this option is " \
-			"true then convert them to linear RGB")
+	parser.add_option("--to-linear-rgb", dest = "to_linear_rgb", type = "int", action = "store", default = 0,
+			help = "assume the input textures are sRGB. If this option is true then convert them to linear RGB")
 
-	parser.add_option("--filter", dest = "filter", type = "string",
-			default = "default", help = "texture filtering. Can be: " \
-			"default, linear, nearest")
+	parser.add_option("--filter", dest = "filter", type = "string", default = "default", 
+			help = "texture filtering. Can be: default, linear, nearest")
 
-	parser.add_option("--mips-count", dest = "mips_count", type = "int",
-			default = "0xFFFF", help = "Max number of mipmaps")
+	parser.add_option("--mips-count", dest = "mips_count", type = "int", default = "0xFFFF", 
+			help = "Max number of mipmaps")
 
 	# Add the default value on each option when printing help
 	for option in parser.option_list:
@@ -269,10 +256,8 @@ def parse_commandline():
 	else:
 		parser.error("Unrecognized type: " + options.filter)
 
-	if not options.store_uncompressed \
-			and not options.store_compressed:
-		parser.error("One of --store-compressed and --store-uncompressed "\
-				"should be True")
+	if not options.store_uncompressed and not options.store_compressed:
+		parser.error("One of --store-compressed and --store-uncompressed should be True")
 
 	if int(options.mips_count) <= 0:
 		parser.error("Wrong number of mipmaps")
@@ -300,8 +285,8 @@ def identify_image(in_file):
 	width = 0
 	height = 0
 
-	proc = subprocess.Popen(["identify", "-verbose" , in_file],
-			stdout=subprocess.PIPE)
+	print(["identify", "-verbose" , in_file])
+	proc = subprocess.Popen(["identify", "-verbose" , in_file], stdout=subprocess.PIPE)
 
 	stdout_str = proc.stdout.read()
 
@@ -335,8 +320,7 @@ def identify_image(in_file):
 
 	return (color_format, int(reg.group(1)), int(reg.group(2)))
 
-def create_mipmaps(in_file, tmp_dir, width_, height_, color_format, \
-		to_linear_rgb, max_mip_count):
+def create_mipmaps(in_file, tmp_dir, width_, height_, color_format, to_linear_rgb, max_mip_count):
 	""" Create a number of images for all mipmaps """
 
 	printi("Generate mipmaps")
@@ -348,8 +332,7 @@ def create_mipmaps(in_file, tmp_dir, width_, height_, color_format, \
 
 	while width >= 4 and height >= 4:
 		size_str = "%dx%d" % (width, height)
-		out_file_str = os.path.join(tmp_dir, get_base_fname(in_file)) \
-				+ "." + size_str
+		out_file_str = os.path.join(tmp_dir, get_base_fname(in_file)) + "." + size_str
 
 		printi("  %s.tga" % out_file_str)
 
@@ -421,8 +404,7 @@ def create_etc_images(mips_fnames, tmp_dir, fast, color_format, convert_path):
 		else:
 			args.append("RGBA")
 
-		# Call the executable AND change the working directory so that etcpack
-		# will find convert
+		# Call the executable AND change the working directory so that etcpack will find convert
 		subprocess.check_call(args, stdout = subprocess.PIPE, cwd = tmp_dir)
 
 def create_dds_images(mips_fnames, tmp_dir, fast, color_format, normal):
@@ -471,8 +453,7 @@ def write_raw(tex_file, fname, width, height, color_format):
 	printi("  Appending %s" % fname)
 
 	# Read and check the header
-	uncompressed_tga_header = struct.pack("BBBBBBBBBBBB", \
-			0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+	uncompressed_tga_header = struct.pack("BBBBBBBBBBBB", 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0)
 
 	in_file = open(fname, "rb")
 	tga_header = in_file.read(12)
@@ -495,8 +476,7 @@ def write_raw(tex_file, fname, width, height, color_format):
 	img_height = header6[3] * 256 + header6[2]
 	img_bpp = header6[4];
 
-	if (color_format != CF_RGB8 or img_bpp != 24) \
-			and (color_format != CF_RGBA8 or img_bpp != 32):
+	if (color_format != CF_RGB8 or img_bpp != 24) and (color_format != CF_RGBA8 or img_bpp != 32):
 		raise Exception("Unexpected bpp")
 
 	if img_width != width or img_height != height:
@@ -545,12 +525,10 @@ def write_s3tc(out_file, fname, width, height, color_format):
 	if dds_header.dwWidth != width or dds_header.dwHeight != height:
 		raise Exception("Incorrect width")
 
-	if color_format == CF_RGB8 \
-			and dds_header.dwFourCC != "DXT1":
+	if color_format == CF_RGB8 and dds_header.dwFourCC != "DXT1":
 		raise Exception("Incorrect format. Expecting DXT1")
 
-	if color_format == CF_RGBA8 \
-			and dds_header.dwFourCC != "DXT5":
+	if color_format == CF_RGBA8 and dds_header.dwFourCC != "DXT5":
 		raise Exception("Incorrect format. Expecting DXT5")
 
 	# Read and write the data
@@ -633,16 +611,14 @@ def convert(config):
 
 	# Create images
 	for in_file in config.in_files:
-		mips_fnames = create_mipmaps(in_file, config.tmp_dir, width, height, \
-				color_format, config.to_linear_rgb, config.mips_count)
+		mips_fnames = create_mipmaps(in_file, config.tmp_dir, width, height, color_format, config.to_linear_rgb, 
+				config.mips_count)
 
 		# Create etc images
-		create_etc_images(mips_fnames, config.tmp_dir, config.fast, \
-				color_format, config.convert_path)
+		create_etc_images(mips_fnames, config.tmp_dir, config.fast, color_format, config.convert_path)
 
 		# Create dds images
-		create_dds_images(mips_fnames, config.tmp_dir, config.fast, \
-				color_format, config.normal)
+		create_dds_images(mips_fnames, config.tmp_dir, config.fast, color_format, config.normal)
 
 	# Open file
 	fname = config.out_file
@@ -693,21 +669,17 @@ def convert(config):
 			# For each image
 			for in_file in config.in_files:
 				size_str = "%dx%d" % (tmp_width, tmp_height)
-				in_base_fname = os.path.join(config.tmp_dir, \
-						get_base_fname(in_file)) + "." + size_str
+				in_base_fname = os.path.join(config.tmp_dir, get_base_fname(in_file)) + "." + size_str
 
 				# Write RAW
 				if compression == 0 and config.store_uncompressed:
-					write_raw(tex_file, in_base_fname + ".tga", \
-							tmp_width, tmp_height, color_format)
+					write_raw(tex_file, in_base_fname + ".tga", tmp_width, tmp_height, color_format)
 				# Write S3TC
 				elif compression == 1 and config.store_compressed:
-					write_s3tc(tex_file, in_base_fname + ".dds", \
-							tmp_width, tmp_height, color_format)
+					write_s3tc(tex_file, in_base_fname + ".dds", tmp_width, tmp_height, color_format)
 				# Write ETC
 				elif compression == 2 and config.store_compressed:
-					write_etc(tex_file, in_base_fname + "_flip.pkm", \
-							tmp_width, tmp_height, color_format)
+					write_etc(tex_file, in_base_fname + "_flip.pkm", tmp_width, tmp_height, color_format)
 
 			tmp_width = tmp_width / 2
 			tmp_height = tmp_height / 2
@@ -721,8 +693,7 @@ def main():
 	if config.type == TT_CUBE and len(config.in_files) != 6:
 		raise Exception("Not enough images for cube generation")
 
-	if (config.type == TT_3D or config.type == TT_2D_ARRAY) \
-			and len(config.in_files) < 2:
+	if (config.type == TT_3D or config.type == TT_2D_ARRAY) and len(config.in_files) < 2:
 		#raise Exception("Not enough images for 2DArray/3D texture")
 		printw("Not enough images for 2DArray/3D texture")
 

+ 15 - 22
tools/texture/create_atlas.py

@@ -56,22 +56,17 @@ def printi(msg):
 def parse_commandline():
 	""" Parse the command line arguments """
 
-	parser = argparse.ArgumentParser(
-			description = "This program creates a texture atlas",
+	parser = argparse.ArgumentParser(description = "This program creates a texture atlas",
 			formatter_class = argparse.ArgumentDefaultsHelpFormatter)
 
 	parser.add_argument("-i", "--input", nargs = "+", required = True,
 			help = "specify the image(s) to convert. Seperate with space")
 
-	parser.add_argument("-o", "--output", default = "atlas.png",
-			help = "specify output PNG image.")
+	parser.add_argument("-o", "--output", default = "atlas.png", help = "specify output PNG image.")
 
-	parser.add_argument("-m", "--margin", type = int, default = 0,
-			help = "specify the margin.")
+	parser.add_argument("-m", "--margin", type = int, default = 0, help = "specify the margin.")
 
-	parser.add_argument("-b", "--background-color", 
-			help = "specify background of empty areas",
-			default = "ff00ff00")
+	parser.add_argument("-b", "--background-color", help = "specify background of empty areas", default = "ff00ff00")
 
 	args = parser.parse_args()
 
@@ -98,8 +93,7 @@ def load_images(ctx):
 			ctx.mode = img.image.mode
 		else:
 			if ctx.mode != img.image.mode:
-				raise Exception("Image \"%s\" has a different mode: \"%s\"" \
-						% (i, img.image.mode))
+				raise Exception("Image \"%s\" has a different mode: \"%s\"" % (i, img.image.mode))
 
 		img.width = img.image.size[0]
 		img.height = img.image.size[1]
@@ -174,7 +168,7 @@ def place_sub_images(ctx):
 		sub_image = ctx.sub_images[unplaced_imgs[0]]
 		unplaced_imgs.pop(0)
 
-		printi("Will try to place image \"%s\" of size %ux%d" % \
+		printi("Will try to place image \"%s\" of size %ux%d" % 
 				(sub_image.image_name, sub_image.width, sub_image.height))
 
 		# Find best frame
@@ -246,11 +240,9 @@ def create_atlas(ctx):
 	draw.rectangle((0, 0, ctx.atlas_width, ctx.atlas_height), bg_color)
 
 	for sub_image in ctx.sub_images:
-		assert sub_image.atlas_x != 0xFFFFFFFF and \
-				sub_image.atlas_y != 0xFFFFFFFF, "See file"
+		assert sub_image.atlas_x != 0xFFFFFFFF and sub_image.atlas_y != 0xFFFFFFFF, "See file"
 
-		atlas_img.paste(sub_image.image, \
-				(int(sub_image.atlas_x), int(sub_image.atlas_y)))
+		atlas_img.paste(sub_image.image, (int(sub_image.atlas_x), int(sub_image.atlas_y)))
 
 	printi("Saving atlas \"%s\"" % ctx.out_file)
 	atlas_img.save(ctx.out_file)
@@ -261,12 +253,13 @@ def write_xml(ctx):
 	fname = os.path.splitext(ctx.out_file)[0] + ".ankiatex"
 	printi("Writing XML \"%s\"" % fname)
 	f = open(fname, "w")
-	f.write("<atlasTexture>\n")
+	f.write("<textureAtlas>\n")
 	f.write("\t<texture>%s</texture>\n" % ctx.out_file)
-	f.write("\t<subImages>\n")
+	f.write("\t<subTextureMargin>%u</subTextureMargin>\n" % ctx.margin)
+	f.write("\t<subTextures>\n")
 
 	for sub_image in ctx.sub_images:
-		f.write("\t\t<subImage>\n")
+		f.write("\t\t<subTexture>\n")
 		f.write("\t\t\t<name>%s</name>\n" % sub_image.image_name)
 
 		# Now change coordinate system
@@ -276,10 +269,10 @@ def write_xml(ctx):
 		bottom = top - (sub_image.height / ctx.atlas_height)
 
 		f.write("\t\t\t<uv>%f %f %f %f</uv>\n" % (left, bottom, right, top))
-		f.write("\t\t</subImage>\n")
+		f.write("\t\t</subTexture>\n")
 
-	f.write("\t</subImages>\n")
-	f.write("</atlasTexture>\n")
+	f.write("\t</subTextures>\n")
+	f.write("</textureAtlas>\n")
 
 def main():
 	""" The main """