Browse Source

Move SSAO to compute. Better perf on AMD

Panagiotis Christopoulos Charitos 7 years ago
parent
commit
4ae62c5e0b

+ 2 - 103
programs/Ssao.ankiprog

@@ -24,112 +24,11 @@ http://www.anki3d.org/LICENSE
 				<input name="BIAS" type="float" const="1"/>
 				<input name="BIAS" type="float" const="1"/>
 				<input name="STRENGTH" type="float" const="1"/>
 				<input name="STRENGTH" type="float" const="1"/>
 				<input name="SAMPLE_COUNT" type="uint" const="1"/>
 				<input name="SAMPLE_COUNT" type="uint" const="1"/>
+				<input name="WORKGROUP_SIZE" type="uvec2" const="1"/>
 			</inputs>
 			</inputs>
 
 
 			<source><![CDATA[
 			<source><![CDATA[
-#include "shaders/Common.glsl"
-#include "shaders/Pack.glsl"
-#include "shaders/Functions.glsl"
-
-layout(location = 0) in vec2 in_uv;
-
-layout(location = 0) out float out_color;
-
-layout(ANKI_UBO_BINDING(0, 0), std140, row_major) uniform _blk
-{
-	vec4 u_unprojectionParams;
-	vec4 u_projectionMat;
-	mat3 u_viewRotMat;
-};
-
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_mMsDepthRt;
-layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2DArray u_noiseMap;
-#if USE_NORMAL
-layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_msRt;
-#endif
-
-#if USE_NORMAL
-// Get normal
-vec3 readNormal(in vec2 uv)
-{
-	vec3 normal;
-	readNormalFromGBuffer(u_msRt, uv, normal);
-	normal = u_viewRotMat * normal;
-	return normal;
-}
-#endif
-
-// Read the noise tex
-vec3 readRandom(vec2 uv, float layer)
-{
-	const vec2 tmp = vec2(float(FB_SIZE.x) / float(NOISE_MAP_SIZE), float(FB_SIZE.y) / float(NOISE_MAP_SIZE));
-	vec3 r = texture(u_noiseMap, vec3(tmp * uv, layer)).rgb;
-	return r;
-}
-
-// Returns the Z of the position in view space
-float readZ(in vec2 uv)
-{
-	float depth = textureLod(u_mMsDepthRt, uv, 0.0).r;
-	float z = u_unprojectionParams.z / (u_unprojectionParams.w + depth);
-	return z;
-}
-
-// Read position in view space
-vec3 readPosition(in vec2 uv)
-{
-	vec3 fragPosVspace;
-	fragPosVspace.z = readZ(uv);
-	fragPosVspace.xy = UV_TO_NDC(uv) * u_unprojectionParams.xy * fragPosVspace.z;
-
-	return fragPosVspace;
-}
-
-vec4 project(vec4 point)
-{
-	return projectPerspective(point, u_projectionMat.x, u_projectionMat.y, u_projectionMat.z, u_projectionMat.w);
-}
-
-void main(void)
-{
-	vec2 ndc = UV_TO_NDC(in_uv);
-
-	// Compute origin
-	vec3 origin = readPosition(in_uv);
-
-	// Get normal
-#if USE_NORMAL
-	vec3 normal = readNormal(in_uv);
-#else
-	vec3 normal = normalize(cross(dFdx(origin), dFdy(origin)));
-#endif
-
-	// Find the projected radius
-	vec3 sphereLimit = origin + vec3(RADIUS, 0.0, 0.0);
-	vec4 projSphereLimit = project(vec4(sphereLimit, 1.0));
-	vec2 projSphereLimit2 = projSphereLimit.xy / projSphereLimit.w;
-	float projRadius = length(projSphereLimit2 - ndc);
-
-	// Loop to compute
-	float ssao = 0.0;
-	for(uint i = 0; i < SAMPLE_COUNT; ++i)
-	{
-		// Compute disk
-		vec3 randFactors = readRandom(in_uv, float(i));
-		vec2 dir = normalize(randFactors.xy * 2.0 - 1.0);
-		float radius = projRadius * (randFactors.z * 0.85 + 0.15);
-		vec2 finalDiskPoint = ndc + dir * radius;
-
-		// Compute factor
-		vec3 s = readPosition(NDC_TO_UV(finalDiskPoint));
-		vec3 u = s - origin;
-		ssao += max(dot(normal, u) + BIAS, EPSILON) / max(dot(u, u), EPSILON);
-	}
-
-	ssao *= (1.0 / float(SAMPLE_COUNT));
-	ssao = 1.0 - ssao * STRENGTH;
-	out_color = ssao;
-}
+#include "shaders/Ssao.glsl"
 			]]></source>
 			]]></source>
 		</shader>
 		</shader>
 	</shaders>
 	</shaders>

+ 31 - 0
programs/SsaoCompute.ankiprog

@@ -0,0 +1,31 @@
+<!-- 
+Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+All rights reserved.
+Code licensed under the BSD License.
+http://www.anki3d.org/LICENSE
+-->
+<shaderProgram>
+	<mutators>
+		<mutator name="USE_NORMAL" values="0 1"/>
+	</mutators>
+
+	<shaders>
+		<shader type="comp">
+			<inputs>
+				<input name="NOISE_MAP_SIZE" type="uint" const="1"/>
+				<input name="FB_SIZE" type="uvec2" const="1"/>
+				<input name="RADIUS" type="float" const="1"/>
+				<input name="BIAS" type="float" const="1"/>
+				<input name="STRENGTH" type="float" const="1"/>
+				<input name="SAMPLE_COUNT" type="uint" const="1"/>
+				<input name="WORKGROUP_SIZE" type="uvec2" const="1"/>
+			</inputs>
+
+			<source><![CDATA[
+#include "shaders/Ssao.glsl"
+			]]></source>
+		</shader>
+	</shaders>
+</shaderProgram>
+
+

+ 180 - 0
shaders/Ssao.glsl

@@ -0,0 +1,180 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// Contains the complete code for a SSAO pass. It requires some variables to be defined before this file is included.
+
+#ifndef ANKI_SHADERS_SSAO_GLSL
+#define ANKI_SHADERS_SSAO_GLSL
+
+#include "shaders/Common.glsl"
+#include "shaders/Pack.glsl"
+#include "shaders/Functions.glsl"
+
+#if defined(ANKI_FRAGMENT_SHADER)
+#	define USE_COMPUTE 0
+#else
+#	define USE_COMPUTE 1
+#endif
+
+#if !USE_COMPUTE
+layout(location = 0) in vec2 in_uv;
+layout(location = 0) out float out_color;
+#else
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
+
+layout(ANKI_IMAGE_BINDING(0, 0)) writeonly uniform image2D out_img;
+#endif
+
+layout(ANKI_UBO_BINDING(0, 0), std140, row_major) uniform _blk
+{
+	vec4 u_unprojectionParams;
+	vec4 u_projectionMat;
+	mat3 u_viewRotMat;
+};
+
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_mMsDepthRt;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2DArray u_noiseMap;
+#if USE_NORMAL
+layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_msRt;
+#endif
+
+// To compute the normals we need some extra work on compute
+#define COMPLEX_NORMALS (!USE_NORMAL && USE_COMPUTE)
+#if COMPLEX_NORMALS
+shared vec3 s_positions[WORKGROUP_SIZE.y][WORKGROUP_SIZE.x];
+#endif
+
+#if USE_NORMAL
+// Get normal
+vec3 readNormal(in vec2 uv)
+{
+	vec3 normal;
+	readNormalFromGBuffer(u_msRt, uv, normal);
+	normal = u_viewRotMat * normal;
+	return normal;
+}
+#endif
+
+// Read the noise tex
+vec3 readRandom(vec2 uv, float layer)
+{
+	const vec2 tmp = vec2(float(FB_SIZE.x) / float(NOISE_MAP_SIZE), float(FB_SIZE.y) / float(NOISE_MAP_SIZE));
+	vec3 r = texture(u_noiseMap, vec3(tmp * uv, layer)).rgb;
+	return r;
+}
+
+// Returns the Z of the position in view space
+float readZ(in vec2 uv)
+{
+	float depth = textureLod(u_mMsDepthRt, uv, 0.0).r;
+	float z = u_unprojectionParams.z / (u_unprojectionParams.w + depth);
+	return z;
+}
+
+// Read position in view space
+vec3 readPosition(in vec2 uv)
+{
+	vec3 fragPosVspace;
+	fragPosVspace.z = readZ(uv);
+	fragPosVspace.xy = UV_TO_NDC(uv) * u_unprojectionParams.xy * fragPosVspace.z;
+
+	return fragPosVspace;
+}
+
+vec4 project(vec4 point)
+{
+	return projectPerspective(point, u_projectionMat.x, u_projectionMat.y, u_projectionMat.z, u_projectionMat.w);
+}
+
+void main(void)
+{
+#if USE_COMPUTE
+	if(gl_GlobalInvocationID.x >= FB_SIZE.x || gl_GlobalInvocationID.y >= FB_SIZE.y)
+	{
+		// Skip if it's out of bounds
+		return;
+	}
+
+	vec2 uv = vec2(gl_GlobalInvocationID.xy) / vec2(FB_SIZE);
+#else
+	vec2 uv = in_uv;
+#endif
+
+	vec2 ndc = UV_TO_NDC(uv);
+
+	// Compute origin
+	vec3 origin = readPosition(uv);
+
+	// Get normal
+#if USE_NORMAL
+	vec3 normal = readNormal(uv);
+#elif !COMPLEX_NORMALS
+	vec3 normal = normalize(cross(dFdx(origin), dFdy(origin)));
+#else
+	// Every thread stores its position
+	s_positions[gl_LocalInvocationID.y][gl_LocalInvocationID.x] = origin;
+
+	memoryBarrierShared();
+	barrier();
+
+	// Have one thread every quad to compute the normal
+	if((gl_LocalInvocationID.x & 1u) + (gl_LocalInvocationID.y & 1u) == 0u)
+	{
+		// It's the bottom left pixel of the quad
+
+		vec3 center, right, top;
+		center = origin;
+		right = s_positions[gl_LocalInvocationID.y][gl_LocalInvocationID.x + 1u];
+		top = s_positions[gl_LocalInvocationID.y + 1u][gl_LocalInvocationID.x + 1u];
+
+		vec3 normal = normalize(cross(right - center, top - center));
+
+		// Broadcast the normal
+		s_positions[gl_LocalInvocationID.y + 0u][gl_LocalInvocationID.x + 0u] = normal;
+		s_positions[gl_LocalInvocationID.y + 0u][gl_LocalInvocationID.x + 1u] = normal;
+		s_positions[gl_LocalInvocationID.y + 1u][gl_LocalInvocationID.x + 0u] = normal;
+		s_positions[gl_LocalInvocationID.y + 1u][gl_LocalInvocationID.x + 1u] = normal;
+	}
+
+	memoryBarrierShared();
+	barrier();
+
+	vec3 normal = s_positions[gl_LocalInvocationID.y][gl_LocalInvocationID.x];
+#endif
+
+	// Find the projected radius
+	vec3 sphereLimit = origin + vec3(RADIUS, 0.0, 0.0);
+	vec4 projSphereLimit = project(vec4(sphereLimit, 1.0));
+	vec2 projSphereLimit2 = projSphereLimit.xy / projSphereLimit.w;
+	float projRadius = length(projSphereLimit2 - ndc);
+
+	// Loop to compute
+	float ssao = 0.0;
+	ANKI_UNROLL for(uint i = 0; i < SAMPLE_COUNT; ++i)
+	{
+		// Compute disk
+		vec3 randFactors = readRandom(uv, float(i));
+		vec2 dir = normalize(randFactors.xy * 2.0 - 1.0);
+		float radius = projRadius * (randFactors.z * 0.85 + 0.15);
+		vec2 finalDiskPoint = ndc + dir * radius;
+
+		// Compute factor
+		vec3 s = readPosition(NDC_TO_UV(finalDiskPoint));
+		vec3 u = s - origin;
+		ssao += max(dot(normal, u) + BIAS, EPSILON) / max(dot(u, u), EPSILON);
+	}
+
+	ssao *= (1.0 / float(SAMPLE_COUNT));
+	ssao = 1.0 - ssao * STRENGTH;
+
+	// Store the result
+#if USE_COMPUTE
+	imageStore(out_img, ivec2(gl_GlobalInvocationID.xy), vec4(ssao));
+#else
+	out_color = ssao;
+#endif
+}
+
+#endif

+ 1 - 0
src/anki/gr/RenderGraph.cpp

@@ -407,6 +407,7 @@ FramebufferPtr RenderGraph::getOrCreateFramebuffer(
 		else
 		else
 		{
 		{
 			fbInit.m_colorAttachmentCount = 1;
 			fbInit.m_colorAttachmentCount = 1;
+			fbInit.m_colorAttachments[0].m_loadOperation = fbDescr.m_colorAttachments[0].m_loadOperation;
 		}
 		}
 
 
 		// Set FB name
 		// Set FB name

+ 1 - 1
src/anki/gr/vulkan/CommandBufferImpl.cpp

@@ -205,7 +205,7 @@ void CommandBufferImpl::beginRenderPassInternal()
 		bi.renderPass = impl.getRenderPassHandle({}, VK_IMAGE_LAYOUT_MAX_ENUM);
 		bi.renderPass = impl.getRenderPassHandle({}, VK_IMAGE_LAYOUT_MAX_ENUM);
 
 
 		// Perform the transition
 		// Perform the transition
-		setImageBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+		setImageBarrier(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
 			0,
 			0,
 			VK_IMAGE_LAYOUT_UNDEFINED,
 			VK_IMAGE_LAYOUT_UNDEFINED,
 			VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
 			VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,

+ 3 - 3
src/anki/renderer/FinalComposite.cpp

@@ -47,15 +47,15 @@ Error FinalComposite::initInternal(const ConfigSet& config)
 		m_rtDescr.bake();
 		m_rtDescr.bake();
 
 
 		m_fbDescr.m_colorAttachmentCount = 1;
 		m_fbDescr.m_colorAttachmentCount = 1;
-		m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-		m_fbDescr.bake();
 	}
 	}
 	else
 	else
 	{
 	{
 		m_fbDescr.setDefaultFramebuffer();
 		m_fbDescr.setDefaultFramebuffer();
-		m_fbDescr.bake();
 	}
 	}
 
 
+	m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
+	m_fbDescr.bake();
+
 	ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_blueNoise));
 	ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_blueNoise));
 
 
 	// Progs
 	// Progs

+ 1 - 1
src/anki/renderer/Renderer.cpp

@@ -271,9 +271,9 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 	m_vol->populateRenderGraph(ctx);
 	m_vol->populateRenderGraph(ctx);
 	m_ssao->populateRenderGraph(ctx);
 	m_ssao->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
-	m_refl->populateRenderGraph(ctx);
 	m_lensFlare->populateRenderGraph(ctx);
 	m_lensFlare->populateRenderGraph(ctx);
 	m_forwardShading->populateRenderGraph(ctx);
 	m_forwardShading->populateRenderGraph(ctx);
+	m_refl->populateRenderGraph(ctx);
 	m_lightShading->populateRenderGraph(ctx);
 	m_lightShading->populateRenderGraph(ctx);
 	m_temporalAA->populateRenderGraph(ctx);
 	m_temporalAA->populateRenderGraph(ctx);
 	m_downscale->populateRenderGraph(ctx);
 	m_downscale->populateRenderGraph(ctx);

+ 78 - 21
src/anki/renderer/Ssao.cpp

@@ -24,18 +24,26 @@ Error Ssao::initMain(const ConfigSet& config)
 	ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_main.m_noiseTex));
 	ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_main.m_noiseTex));
 
 
 	// Shader
 	// Shader
-	ANKI_CHECK(getResourceManager().loadResource("programs/Ssao.ankiprog", m_main.m_prog));
+	if(m_useCompute)
+	{
+		ANKI_CHECK(getResourceManager().loadResource("programs/SsaoCompute.ankiprog", m_main.m_prog));
+	}
+	else
+	{
+		ANKI_CHECK(getResourceManager().loadResource("programs/Ssao.ankiprog", m_main.m_prog));
+	}
 
 
 	ShaderProgramResourceMutationInitList<1> mutators(m_main.m_prog);
 	ShaderProgramResourceMutationInitList<1> mutators(m_main.m_prog);
-	mutators.add("USE_NORMAL", 0u);
+	mutators.add("USE_NORMAL", (m_useNormal) ? 1u : 0u);
 
 
-	ShaderProgramResourceConstantValueInitList<6> consts(m_main.m_prog);
+	ShaderProgramResourceConstantValueInitList<7> consts(m_main.m_prog);
 	consts.add("NOISE_MAP_SIZE", U32(m_main.m_noiseTex->getWidth()))
 	consts.add("NOISE_MAP_SIZE", U32(m_main.m_noiseTex->getWidth()))
 		.add("FB_SIZE", UVec2(m_width, m_height))
 		.add("FB_SIZE", UVec2(m_width, m_height))
 		.add("RADIUS", 2.5f)
 		.add("RADIUS", 2.5f)
 		.add("BIAS", 0.0f)
 		.add("BIAS", 0.0f)
 		.add("STRENGTH", 2.5f)
 		.add("STRENGTH", 2.5f)
-		.add("SAMPLE_COUNT", 4u);
+		.add("SAMPLE_COUNT", 4u)
+		.add("WORKGROUP_SIZE", UVec2(m_workgroupSize[0], m_workgroupSize[1]));
 	const ShaderProgramResourceVariant* variant;
 	const ShaderProgramResourceVariant* variant;
 	m_main.m_prog->getOrCreateVariant(mutators.get(), consts.get(), variant);
 	m_main.m_prog->getOrCreateVariant(mutators.get(), consts.get(), variant);
 	m_main.m_grProg = variant->getProgram();
 	m_main.m_grProg = variant->getProgram();
@@ -87,12 +95,20 @@ Error Ssao::init(const ConfigSet& config)
 	ANKI_R_LOGI("Initializing SSAO. Size %ux%u", m_width, m_height);
 	ANKI_R_LOGI("Initializing SSAO. Size %ux%u", m_width, m_height);
 
 
 	// RT
 	// RT
-	m_rtDescr = m_r->create2DRenderTargetDescription(m_width,
+	m_rtDescrs[0] = m_r->create2DRenderTargetDescription(m_width,
+		m_height,
+		Ssao::RT_PIXEL_FORMAT,
+		TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE
+			| ((m_useCompute) ? TextureUsageBit::IMAGE_COMPUTE_WRITE : TextureUsageBit::NONE),
+		"SSAO_0");
+	m_rtDescrs[0].bake();
+
+	m_rtDescrs[1] = m_r->create2DRenderTargetDescription(m_width,
 		m_height,
 		m_height,
 		Ssao::RT_PIXEL_FORMAT,
 		Ssao::RT_PIXEL_FORMAT,
-		TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE | TextureUsageBit::CLEAR,
-		"SSAO");
-	m_rtDescr.bake();
+		TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+		"SSAO_1");
+	m_rtDescrs[1].bake();
 
 
 	// FB descr
 	// FB descr
 	m_fbDescr.m_colorAttachmentCount = 1;
 	m_fbDescr.m_colorAttachmentCount = 1;
@@ -123,7 +139,6 @@ void Ssao::runMain(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 {
 {
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
 
-	cmdb->setViewport(0, 0, m_width, m_height);
 	cmdb->bindShaderProgram(m_main.m_grProg);
 	cmdb->bindShaderProgram(m_main.m_grProg);
 
 
 	rgraphCtx.bindTextureAndSampler(
 	rgraphCtx.bindTextureAndSampler(
@@ -134,6 +149,11 @@ void Ssao::runMain(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 		m_r->getTrilinearRepeatSampler(),
 		m_r->getTrilinearRepeatSampler(),
 		TextureUsageBit::SAMPLED_FRAGMENT);
 		TextureUsageBit::SAMPLED_FRAGMENT);
 
 
+	if(m_useNormal)
+	{
+		rgraphCtx.bindColorTextureAndSampler(0, 2, m_r->getGBuffer().getColorRt(2), m_r->getLinearSampler());
+	}
+
 	struct Unis
 	struct Unis
 	{
 	{
 		Vec4 m_unprojectionParams;
 		Vec4 m_unprojectionParams;
@@ -147,7 +167,19 @@ void Ssao::runMain(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 	unis->m_projectionMat = Vec4(pmat(0, 0), pmat(1, 1), pmat(2, 2), pmat(2, 3));
 	unis->m_projectionMat = Vec4(pmat(0, 0), pmat(1, 1), pmat(2, 2), pmat(2, 3));
 	unis->m_viewRotMat = Mat3x4(ctx.m_renderQueue->m_viewMatrix.getRotationPart());
 	unis->m_viewRotMat = Mat3x4(ctx.m_renderQueue->m_viewMatrix.getRotationPart());
 
 
-	drawQuad(cmdb);
+	if(m_useCompute)
+	{
+		rgraphCtx.bindImage(0, 0, m_runCtx.m_rts[0], TextureSubresourceInfo());
+
+		const U sizeX = (m_width + m_workgroupSize[0] - 1) / m_workgroupSize[0];
+		const U sizeY = (m_height + m_workgroupSize[1] - 1) / m_workgroupSize[1];
+		cmdb->dispatchCompute(sizeX, sizeY, 1);
+	}
+	else
+	{
+		cmdb->setViewport(0, 0, m_width, m_height);
+		drawQuad(cmdb);
+	}
 }
 }
 
 
 void Ssao::runHBlur(RenderPassWorkContext& rgraphCtx)
 void Ssao::runHBlur(RenderPassWorkContext& rgraphCtx)
@@ -180,20 +212,45 @@ void Ssao::populateRenderGraph(RenderingContext& ctx)
 	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
 	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
 
 
 	// Create RTs
 	// Create RTs
-	m_runCtx.m_rts[0] = rgraph.newRenderTarget(m_rtDescr);
-	m_runCtx.m_rts[1] = rgraph.newRenderTarget(m_rtDescr);
+	m_runCtx.m_rts[0] = rgraph.newRenderTarget(m_rtDescrs[0]);
+	m_runCtx.m_rts[1] = rgraph.newRenderTarget(m_rtDescrs[1]);
 
 
 	// Create main render pass
 	// Create main render pass
 	{
 	{
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("SSAO main");
-
-		pass.setWork(runMainCallback, this, 0);
-		pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rts[0]}}, {});
-
-		pass.newConsumer({m_r->getGBuffer().getColorRt(2), TextureUsageBit::SAMPLED_FRAGMENT});
-		pass.newConsumer({m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_FRAGMENT, HIZ_QUARTER_DEPTH});
-		pass.newConsumer({m_runCtx.m_rts[0], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
-		pass.newProducer({m_runCtx.m_rts[0], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
+		if(m_useCompute)
+		{
+			ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("SSAO main");
+
+			if(m_useNormal)
+			{
+				pass.newConsumer({m_r->getGBuffer().getColorRt(2), TextureUsageBit::SAMPLED_COMPUTE});
+			}
+
+			pass.newConsumer(
+				{m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_COMPUTE, HIZ_QUARTER_DEPTH});
+			pass.newConsumer({m_runCtx.m_rts[0], TextureUsageBit::IMAGE_COMPUTE_WRITE});
+			pass.newProducer({m_runCtx.m_rts[0], TextureUsageBit::IMAGE_COMPUTE_WRITE});
+
+			pass.setWork(runMainCallback, this, 0);
+		}
+		else
+		{
+			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("SSAO main");
+
+			pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rts[0]}}, {});
+
+			if(m_useNormal)
+			{
+				pass.newConsumer({m_r->getGBuffer().getColorRt(2), TextureUsageBit::SAMPLED_FRAGMENT});
+			}
+
+			pass.newConsumer(
+				{m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_FRAGMENT, HIZ_QUARTER_DEPTH});
+			pass.newConsumer({m_runCtx.m_rts[0], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
+			pass.newProducer({m_runCtx.m_rts[0], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
+
+			pass.setWork(runMainCallback, this, 0);
+		}
 	}
 	}
 
 
 	// Create HBlur pass
 	// Create HBlur pass

+ 4 - 1
src/anki/renderer/Ssao.h

@@ -39,7 +39,10 @@ anki_internal:
 	}
 	}
 
 
 private:
 private:
+	static const Bool m_useNormal = false;
+	static const Bool m_useCompute = true;
 	U32 m_width, m_height;
 	U32 m_width, m_height;
+	Array<U32, 2> m_workgroupSize = {{16, 16}};
 
 
 	class
 	class
 	{
 	{
@@ -70,7 +73,7 @@ private:
 		const RenderingContext* m_ctx = nullptr;
 		const RenderingContext* m_ctx = nullptr;
 	} m_runCtx; ///< Runtime context.
 	} m_runCtx; ///< Runtime context.
 
 
-	RenderTargetDescription m_rtDescr;
+	Array<RenderTargetDescription, 2> m_rtDescrs;
 	FramebufferDescription m_fbDescr;
 	FramebufferDescription m_fbDescr;
 
 
 	ANKI_USE_RESULT Error initMain(const ConfigSet& set);
 	ANKI_USE_RESULT Error initMain(const ConfigSet& set);