Browse Source

Add a raster path on boom passes

Panagiotis Christopoulos Charitos 4 years ago
parent
commit
372964da37

+ 76 - 21
AnKi/Renderer/Bloom.cpp

@@ -32,7 +32,9 @@ Error Bloom::initExposure()
 	m_exposure.m_rtDescr.bake();
 	m_exposure.m_rtDescr.bake();
 
 
 	// init shaders
 	// init shaders
-	ANKI_CHECK(getResourceManager().loadResource("Shaders/Bloom.ankiprog", m_exposure.m_prog));
+	ANKI_CHECK(getResourceManager().loadResource((getConfig().getRPreferCompute()) ? "Shaders/BloomCompute.ankiprog"
+																				   : "Shaders/BloomRaster.ankiprog",
+												 m_exposure.m_prog));
 
 
 	ShaderProgramResourceVariantInitInfo variantInitInfo(m_exposure.m_prog);
 	ShaderProgramResourceVariantInitInfo variantInitInfo(m_exposure.m_prog);
 	variantInitInfo.addConstant("FB_SIZE", UVec2(m_exposure.m_width, m_exposure.m_height));
 	variantInitInfo.addConstant("FB_SIZE", UVec2(m_exposure.m_width, m_exposure.m_height));
@@ -40,9 +42,6 @@ Error Bloom::initExposure()
 	const ShaderProgramResourceVariant* variant;
 	const ShaderProgramResourceVariant* variant;
 	m_exposure.m_prog->getOrCreateVariant(variantInitInfo, variant);
 	m_exposure.m_prog->getOrCreateVariant(variantInitInfo, variant);
 	m_exposure.m_grProg = variant->getProgram();
 	m_exposure.m_grProg = variant->getProgram();
-	ANKI_ASSERT(variant->getWorkgroupSizes()[0] == m_workgroupSize[0]
-				&& variant->getWorkgroupSizes()[1] == m_workgroupSize[1]
-				&& variant->getWorkgroupSizes()[2] == m_workgroupSize[2]);
 
 
 	return Error::NONE;
 	return Error::NONE;
 }
 }
@@ -58,7 +57,10 @@ Error Bloom::initUpscale()
 	m_upscale.m_rtDescr.bake();
 	m_upscale.m_rtDescr.bake();
 
 
 	// init shaders
 	// init shaders
-	ANKI_CHECK(getResourceManager().loadResource("Shaders/BloomUpscale.ankiprog", m_upscale.m_prog));
+	ANKI_CHECK(getResourceManager().loadResource((getConfig().getRPreferCompute())
+													 ? "Shaders/BloomUpscaleCompute.ankiprog"
+													 : "Shaders/BloomUpscaleRaster.ankiprog",
+												 m_upscale.m_prog));
 
 
 	ShaderProgramResourceVariantInitInfo variantInitInfo(m_upscale.m_prog);
 	ShaderProgramResourceVariantInitInfo variantInitInfo(m_upscale.m_prog);
 	variantInitInfo.addConstant("FB_SIZE", UVec2(m_upscale.m_width, m_upscale.m_height));
 	variantInitInfo.addConstant("FB_SIZE", UVec2(m_upscale.m_width, m_upscale.m_height));
@@ -67,9 +69,6 @@ Error Bloom::initUpscale()
 	const ShaderProgramResourceVariant* variant;
 	const ShaderProgramResourceVariant* variant;
 	m_upscale.m_prog->getOrCreateVariant(variantInitInfo, variant);
 	m_upscale.m_prog->getOrCreateVariant(variantInitInfo, variant);
 	m_upscale.m_grProg = variant->getProgram();
 	m_upscale.m_grProg = variant->getProgram();
-	ANKI_ASSERT(variant->getWorkgroupSizes()[0] == m_workgroupSize[0]
-				&& variant->getWorkgroupSizes()[1] == m_workgroupSize[1]
-				&& variant->getWorkgroupSizes()[2] == m_workgroupSize[2]);
 
 
 	// Textures
 	// Textures
 	ANKI_CHECK(getResourceManager().loadResource("EngineAssets/LensDirt.ankitex", m_upscale.m_lensDirtImage));
 	ANKI_CHECK(getResourceManager().loadResource("EngineAssets/LensDirt.ankitex", m_upscale.m_lensDirtImage));
@@ -80,6 +79,7 @@ Error Bloom::initUpscale()
 void Bloom::populateRenderGraph(RenderingContext& ctx)
 void Bloom::populateRenderGraph(RenderingContext& ctx)
 {
 {
 	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
 	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
+	const Bool preferCompute = getConfig().getRPreferCompute();
 
 
 	// Main pass
 	// Main pass
 	{
 	{
@@ -87,14 +87,34 @@ void Bloom::populateRenderGraph(RenderingContext& ctx)
 		m_runCtx.m_exposureRt = rgraph.newRenderTarget(m_exposure.m_rtDescr);
 		m_runCtx.m_exposureRt = rgraph.newRenderTarget(m_exposure.m_rtDescr);
 
 
 		// Set the render pass
 		// Set the render pass
-		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("Bloom Main");
-
 		TextureSubresourceInfo inputTexSubresource;
 		TextureSubresourceInfo inputTexSubresource;
 		inputTexSubresource.m_firstMipmap = m_r->getDownscaleBlur().getMipmapCount() - 1;
 		inputTexSubresource.m_firstMipmap = m_r->getDownscaleBlur().getMipmapCount() - 1;
-		rpass.newDependency({m_r->getDownscaleBlur().getRt(), TextureUsageBit::SAMPLED_COMPUTE, inputTexSubresource});
-		rpass.newDependency({m_runCtx.m_exposureRt, TextureUsageBit::IMAGE_COMPUTE_WRITE});
 
 
-		rpass.setWork([this](RenderPassWorkContext& rgraphCtx) {
+		RenderPassDescriptionBase* prpass;
+		if(preferCompute)
+		{
+			ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("Bloom Main");
+
+			rpass.newDependency(RenderPassDependency(m_r->getDownscaleBlur().getRt(), TextureUsageBit::SAMPLED_COMPUTE,
+													 inputTexSubresource));
+			rpass.newDependency(RenderPassDependency(m_runCtx.m_exposureRt, TextureUsageBit::IMAGE_COMPUTE_WRITE));
+
+			prpass = &rpass;
+		}
+		else
+		{
+			GraphicsRenderPassDescription& rpass = rgraph.newGraphicsRenderPass("Bloom Main");
+			rpass.setFramebufferInfo(m_fbDescr, {m_runCtx.m_exposureRt}, {});
+
+			rpass.newDependency(RenderPassDependency(m_r->getDownscaleBlur().getRt(), TextureUsageBit::SAMPLED_FRAGMENT,
+													 inputTexSubresource));
+			rpass.newDependency(
+				RenderPassDependency(m_runCtx.m_exposureRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE));
+
+			prpass = &rpass;
+		}
+
+		prpass->setWork([this](RenderPassWorkContext& rgraphCtx) {
 			CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 			CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
 
 			cmdb->bindShaderProgram(m_exposure.m_grProg);
 			cmdb->bindShaderProgram(m_exposure.m_grProg);
@@ -110,9 +130,19 @@ void Bloom::populateRenderGraph(RenderingContext& ctx)
 
 
 			rgraphCtx.bindStorageBuffer(0, 2, m_r->getTonemapping().getAverageLuminanceBuffer());
 			rgraphCtx.bindStorageBuffer(0, 2, m_r->getTonemapping().getAverageLuminanceBuffer());
 
 
-			rgraphCtx.bindImage(0, 3, m_runCtx.m_exposureRt, TextureSubresourceInfo());
+			if(getConfig().getRPreferCompute())
+			{
+				rgraphCtx.bindImage(0, 3, m_runCtx.m_exposureRt, TextureSubresourceInfo());
 
 
-			dispatchPPCompute(cmdb, m_workgroupSize[0], m_workgroupSize[1], m_exposure.m_width, m_exposure.m_height);
+				dispatchPPCompute(cmdb, m_workgroupSize[0], m_workgroupSize[1], m_exposure.m_width,
+								  m_exposure.m_height);
+			}
+			else
+			{
+				cmdb->setViewport(0, 0, m_exposure.m_width, m_exposure.m_height);
+
+				cmdb->drawArrays(PrimitiveTopology::TRIANGLES, 3);
+			}
 		});
 		});
 	}
 	}
 
 
@@ -122,12 +152,28 @@ void Bloom::populateRenderGraph(RenderingContext& ctx)
 		m_runCtx.m_upscaleRt = rgraph.newRenderTarget(m_upscale.m_rtDescr);
 		m_runCtx.m_upscaleRt = rgraph.newRenderTarget(m_upscale.m_rtDescr);
 
 
 		// Set the render pass
 		// Set the render pass
-		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("Bloom Upscale");
+		RenderPassDescriptionBase* prpass;
+		if(preferCompute)
+		{
+			ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("Bloom Upscale");
+
+			rpass.newDependency({m_runCtx.m_exposureRt, TextureUsageBit::SAMPLED_COMPUTE});
+			rpass.newDependency({m_runCtx.m_upscaleRt, TextureUsageBit::IMAGE_COMPUTE_WRITE});
+
+			prpass = &rpass;
+		}
+		else
+		{
+			GraphicsRenderPassDescription& rpass = rgraph.newGraphicsRenderPass("Bloom Upscale");
+			rpass.setFramebufferInfo(m_fbDescr, {m_runCtx.m_upscaleRt}, {});
 
 
-		rpass.newDependency({m_runCtx.m_exposureRt, TextureUsageBit::SAMPLED_COMPUTE});
-		rpass.newDependency({m_runCtx.m_upscaleRt, TextureUsageBit::IMAGE_COMPUTE_WRITE});
+			rpass.newDependency({m_runCtx.m_exposureRt, TextureUsageBit::SAMPLED_FRAGMENT});
+			rpass.newDependency({m_runCtx.m_upscaleRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
 
 
-		rpass.setWork([this](RenderPassWorkContext& rgraphCtx) {
+			prpass = &rpass;
+		}
+
+		prpass->setWork([this](RenderPassWorkContext& rgraphCtx) {
 			CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 			CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
 
 			cmdb->bindShaderProgram(m_upscale.m_grProg);
 			cmdb->bindShaderProgram(m_upscale.m_grProg);
@@ -136,9 +182,18 @@ void Bloom::populateRenderGraph(RenderingContext& ctx)
 			rgraphCtx.bindColorTexture(0, 1, m_runCtx.m_exposureRt);
 			rgraphCtx.bindColorTexture(0, 1, m_runCtx.m_exposureRt);
 			cmdb->bindTexture(0, 2, m_upscale.m_lensDirtImage->getTextureView());
 			cmdb->bindTexture(0, 2, m_upscale.m_lensDirtImage->getTextureView());
 
 
-			rgraphCtx.bindImage(0, 3, m_runCtx.m_upscaleRt, TextureSubresourceInfo());
+			if(getConfig().getRPreferCompute())
+			{
+				rgraphCtx.bindImage(0, 3, m_runCtx.m_upscaleRt, TextureSubresourceInfo());
+
+				dispatchPPCompute(cmdb, m_workgroupSize[0], m_workgroupSize[1], m_upscale.m_width, m_upscale.m_height);
+			}
+			else
+			{
+				cmdb->setViewport(0, 0, m_upscale.m_width, m_upscale.m_height);
 
 
-			dispatchPPCompute(cmdb, m_workgroupSize[0], m_workgroupSize[1], m_upscale.m_width, m_upscale.m_height);
+				cmdb->drawArrays(PrimitiveTopology::TRIANGLES, 3);
+			}
 		});
 		});
 	}
 	}
 }
 }

+ 5 - 1
AnKi/Renderer/Bloom.h

@@ -43,7 +43,9 @@ public:
 private:
 private:
 	static constexpr Format RT_PIXEL_FORMAT = Format::A2B10G10R10_UNORM_PACK32;
 	static constexpr Format RT_PIXEL_FORMAT = Format::A2B10G10R10_UNORM_PACK32;
 
 
-	const Array<U32, 3> m_workgroupSize = {16, 16, 1};
+	const Array<U32, 2> m_workgroupSize = {16, 16};
+
+	FramebufferDescription m_fbDescr;
 
 
 	class
 	class
 	{
 	{
@@ -84,6 +86,8 @@ private:
 	{
 	{
 		ANKI_CHECK(initExposure());
 		ANKI_CHECK(initExposure());
 		ANKI_CHECK(initUpscale());
 		ANKI_CHECK(initUpscale());
+		m_fbDescr.m_colorAttachmentCount = 1;
+		m_fbDescr.bake();
 		return Error::NONE;
 		return Error::NONE;
 	}
 	}
 };
 };

+ 22 - 15
AnKi/Shaders/Bloom.ankiprog → AnKi/Shaders/Bloom.glsl

@@ -4,41 +4,45 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(FB_SIZE, 0u);
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(FB_SIZE, 0u);
-const UVec2 WORKGROUP_SIZE = UVec2(16, 16);
 
 
-#pragma anki start comp
-#include <AnKi/Shaders/Common.glsl>
 #include <AnKi/Shaders/TonemappingFunctions.glsl>
 #include <AnKi/Shaders/TonemappingFunctions.glsl>
+#include <AnKi/Shaders/Functions.glsl>
 
 
-layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
-
-// Vars
 layout(set = 0, binding = 0) uniform sampler u_linearAnyClampSampler;
 layout(set = 0, binding = 0) uniform sampler u_linearAnyClampSampler;
 layout(set = 0, binding = 1) uniform ANKI_RP texture2D u_tex; ///< Its the IS RT
 layout(set = 0, binding = 1) uniform ANKI_RP texture2D u_tex; ///< Its the IS RT
 
 
-layout(push_constant) uniform pc_
+layout(push_constant) uniform b_pc
 {
 {
 	Vec4 u_thresholdScalePad2;
 	Vec4 u_thresholdScalePad2;
 };
 };
 
 
-layout(set = 0, binding = 2, std140) readonly buffer ss0_
+layout(set = 0, binding = 2, std140) readonly buffer b_avgLum
 {
 {
-	Vec4 u_averageLuminancePad3;
+	ANKI_RP Vec4 u_averageLuminancePad3;
 };
 };
 
 
+#if defined(ANKI_COMPUTE_SHADER)
+const UVec2 WORKGROUP_SIZE = UVec2(16, 16);
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
+
 layout(set = 0, binding = 3) writeonly uniform image2D u_outImg;
 layout(set = 0, binding = 3) writeonly uniform image2D u_outImg;
+#else
+layout(location = 0) in Vec2 in_uv;
+layout(location = 0) out ANKI_RP Vec3 out_color;
+#endif
 
 
 void main()
 void main()
 {
 {
-	if((FB_SIZE.x % WORKGROUP_SIZE.x) != 0u || (FB_SIZE.y % WORKGROUP_SIZE.y) != 0u) // This check is free
+#if defined(ANKI_COMPUTE_SHADER)
+	if(skipOutOfBoundsInvocations(WORKGROUP_SIZE, FB_SIZE))
 	{
 	{
-		if(gl_GlobalInvocationID.x >= FB_SIZE.x || gl_GlobalInvocationID.y >= FB_SIZE.y)
-		{
-			return;
-		}
+		return;
 	}
 	}
 
 
 	const Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(FB_SIZE);
 	const Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(FB_SIZE);
+#else
+	const Vec2 uv = in_uv;
+#endif
 
 
 	ANKI_RP F32 weight = 1.0 / 5.0;
 	ANKI_RP F32 weight = 1.0 / 5.0;
 	ANKI_RP Vec3 color = textureLod(u_tex, u_linearAnyClampSampler, uv, 0.0).rgb * weight;
 	ANKI_RP Vec3 color = textureLod(u_tex, u_linearAnyClampSampler, uv, 0.0).rgb * weight;
@@ -49,6 +53,9 @@ void main()
 
 
 	color = tonemap(color, u_averageLuminancePad3.x, u_thresholdScalePad2.x) * u_thresholdScalePad2.y;
 	color = tonemap(color, u_averageLuminancePad3.x, u_thresholdScalePad2.x) * u_thresholdScalePad2.y;
 
 
+#if defined(ANKI_COMPUTE_SHADER)
 	imageStore(u_outImg, IVec2(gl_GlobalInvocationID.xy), Vec4(color, 0.0));
 	imageStore(u_outImg, IVec2(gl_GlobalInvocationID.xy), Vec4(color, 0.0));
+#else
+	out_color = color;
+#endif
 }
 }
-#pragma anki end

+ 8 - 0
AnKi/Shaders/BloomCompute.ankiprog

@@ -0,0 +1,8 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki start comp
+#include <AnKi/Shaders/Bloom.glsl>
+#pragma anki end

+ 12 - 0
AnKi/Shaders/BloomRaster.ankiprog

@@ -0,0 +1,12 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki start vert
+#include <AnKi/Shaders/QuadVert.glsl>
+#pragma anki end
+
+#pragma anki start frag
+#include <AnKi/Shaders/Bloom.glsl>
+#pragma anki end

+ 19 - 7
AnKi/Shaders/BloomUpscale.ankiprog → AnKi/Shaders/BloomUpscale.glsl

@@ -5,18 +5,22 @@
 
 
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(FB_SIZE, 0u);
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(FB_SIZE, 0u);
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(INPUT_TEX_SIZE, 2u);
 ANKI_SPECIALIZATION_CONSTANT_UVEC2(INPUT_TEX_SIZE, 2u);
-const UVec2 WORKGROUP_SIZE = UVec2(16u, 16u);
 
 
-#pragma anki start comp
 #include <AnKi/Shaders/Functions.glsl>
 #include <AnKi/Shaders/Functions.glsl>
 
 
-layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
-
 layout(set = 0, binding = 0) uniform sampler u_linearAnyClampSampler;
 layout(set = 0, binding = 0) uniform sampler u_linearAnyClampSampler;
 layout(set = 0, binding = 1) uniform ANKI_RP texture2D u_tex;
 layout(set = 0, binding = 1) uniform ANKI_RP texture2D u_tex;
 layout(set = 0, binding = 2) uniform ANKI_RP texture2D u_lensDirtTex;
 layout(set = 0, binding = 2) uniform ANKI_RP texture2D u_lensDirtTex;
 
 
-layout(set = 0, binding = 3) writeonly uniform image2D out_img;
+#if defined(ANKI_COMPUTE_SHADER)
+const UVec2 WORKGROUP_SIZE = UVec2(16u, 16u);
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
+
+layout(set = 0, binding = 3) writeonly uniform ANKI_RP image2D u_outImg;
+#else
+layout(location = 0) in Vec2 in_uv;
+layout(location = 0) out ANKI_RP Vec3 out_color;
+#endif
 
 
 // Constants
 // Constants
 const U32 MAX_GHOSTS = 4u;
 const U32 MAX_GHOSTS = 4u;
@@ -93,14 +97,22 @@ ANKI_RP Vec3 upscale(Vec2 uv)
 
 
 void main()
 void main()
 {
 {
+#if defined(ANKI_COMPUTE_SHADER)
 	if(skipOutOfBoundsInvocations(WORKGROUP_SIZE, FB_SIZE))
 	if(skipOutOfBoundsInvocations(WORKGROUP_SIZE, FB_SIZE))
 	{
 	{
 		return;
 		return;
 	}
 	}
 
 
 	const Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(FB_SIZE);
 	const Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(FB_SIZE);
+#else
+	const Vec2 uv = in_uv;
+#endif
 
 
 	const ANKI_RP Vec3 outColor = ssLensFlare(uv) + upscale(uv);
 	const ANKI_RP Vec3 outColor = ssLensFlare(uv) + upscale(uv);
-	imageStore(out_img, IVec2(gl_GlobalInvocationID.xy), Vec4(outColor, 0.0));
+
+#if defined(ANKI_COMPUTE_SHADER)
+	imageStore(u_outImg, IVec2(gl_GlobalInvocationID.xy), Vec4(outColor, 0.0));
+#else
+	out_color = outColor;
+#endif
 }
 }
-#pragma anki end

+ 8 - 0
AnKi/Shaders/BloomUpscaleCompute.ankiprog

@@ -0,0 +1,8 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki start comp
+#include <AnKi/Shaders/BloomUpscale.glsl>
+#pragma anki end

+ 12 - 0
AnKi/Shaders/BloomUpscaleRaster.ankiprog

@@ -0,0 +1,12 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki start vert
+#include <AnKi/Shaders/QuadVert.glsl>
+#pragma anki end
+
+#pragma anki start frag
+#include <AnKi/Shaders/BloomUpscale.glsl>
+#pragma anki end

+ 5 - 1
Tools/Android/GenerateAndroidProject.py

@@ -56,10 +56,12 @@ def main():
     ctx = parse_commandline()
     ctx = parse_commandline()
     this_script_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
     this_script_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
 
 
-    # Copy dir
+    # Copy template
     project_dir = os.path.join(ctx.out_dir, "AndroidProject_%s" % ctx.target)
     project_dir = os.path.join(ctx.out_dir, "AndroidProject_%s" % ctx.target)
     if not os.path.isdir(project_dir):
     if not os.path.isdir(project_dir):
         shutil.copytree(this_script_dir, project_dir)
         shutil.copytree(this_script_dir, project_dir)
+    else:
+        print("Project directory (%s) already exists. Won't copy template" % project_dir)
 
 
     # RM the script
     # RM the script
     try:
     try:
@@ -80,6 +82,8 @@ def main():
         os.symlink(os.path.join(this_script_dir, "../../ThirdParty/FidelityFX"),
         os.symlink(os.path.join(this_script_dir, "../../ThirdParty/FidelityFX"),
                    os.path.join(project_dir, "assets/ThirdParty/FidelityFX"))
                    os.path.join(project_dir, "assets/ThirdParty/FidelityFX"))
         os.symlink(ctx.asserts_dir, os.path.join(project_dir, "assets/Assets"))
         os.symlink(ctx.asserts_dir, os.path.join(project_dir, "assets/Assets"))
+    else:
+        print("Asset directory (%s) already exists. Skipping" % assets_dir)
 
 
     # Write the asset directory structure to a file
     # Write the asset directory structure to a file
     dir_structure_file = open(os.path.join(assets_dir, "DirStructure.txt"), "w", newline="\n")
     dir_structure_file = open(os.path.join(assets_dir, "DirStructure.txt"), "w", newline="\n")