2
0
Эх сурвалжийг харах

Add parallel compilation. Add the GBufferGeneric monster shader

Panagiotis Christopoulos Charitos 5 жил өмнө
parent
commit
9574b6e138

+ 112 - 0
shaders/GBufferCommon.glsl

@@ -0,0 +1,112 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// Defines the interfaces for the programs that write the GBuffer
+
+#pragma once
+
+#include <shaders/Pack.glsl>
+
+//
+// Vert input
+//
+#if defined(ANKI_VERTEX_SHADER)
+layout(location = POSITION_LOCATION) in Vec3 in_position;
+#	if ANKI_PASS == PASS_GB
+layout(location = TEXTURE_COORDINATE_LOCATION) in Vec2 in_uv;
+layout(location = NORMAL_LOCATION) in Vec3 in_normal;
+layout(location = TANGENT_LOCATION) in Vec4 in_tangent;
+#	endif
+
+#	if ANKI_BONES
+layout(location = BONE_WEIGHTS_LOCATION) in Vec4 in_boneWeights;
+layout(location = BONE_INDICES_LOCATION) in UVec4 in_boneIndices;
+#	endif
+#endif
+
+//
+// Vert out
+//
+#if defined(ANKI_VERTEX_SHADER)
+out gl_PerVertex
+{
+	Vec4 gl_Position;
+};
+
+#	if ANKI_PASS == PASS_GB
+layout(location = 0) out Vec2 out_uv;
+layout(location = 1) out Vec3 out_normal;
+layout(location = 2) out Vec3 out_tangent;
+layout(location = 3) out Vec3 out_bitangent;
+
+#		if REALLY_USING_PARALLAX
+layout(location = 4) out F32 out_distFromTheCamera;
+layout(location = 5) out Vec3 out_eyeTangentSpace;
+layout(location = 6) out Vec3 out_normalTangentSpace;
+#		endif
+
+#		if ANKI_VELOCITY
+layout(location = 7) out Vec2 out_velocity;
+#		endif
+#	endif // ANKI_PASS == PASS_GB
+#endif // defined(ANKI_VERTEX_SHADER)
+
+//
+// Frag input
+//
+#if defined(ANKI_FRAGMENT_SHADER) && ANKI_PASS == PASS_GB
+layout(location = 0) in Vec2 in_uv;
+layout(location = 1) in Vec3 in_normal;
+layout(location = 2) in Vec3 in_tangent;
+layout(location = 3) in Vec3 in_bitangent;
+
+#	if REALLY_USING_PARALLAX
+layout(location = 4) in F32 in_distFromTheCamera;
+layout(location = 5) in Vec3 in_eyeTangentSpace;
+layout(location = 6) in Vec3 in_normalTangentSpace;
+#	endif
+
+#	if ANKI_VELOCITY
+layout(location = 7) in Vec2 in_velocity;
+#	endif
+#endif
+
+//
+// Frag out
+//
+#if defined(ANKI_FRAGMENT_SHADER) && (ANKI_PASS == PASS_GB || ANKI_PASS == PASS_EZ)
+layout(location = 0) out Vec4 out_gbuffer0;
+layout(location = 1) out Vec4 out_gbuffer1;
+layout(location = 2) out Vec4 out_gbuffer2;
+layout(location = 3) out Vec2 out_gbuffer3;
+#endif
+
+//
+// Functions
+//
+
+// Write the data to RTs
+#if defined(ANKI_FRAGMENT_SHADER) && ANKI_PASS == PASS_GB
+void writeGBuffer(Vec3 diffColor,
+	Vec3 normal,
+	Vec3 specularColor,
+	F32 roughness,
+	F32 subsurface,
+	Vec3 emission,
+	F32 metallic,
+	Vec2 velocity)
+{
+	GbufferInfo g;
+	g.m_diffuse = diffColor;
+	g.m_normal = normal;
+	g.m_specular = specularColor;
+	g.m_roughness = roughness;
+	g.m_subsurface = subsurface;
+	g.m_emission = (emission.r + emission.g + emission.b) / 3.0;
+	g.m_metallic = metallic;
+	g.m_velocity = velocity;
+	writeGBuffer(g, out_gbuffer0, out_gbuffer1, out_gbuffer2, out_gbuffer3);
+}
+#endif

+ 399 - 0
shaders/GBufferGeneric.ankiprog

@@ -0,0 +1,399 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki mutator ANKI_INSTANCE_COUNT 1 2 4 8 16 32 64
+#pragma anki mutator ANKI_LOD 0 1 2
+#pragma anki mutator ANKI_VELOCITY 0 1
+#pragma anki mutator ANKI_PASS 0 1 2 3
+#pragma anki mutator ANKI_BONES 0 1
+#pragma anki mutator DIFFUSE_TEX 0 1
+#pragma anki mutator SPECULAR_TEX 0 1
+#pragma anki mutator ROUGHNESS_TEX 0 1
+#pragma anki mutator METAL_TEX 0 1
+#pragma anki mutator NORMAL_TEX 0 1
+#pragma anki mutator PARALLAX 0 1
+#pragma anki mutator EMISSIVE_TEX 0 1
+
+#pragma anki rewrite_mutation ANKI_PASS 1 DIFFUSE_TEX 1 to ANKI_PASS 1 DIFFUSE_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 DIFFUSE_TEX 1 to ANKI_PASS 2 DIFFUSE_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 DIFFUSE_TEX 1 to ANKI_PASS 2 DIFFUSE_TEX 0
+
+#pragma anki rewrite_mutation ANKI_PASS 1 SPECULAR_TEX 1 to ANKI_PASS 1 SPECULAR_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 SPECULAR_TEX 1 to ANKI_PASS 2 SPECULAR_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 SPECULAR_TEX 1 to ANKI_PASS 2 SPECULAR_TEX 0
+
+#pragma anki rewrite_mutation ANKI_PASS 1 NORMAL_TEX 1 to ANKI_PASS 1 NORMAL_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 NORMAL_TEX 1 to ANKI_PASS 2 NORMAL_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 NORMAL_TEX 1 to ANKI_PASS 2 NORMAL_TEX 0
+
+#pragma anki rewrite_mutation ANKI_PASS 1 ROUGHNESS_TEX 1 to ANKI_PASS 1 ROUGHNESS_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 ROUGHNESS_TEX 1 to ANKI_PASS 2 ROUGHNESS_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 ROUGHNESS_TEX 1 to ANKI_PASS 2 ROUGHNESS_TEX 0
+
+#pragma anki rewrite_mutation ANKI_PASS 1 METAL_TEX 1 to ANKI_PASS 1 METAL_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 METAL_TEX 1 to ANKI_PASS 2 METAL_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 METAL_TEX 1 to ANKI_PASS 2 METAL_TEX 0
+
+#pragma anki rewrite_mutation ANKI_PASS 1 EMISSIVE_TEX 1 to ANKI_PASS 1 EMISSIVE_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 EMISSIVE_TEX 1 to ANKI_PASS 2 EMISSIVE_TEX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 EMISSIVE_TEX 1 to ANKI_PASS 2 EMISSIVE_TEX 0
+
+#pragma anki rewrite_mutation ANKI_PASS 1 PARALLAX 1 to ANKI_PASS 1 PARALLAX 0
+#pragma anki rewrite_mutation ANKI_PASS 2 PARALLAX 1 to ANKI_PASS 2 PARALLAX 0
+#pragma anki rewrite_mutation ANKI_PASS 3 PARALLAX 1 to ANKI_PASS 2 PARALLAX 0
+
+#define REALLY_USING_PARALLAX (PARALLAX == 1 && ANKI_PASS == 0 && ANKI_LOD == 0)
+
+#include <shaders/GBufferCommon.glsl>
+
+layout(set = 0, binding = 1) uniform sampler u_ankiGlobalSampler;
+#if DIFFUSE_TEX == 1 && ANKI_PASS == PASS_GB
+layout(set = 0, binding = 2) uniform texture2D u_diffTex;
+#	define USING_DIFF_TEX 1
+#endif
+#if SPECULAR_TEX == 1 && ANKI_PASS == PASS_GB
+layout(set = 0, binding = 3) uniform texture2D u_specTex;
+#	define USING_SPECULAR_TEX 1
+#endif
+#if ROUGHNESS_TEX == 1 && ANKI_PASS == PASS_GB
+layout(set = 0, binding = 4) uniform texture2D u_roughnessTex;
+#	define USING_ROUGHNESS_TEX 1
+#endif
+#if NORMAL_TEX == 1 && ANKI_PASS == PASS_GB && ANKI_LOD < 2
+layout(set = 0, binding = 5) uniform texture2D u_normalTex;
+#	define USING_NORMAL_TEX 1
+#endif
+#if METAL_TEX == 1 && ANKI_PASS == PASS_GB
+layout(set = 0, binding = 6) uniform texture2D u_metallicTex;
+#	define USING_METALLIC_TEX 1
+#endif
+#if REALLY_USING_PARALLAX
+layout(set = 0, binding = 7) uniform texture2D u_heightTex;
+#endif
+#if EMISSIVE_TEX == 1 && ANKI_PASS == PASS_GB
+layout(set = 0, binding = 8) uniform texture2D u_emissiveTex;
+#	define USING_EMISSIVE_TEX 1
+#endif
+
+#if ANKI_PASS == PASS_GB
+struct PerDraw
+{
+#	if !defined(USING_DIFF_TEX)
+	Vec3 m_diffColor;
+#	endif
+#	if !defined(USING_ROUGHNESS_TEX)
+	F32 m_roughness;
+#	endif
+#	if !defined(USING_SPECULAR_TEX)
+	Vec3 m_specColor;
+#	endif
+#	if !defined(USING_METALLIC_TEX)
+	F32 m_metallic;
+#	endif
+#	if !defined(USING_EMISSIVE_TEX)
+	Vec3 m_emission;
+#	endif
+#	if REALLY_USING_PARALLAX
+	F32 m_heightMapScale;
+#	endif
+#	if ANKI_PASS == PASS_GB
+	F32 m_subsurface;
+#	endif
+};
+#endif
+
+struct PerInstance
+{
+	Mat4 m_ankiMvp;
+#if ANKI_PASS == PASS_GB
+	Mat3 m_ankiRotationMatrix;
+#endif
+#if REALLY_USING_PARALLAX
+	Mat4 m_ankiModelViewMatrix;
+#endif
+#if ANKI_PASS == PASS_GB && ANKI_VELOCITY == 1
+	Mat4 m_ankiPreviousMvp;
+#endif
+};
+
+layout(set = 0, binding = 0, row_major, std140) uniform b_ankiMaterial
+{
+#if ANKI_PASS == PASS_GB
+	PerDraw u_ankiPerDraw;
+#endif
+	PerInstance u_ankiPerInstance[ANKI_INSTANCE_COUNT];
+};
+
+#if ANKI_BONES
+layout(set = 0, binding = 9, row_major, std140) readonly buffer b_ankiBoneTransforms
+{
+	Mat4 u_ankiBoneTransforms[];
+};
+#endif
+
+#if ANKI_INSTANCE_COUNT == 1
+#	define INSTANCE_ID 0
+#else
+#	define INSTANCE_ID gl_InstanceID
+#endif
+
+#pragma anki start vert
+
+// Globals (always in local space)
+Vec3 g_position = in_position;
+#if ANKI_PASS == PASS_GB
+Vec2 g_uv = in_uv;
+Vec3 g_normal = in_normal;
+Vec4 g_tangent = in_tangent;
+#endif
+
+// Perform skinning
+#if ANKI_BONES
+void skinning()
+{
+	Vec3 position = Vec3(0.0);
+	Vec3 normal = Vec3(0.0);
+	Vec3 tangent = Vec3(0.0);
+	for(U32 i = 0; i < 4; ++i)
+	{
+		const U32 boneIdx = in_boneIndices[i];
+		if(boneIdx < 0xFFFF)
+		{
+			const F32 boneWeight = in_boneWeights[i];
+
+			position += (u_ankiBoneTransforms[boneIdx] * Vec4(g_position * boneWeight, 1.0)).xyz;
+#	if ANKI_PASS == PASS_GB
+			normal += (u_ankiBoneTransforms[boneIdx] * Vec4(g_normal * boneWeight, 0.0)).xyz;
+			tangent += (u_ankiBoneTransforms[boneIdx] * Vec4(g_tangent.xyz * boneWeight, 0.0)).xyz;
+#	endif
+		}
+	}
+
+	g_position = position;
+#	if ANKI_PASS == PASS_GB
+	g_tangent.xyz = tangent;
+	g_normal = normal;
+#	endif
+}
+#endif
+
+// Common store function
+#if ANKI_PASS == PASS_GB
+void positionUvNormalTangent()
+{
+	gl_Position = u_ankiPerInstance[INSTANCE_ID].m_ankiMvp * Vec4(g_position, 1.0);
+	out_normal = u_ankiPerInstance[INSTANCE_ID].m_ankiRotationMatrix * g_normal.xyz;
+	out_tangent = u_ankiPerInstance[INSTANCE_ID].m_ankiRotationMatrix * g_tangent.xyz;
+	out_bitangent = cross(out_normal, out_tangent) * g_tangent.w;
+	out_uv = g_uv;
+}
+#endif
+
+// Store stuff for parallax mapping
+#if REALLY_USING_PARALLAX
+void parallax()
+{
+	const Mat4 modelViewMat = u_ankiPerInstance[INSTANCE_ID].m_ankiModelViewMatrix;
+	const Vec3 n = in_normal;
+	const Vec3 t = in_tangent.xyz;
+	const Vec3 b = cross(n, t) * in_tangent.w;
+
+	const Mat3 normalMat = Mat3(modelViewMat);
+	const Mat3 invTbn = transpose(normalMat * Mat3(t, b, n));
+
+	const Vec3 viewPos = (modelViewMat * Vec4(g_position, 1.0)).xyz;
+	out_distFromTheCamera = viewPos.z;
+
+	out_eyeTangentSpace = invTbn * viewPos;
+	out_normalTangentSpace = invTbn * n;
+}
+#endif
+
+#if ANKI_VELOCITY && ANKI_PASS == PASS_GB
+void velocity()
+{
+	const Vec4 v4 = u_ankiPerInstance[INSTANCE_ID].m_ankiPreviousMvp * Vec4(g_position, 1.0);
+	const Vec2 prevNdc = v4.xy / v4.w;
+
+	const Vec2 crntNdc = gl_Position.xy / gl_Position.w;
+
+	// It's NDC_TO_UV(prevNdc) - NDC_TO_UV(crntNdc) or:
+	out_velocity = (prevNdc - crntNdc) * 0.5;
+}
+#endif
+
+void main()
+{
+#if ANKI_BONES
+	skinning();
+#endif
+
+#if ANKI_PASS == PASS_GB
+	positionUvNormalTangent();
+
+#	if REALLY_USING_PARALLAX
+	parallax();
+#	endif
+
+#	if ANKI_VELOCITY
+	velocity();
+#	endif
+#else
+	gl_Position = u_ankiPerInstance[INSTANCE_ID].m_ankiMvp * Vec4(g_position, 1.0);
+#endif
+}
+#pragma anki end
+
+#pragma anki start frag
+
+#if REALLY_USING_PARALLAX
+Vec2 computeTextureCoordParallax(texture2D heightMap, sampler sampl, Vec2 uv, F32 heightMapScale)
+{
+	const U32 MAX_SAMPLES = 25;
+	const U32 MIN_SAMPLES = 1;
+	const F32 MAX_EFFECTIVE_DISTANCE = 32.0;
+
+	// Get that because we are sampling inside a loop
+	const Vec2 dPdx = dFdx(uv);
+	const Vec2 dPdy = dFdy(uv);
+
+	const Vec3 eyeTangentSpace = in_eyeTangentSpace;
+	const Vec3 normTangentSpace = in_normalTangentSpace;
+
+	F32 parallaxLimit = -length(eyeTangentSpace.xy) / eyeTangentSpace.z;
+	parallaxLimit *= heightMapScale;
+
+	const Vec2 offsetDir = normalize(eyeTangentSpace.xy);
+	const Vec2 maxOffset = offsetDir * parallaxLimit;
+
+	const Vec3 E = normalize(eyeTangentSpace);
+
+	const F32 factor0 = -dot(E, normTangentSpace);
+	const F32 factor1 = in_distFromTheCamera / -MAX_EFFECTIVE_DISTANCE;
+	const F32 factor = saturate((1.0 - factor0) * (1.0 - factor1));
+	const F32 sampleCountf = mix(F32(MIN_SAMPLES), F32(MAX_SAMPLES), factor);
+
+	const F32 stepSize = 1.0 / sampleCountf;
+
+	F32 crntRayHeight = 1.0;
+	Vec2 crntOffset = Vec2(0.0);
+	Vec2 lastOffset = Vec2(0.0);
+
+	F32 lastSampledHeight = 1.0;
+	F32 crntSampledHeight = 1.0;
+
+	U32 crntSample = 0;
+
+	const U32 sampleCount = U32(sampleCountf);
+	ANKI_LOOP while(crntSample < sampleCount)
+	{
+		crntSampledHeight = textureGrad(heightMap, sampl, uv + crntOffset, dPdx, dPdy).r;
+
+		if(crntSampledHeight > crntRayHeight)
+		{
+			const F32 delta1 = crntSampledHeight - crntRayHeight;
+			const F32 delta2 = (crntRayHeight + stepSize) - lastSampledHeight;
+			const F32 ratio = delta1 / (delta1 + delta2);
+
+			crntOffset = mix(crntOffset, lastOffset, ratio);
+
+			crntSample = sampleCount + 1;
+		}
+		else
+		{
+			crntSample++;
+
+			crntRayHeight -= stepSize;
+
+			lastOffset = crntOffset;
+			crntOffset += stepSize * maxOffset;
+
+			lastSampledHeight = crntSampledHeight;
+		}
+	}
+
+	return uv + crntOffset;
+}
+#endif
+
+// Do normal mapping
+#if ANKI_PASS == PASS_GB
+Vec3 readNormalFromTexture(texture2D map, sampler sampl, highp Vec2 texCoords)
+{
+	// First read the texture
+	const Vec3 nAtTangentspace = normalize((texture(map, sampl, texCoords).rgb - 0.5) * 2.0);
+
+	const Vec3 n = normalize(in_normal);
+	const Vec3 t = normalize(in_tangent);
+	const Vec3 b = normalize(in_bitangent);
+
+	const Mat3 tbnMat = Mat3(t, b, n);
+
+	return tbnMat * nAtTangentspace;
+}
+#endif
+
+void main()
+{
+#if ANKI_PASS == PASS_GB
+#	if REALLY_USING_PARALLAX
+	const Vec2 uv =
+		computeTextureCoordParallax(u_heightTex, u_ankiGlobalSampler, in_uv, u_ankiPerDraw.m_heightMapScale);
+#	else
+	const Vec2 uv = in_uv;
+#	endif
+
+#	if defined(USING_DIFF_TEX)
+	const Vec3 diffColor = texture(u_diffTex, u_ankiGlobalSampler, uv).rgb;
+#	else
+	const Vec3 diffColor = u_ankiPerDraw.m_diffColor;
+#	endif
+
+#	if defined(USING_SPECULAR_TEX)
+	const Vec3 specColor = texture(u_specTex, u_ankiGlobalSampler, uv).rgb;
+#	else
+	const Vec3 specColor = u_ankiPerDraw.m_specColor;
+#	endif
+
+#	if defined(USING_ROUGHNESS_TEX)
+	const F32 roughness = texture(u_roughnessTex, u_ankiGlobalSampler, uv).g;
+#	else
+	const F32 roughness = u_ankiPerDraw.m_roughness;
+#	endif
+
+#	if defined(USING_METALLIC_TEX)
+	const F32 metallic = texture(u_metallicTex, u_ankiGlobalSampler, uv).b;
+#	else
+	const F32 metallic = u_ankiPerDraw.m_metallic;
+#	endif
+
+#	if defined(USING_NORMAL_TEX)
+	const Vec3 normal = readNormalFromTexture(u_normalTex, u_ankiGlobalSampler, uv);
+#	else
+	const Vec3 normal = normalize(in_normal);
+#	endif
+
+#	if defined(USING_EMISSIVE_TEX)
+	const Vec3 emission = texture(u_emissiveTex, u_ankiGlobalSampler, uv).rgb;
+#	else
+	const Vec3 emission = u_ankiPerDraw.m_emission;
+#	endif
+
+#	if ANKI_VELOCITY
+	const Vec2 velocity = in_velocity;
+#	else
+	const Vec2 velocity = Vec2(-1.0);
+#	endif
+
+	writeGBuffer(diffColor, normal, specColor, roughness, u_ankiPerDraw.m_subsurface, emission, metallic, velocity);
+#elif ANKI_PASS == PASS_EZ
+	out_gbuffer0 = Vec4(0.0);
+	out_gbuffer1 = Vec4(0.0);
+	out_gbuffer2 = Vec4(0.0);
+	out_gbuffer3 = Vec2(0.0);
+#endif
+}
+
+#pragma anki end

+ 68 - 0
shaders/Ui.ankiprog

@@ -0,0 +1,68 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki mutator TEXTURE_TYPE 0 1 // 0: no tex, 1: rgba tex
+
+#pragma anki start vert
+#include <shaders/Common.glsl>
+
+layout(location = 0) in Vec2 in_pos;
+layout(location = 1) in Vec4 in_col;
+#if TEXTURE_TYPE > 0
+layout(location = 2) in Vec2 in_uv;
+#endif
+
+#if TEXTURE_TYPE > 0
+layout(location = 0) out Vec2 out_uv;
+#endif
+layout(location = 1) out Vec4 out_col;
+
+out gl_PerVertex
+{
+	Vec4 gl_Position;
+};
+
+layout(push_constant) uniform u_
+{
+	Vec4 u_transform; // x: x scale, y: y scale, z: x transl, w: y transl
+};
+
+void main()
+{
+#if TEXTURE_TYPE > 0
+	out_uv = in_uv;
+#endif
+	out_col = in_col;
+
+	const Vec2 pos = u_transform.xy * in_pos + u_transform.zw;
+	gl_Position = Vec4(pos, 0.0, 1.0);
+}
+#pragma anki end
+
+#pragma anki start frag
+#include <shaders/Common.glsl>
+
+#if TEXTURE_TYPE > 0
+layout(location = 0) in Vec2 in_uv;
+#endif
+layout(location = 1) in Vec4 in_col;
+
+layout(location = 0) out Vec4 out_col;
+
+#if TEXTURE_TYPE > 0
+layout(set = 0, binding = 0) uniform sampler u_trilinearRepeatSampler;
+layout(set = 0, binding = 1) uniform texture2D u_tex;
+#endif
+
+void main()
+{
+#if TEXTURE_TYPE == 0
+	out_col = in_col;
+#elif TEXTURE_TYPE == 1
+	out_col = in_col * texture(u_tex, u_trilinearRepeatSampler, in_uv);
+#endif
+}
+
+#pragma anki end

+ 60 - 4
src/anki/core/App.cpp

@@ -714,6 +714,12 @@ Error App::compileAllShaders()
 	ANKI_CORE_LOGI("Compiling shaders");
 	U32 shadersCompileCount = 0;
 
+	// Compute hash for both
+	const GpuDeviceCapabilities caps = m_gr->getDeviceCapabilities();
+	const BindlessLimits limits = m_gr->getBindlessLimits();
+	U64 gpuHash = computeHash(&caps, sizeof(caps));
+	gpuHash = appendHash(&limits, sizeof(limits), gpuHash);
+
 	ANKI_CHECK(m_resourceFs->iterateAllFilenames([&](CString fname) -> Error {
 		// Check file extension
 		StringAuto extension(m_heapAlloc);
@@ -723,6 +729,8 @@ Error App::compileAllShaders()
 			return Error::NONE;
 		}
 
+		ANKI_CORE_LOGI("\t%s", fname.cstr());
+
 		// Get some filenames
 		StringAuto baseFname(m_heapAlloc);
 		getFilepathFilename(fname, baseFname);
@@ -754,32 +762,80 @@ Error App::compileAllShaders()
 		} fsystem;
 		fsystem.m_fsystem = m_resourceFs;
 
+		// Skip interface
 		class Skip : public ShaderProgramPostParseInterface
 		{
 		public:
 			U64 m_metafileHash;
 			U64 m_newHash;
+			U64 m_gpuHash;
 
 			Bool skipCompilation(U64 hash)
 			{
 				ANKI_ASSERT(hash != 0);
-				m_newHash = hash;
-				// TODO Need to consider getDeviceCapabilities and getBindlessLimits
+				const Array<U64, 2> hashes = {{hash, m_gpuHash}};
+				const U64 finalHash = computeHash(hashes.getBegin(), hashes.getSizeInBytes());
+
+				m_newHash = finalHash;
 				return hash == m_metafileHash;
 			};
 		} skip;
 		skip.m_metafileHash = metafileHash;
 		skip.m_newHash = 0;
+		skip.m_gpuHash = gpuHash;
+
+		// Threading interface
+		class TaskManager : public ShaderProgramAsyncTaskInterface
+		{
+		public:
+			ThreadHive* m_hive = nullptr;
+			HeapAllocator<U8> m_alloc;
+
+			void enqueueTask(void (*callback)(void* userData), void* userData)
+			{
+				struct Ctx
+				{
+					void (*m_callback)(void* userData);
+					void* m_userData;
+					HeapAllocator<U8> m_alloc;
+				};
+				Ctx* ctx = m_alloc.newInstance<Ctx>();
+				ctx->m_callback = callback;
+				ctx->m_userData = userData;
+				ctx->m_alloc = m_alloc;
+
+				m_hive->submitTask(
+					[](void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore) {
+						Ctx* ctx = static_cast<Ctx*>(userData);
+						ctx->m_callback(ctx->m_userData);
+						auto alloc = ctx->m_alloc;
+						alloc.deleteInstance(ctx);
+					},
+					ctx);
+			}
+
+			Error joinTasks()
+			{
+				m_hive->waitAllTasks();
+				return Error::NONE;
+			}
+		} taskManager;
+		taskManager.m_hive = m_threadHive;
+		taskManager.m_alloc = m_heapAlloc;
 
 		// Compile
 		ShaderProgramBinaryWrapper binary(m_heapAlloc);
-		ANKI_CHECK(compileShaderProgram(
-			fname, fsystem, &skip, m_heapAlloc, m_gr->getDeviceCapabilities(), m_gr->getBindlessLimits(), binary));
+		ANKI_CHECK(compileShaderProgram(fname, fsystem, &skip, &taskManager, m_heapAlloc, caps, limits, binary));
 
 		const Bool cachedBinIsUpToDate = metafileHash == skip.m_newHash;
 		if(!cachedBinIsUpToDate)
 		{
 			++shadersCompileCount;
+			ANKI_CORE_LOGI("\t\tCompiled");
+		}
+		else
+		{
+			ANKI_CORE_LOGI("\t\tSkipped");
 		}
 
 		// Update the meta file

+ 9 - 0
src/anki/shader_compiler/Common.h

@@ -37,6 +37,15 @@ class ShaderProgramPostParseInterface
 public:
 	virtual Bool skipCompilation(U64 programHash) = 0;
 };
+
+/// An interface for asynchronous shader compilation.
+class ShaderProgramAsyncTaskInterface
+{
+public:
+	virtual void enqueueTask(void (*callback)(void* userData), void* userData) = 0;
+
+	virtual ANKI_USE_RESULT Error joinTasks() = 0;
+};
 /// @}
 
 } // end namespace anki

+ 171 - 230
src/anki/shader_compiler/ShaderProgramCompiler.cpp

@@ -9,7 +9,6 @@
 #include <anki/shader_compiler/ShaderProgramReflection.h>
 #include <anki/util/Serializer.h>
 #include <anki/util/HashMap.h>
-#include <SPIRV-Cross/spirv_glsl.hpp>
 
 namespace anki
 {
@@ -167,68 +166,146 @@ static Bool spinDials(DynamicArrayAuto<U32>& dials, ConstWeakArray<ShaderProgram
 	return done;
 }
 
-static Error compileVariant(ConstWeakArray<MutatorValue> mutation,
+static Error compileSpirv(ConstWeakArray<MutatorValue> mutation,
 	const ShaderProgramParser& parser,
-	ShaderProgramBinaryVariant& variant,
-	DynamicArrayAuto<ShaderProgramBinaryCodeBlock>& codeBlocks,
-	DynamicArrayAuto<U64>& codeBlockHashes,
 	GenericMemoryPoolAllocator<U8>& tmpAlloc,
-	GenericMemoryPoolAllocator<U8>& binaryAlloc)
+	Array<DynamicArrayAuto<U8>, U32(ShaderType::COUNT)>& spirv)
 {
-	variant = {};
-
 	// Generate the source and the rest for the variant
 	ShaderProgramParserVariant parserVariant;
 	ANKI_CHECK(parser.generateVariant(mutation, parserVariant));
 
 	// Compile stages
-	Array<ConstWeakArray<U8>, U32(ShaderType::COUNT)> spirvBinaries;
-	for(ShaderType shaderType = ShaderType::FIRST; shaderType < ShaderType::COUNT; ++shaderType)
+	for(ShaderType shaderType : EnumIterable<ShaderType>())
 	{
 		if(!(shaderTypeToBit(shaderType) & parser.getShaderTypes()))
 		{
-			variant.m_codeBlockIndices[shaderType] = MAX_U32;
 			continue;
 		}
 
 		// Compile
-		DynamicArrayAuto<U8> spirv(tmpAlloc);
-		ANKI_CHECK(compilerGlslToSpirv(parserVariant.getSource(shaderType), shaderType, tmpAlloc, spirv));
-		ANKI_ASSERT(spirv.getSize() > 0);
-
-		// Check if the spirv is already generated
-		const U64 newHash = computeHash(&spirv[0], spirv.getSize());
-		Bool found = false;
-		for(U32 i = 0; i < codeBlockHashes.getSize(); ++i)
+		ANKI_CHECK(compilerGlslToSpirv(parserVariant.getSource(shaderType), shaderType, tmpAlloc, spirv[shaderType]));
+		ANKI_ASSERT(spirv[shaderType].getSize() > 0);
+	}
+
+	return Error::NONE;
+}
+
+static void compileVariantAsync(ConstWeakArray<MutatorValue> mutation,
+	const ShaderProgramParser& parser,
+	ShaderProgramBinaryVariant& variant,
+	DynamicArrayAuto<ShaderProgramBinaryCodeBlock>& codeBlocks,
+	DynamicArrayAuto<U64>& codeBlockHashes,
+	GenericMemoryPoolAllocator<U8>& tmpAlloc,
+	GenericMemoryPoolAllocator<U8>& binaryAlloc,
+	ShaderProgramAsyncTaskInterface& taskManager,
+	Mutex& mtx,
+	Atomic<I32>& error)
+{
+	variant = {};
+
+	class Ctx
+	{
+	public:
+		GenericMemoryPoolAllocator<U8> m_tmpAlloc;
+		GenericMemoryPoolAllocator<U8> m_binaryAlloc;
+		DynamicArrayAuto<MutatorValue> m_mutation{m_tmpAlloc};
+		const ShaderProgramParser* m_parser;
+		ShaderProgramBinaryVariant* m_variant;
+		DynamicArrayAuto<ShaderProgramBinaryCodeBlock>* m_codeBlocks;
+		DynamicArrayAuto<U64>* m_codeBlockHashes;
+		Mutex* m_mtx;
+		Atomic<I32>* m_err;
+
+		Ctx(GenericMemoryPoolAllocator<U8> tmpAlloc)
+			: m_tmpAlloc(tmpAlloc)
 		{
-			if(codeBlockHashes[i] == newHash)
-			{
-				// Found it
-				variant.m_codeBlockIndices[shaderType] = i;
-				found = true;
-				break;
-			}
 		}
+	};
+
+	Ctx* ctx = tmpAlloc.newInstance<Ctx>(tmpAlloc);
+	ctx->m_binaryAlloc = binaryAlloc;
+	ctx->m_mutation.create(mutation.getSize());
+	memcpy(ctx->m_mutation.getBegin(), mutation.getBegin(), mutation.getSizeInBytes());
+	ctx->m_parser = &parser;
+	ctx->m_variant = &variant;
+	ctx->m_codeBlocks = &codeBlocks;
+	ctx->m_codeBlockHashes = &codeBlockHashes;
+	ctx->m_mtx = &mtx;
+	ctx->m_err = &error;
+
+	auto callback = [](void* userData) {
+		Ctx& ctx = *static_cast<Ctx*>(userData);
+		GenericMemoryPoolAllocator<U8>& tmpAlloc = ctx.m_tmpAlloc;
+
+		if(ctx.m_err->load() != 0)
+		{
+			// Cleanup and return
+			tmpAlloc.deleteInstance(&ctx);
+			return;
+		}
+
+		// All good, compile the variant
+		Array<DynamicArrayAuto<U8>, U32(ShaderType::COUNT)> spirvs = {
+			{{tmpAlloc}, {tmpAlloc}, {tmpAlloc}, {tmpAlloc}, {tmpAlloc}, {tmpAlloc}}};
+		const Error err = compileSpirv(ctx.m_mutation, *ctx.m_parser, tmpAlloc, spirvs);
 
-		// Create it if not found
-		if(!found)
+		if(!err)
 		{
-			U8* code = binaryAlloc.allocate(spirv.getSizeInBytes());
-			memcpy(code, &spirv[0], spirv.getSizeInBytes());
+			// No error, check if the spirvs are common with some other variant and store it
+
+			LockGuard<Mutex> lock(*ctx.m_mtx);
+
+			for(ShaderType shaderType : EnumIterable<ShaderType>())
+			{
+				DynamicArrayAuto<U8>& spirv = spirvs[shaderType];
+
+				if(spirv.isEmpty())
+				{
+					ctx.m_variant->m_codeBlockIndices[shaderType] = MAX_U32;
+					continue;
+				}
+
+				// Check if the spirv is already generated
+				const U64 newHash = computeHash(&spirv[0], spirv.getSize());
+				Bool found = false;
+				for(U32 i = 0; i < ctx.m_codeBlockHashes->getSize(); ++i)
+				{
+					if((*ctx.m_codeBlockHashes)[i] == newHash)
+					{
+						// Found it
+						ctx.m_variant->m_codeBlockIndices[shaderType] = i;
+						found = true;
+						break;
+					}
+				}
 
-			ShaderProgramBinaryCodeBlock block;
-			block.m_binary.setArray(code, U32(spirv.getSizeInBytes()));
-			codeBlocks.emplaceBack(block);
+				// Create it if not found
+				if(!found)
+				{
+					U8* code = ctx.m_binaryAlloc.allocate(spirv.getSizeInBytes());
+					memcpy(code, &spirv[0], spirv.getSizeInBytes());
 
-			codeBlockHashes.emplaceBack(newHash);
+					ShaderProgramBinaryCodeBlock block;
+					block.m_binary.setArray(code, U32(spirv.getSizeInBytes()));
 
-			variant.m_codeBlockIndices[shaderType] = codeBlocks.getSize() - 1;
+					ctx.m_codeBlocks->emplaceBack(block);
+					ctx.m_codeBlockHashes->emplaceBack(newHash);
+
+					ctx.m_variant->m_codeBlockIndices[shaderType] = ctx.m_codeBlocks->getSize() - 1;
+				}
+			}
+		}
+		else
+		{
+			ctx.m_err->store(err._getCode());
 		}
 
-		spirvBinaries[shaderType] = codeBlocks[variant.m_codeBlockIndices[shaderType]].m_binary;
-	}
+		// Cleanup
+		tmpAlloc.deleteInstance(&ctx);
+	};
 
-	return Error::NONE;
+	taskManager.enqueueTask(callback, ctx);
 }
 
 class Refl final : public ShaderReflectionVisitorInterface
@@ -692,6 +769,7 @@ static Error doReflection(
 Error compileShaderProgramInternal(CString fname,
 	ShaderProgramFilesystemInterface& fsystem,
 	ShaderProgramPostParseInterface* postParseCallback,
+	ShaderProgramAsyncTaskInterface* taskManager_,
 	GenericMemoryPoolAllocator<U8> tempAllocator,
 	const GpuDeviceCapabilities& gpuCapabilities,
 	const BindlessLimits& bindlessLimits,
@@ -743,6 +821,24 @@ Error compileShaderProgramInternal(CString fname,
 	}
 
 	// Create all variants
+	Mutex mtx;
+	Atomic<I32> errorAtomic(0);
+	class SyncronousShaderProgramAsyncTaskInterface : public ShaderProgramAsyncTaskInterface
+	{
+	public:
+		void enqueueTask(void (*callback)(void* userData), void* userData) final
+		{
+			callback(userData);
+		}
+
+		Error joinTasks() final
+		{
+			// Nothing
+			return Error::NONE;
+		}
+	} syncTaskManager;
+	ShaderProgramAsyncTaskInterface& taskManager = (taskManager_) ? *taskManager_ : syncTaskManager;
+
 	if(parser.getMutators().getSize() > 0)
 	{
 		// Initialize
@@ -755,6 +851,10 @@ Error compileShaderProgramInternal(CString fname,
 		DynamicArrayAuto<U64> codeBlockHashes(tempAllocator);
 		HashMapAuto<U64, U32> mutationHashToIdx(tempAllocator);
 
+		// Grow the storage of the variants array. Can't have it resize, threads will work on stale data
+		variants.resizeStorage(mutationCount);
+		const ShaderProgramBinaryVariant* baseVariant = nullptr;
+
 		mutationCount = 0;
 
 		// Spin for all possible combinations of mutators and
@@ -788,14 +888,18 @@ Error compileShaderProgramInternal(CString fname,
 				// New and unique mutation and thus variant, add it
 
 				ShaderProgramBinaryVariant& variant = *variants.emplaceBack();
+				baseVariant = (baseVariant == nullptr) ? variants.getBegin() : baseVariant;
 
-				ANKI_CHECK(compileVariant(originalMutationValues,
+				compileVariantAsync(originalMutationValues,
 					parser,
 					variant,
 					codeBlocks,
 					codeBlockHashes,
 					tempAllocator,
-					binaryAllocator));
+					binaryAllocator,
+					taskManager,
+					mtx,
+					errorAtomic);
 
 				mutation.m_variantIndex = variants.getSize() - 1;
 
@@ -815,14 +919,18 @@ Error compileShaderProgramInternal(CString fname,
 					// Rewrite variant not found, create it
 
 					variant = variants.emplaceBack();
+					baseVariant = (baseVariant == nullptr) ? variants.getBegin() : baseVariant;
 
-					ANKI_CHECK(compileVariant(rewrittenMutationValues,
+					compileVariantAsync(originalMutationValues,
 						parser,
 						*variant,
 						codeBlocks,
 						codeBlockHashes,
 						tempAllocator,
-						binaryAllocator));
+						binaryAllocator,
+						taskManager,
+						mtx,
+						errorAtomic);
 
 					ShaderProgramBinaryMutation& otherMutation = mutations[mutationCount++];
 					otherMutation.m_values.setArray(
@@ -846,6 +954,11 @@ Error compileShaderProgramInternal(CString fname,
 		} while(!spinDials(dials, parser.getMutators()));
 
 		ANKI_ASSERT(mutationCount == mutations.getSize());
+		ANKI_ASSERT(baseVariant == variants.getBegin() && "Can't have the variants array grow");
+
+		// Done, wait the threads
+		ANKI_CHECK(taskManager.joinTasks());
+		ANKI_CHECK(Error(errorAtomic.getNonAtomically()));
 
 		// Store temp containers to binary
 		U32 size, storage;
@@ -869,8 +982,20 @@ Error compileShaderProgramInternal(CString fname,
 
 		binary.m_variants.setArray(binaryAllocator.newInstance<ShaderProgramBinaryVariant>(), 1);
 
-		ANKI_CHECK(compileVariant(
-			mutation, parser, binary.m_variants[0], codeBlocks, codeBlockHashes, tempAllocator, binaryAllocator));
+		compileVariantAsync(mutation,
+			parser,
+			binary.m_variants[0],
+			codeBlocks,
+			codeBlockHashes,
+			tempAllocator,
+			binaryAllocator,
+			taskManager,
+			mtx,
+			errorAtomic);
+
+		ANKI_CHECK(taskManager.joinTasks());
+		ANKI_CHECK(Error(errorAtomic.getNonAtomically()));
+
 		ANKI_ASSERT(codeBlocks.getSize() == U32(__builtin_popcount(U32(parser.getShaderTypes()))));
 
 		ShaderProgramBinaryCodeBlock* firstCodeBlock;
@@ -900,13 +1025,14 @@ Error compileShaderProgramInternal(CString fname,
 Error compileShaderProgram(CString fname,
 	ShaderProgramFilesystemInterface& fsystem,
 	ShaderProgramPostParseInterface* postParseCallback,
+	ShaderProgramAsyncTaskInterface* taskManager,
 	GenericMemoryPoolAllocator<U8> tempAllocator,
 	const GpuDeviceCapabilities& gpuCapabilities,
 	const BindlessLimits& bindlessLimits,
 	ShaderProgramBinaryWrapper& binaryW)
 {
 	const Error err = compileShaderProgramInternal(
-		fname, fsystem, postParseCallback, tempAllocator, gpuCapabilities, bindlessLimits, binaryW);
+		fname, fsystem, postParseCallback, taskManager, tempAllocator, gpuCapabilities, bindlessLimits, binaryW);
 	if(err)
 	{
 		ANKI_SHADER_COMPILER_LOGE("Failed to compile: %s", fname.cstr());
@@ -915,189 +1041,4 @@ Error compileShaderProgram(CString fname,
 	return err;
 }
 
-#define ANKI_TAB "    "
-
-static void disassembleBlock(
-	const ShaderProgramBinaryBlockInstance& instance, const ShaderProgramBinaryBlock& block, StringListAuto& lines)
-{
-	lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB "%-32s set %4u binding %4u size %4u\n",
-		block.m_name.getBegin(),
-		block.m_set,
-		block.m_binding,
-		instance.m_size);
-
-	for(U32 i = 0; i < instance.m_variables.getSize(); ++i)
-	{
-		const ShaderProgramBinaryVariableInstance& varInstance = instance.m_variables[i];
-		const ShaderProgramBinaryVariable& var = block.m_variables[varInstance.m_index];
-
-		lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB ANKI_TAB "%-48s type %8s blockInfo %d,%d,%d,%d\n",
-			var.m_name.getBegin(),
-			shaderVariableDataTypeToString(var.m_type).cstr(),
-			varInstance.m_blockInfo.m_offset,
-			varInstance.m_blockInfo.m_arraySize,
-			varInstance.m_blockInfo.m_arrayStride,
-			varInstance.m_blockInfo.m_matrixStride);
-	}
-}
-
-void dumpShaderProgramBinary(const ShaderProgramBinary& binary, StringAuto& humanReadable)
-{
-	GenericMemoryPoolAllocator<U8> alloc = humanReadable.getAllocator();
-	StringListAuto lines(alloc);
-
-	lines.pushBack("**MUTATORS**\n");
-	if(binary.m_mutators.getSize() > 0)
-	{
-		for(const ShaderProgramBinaryMutator& mutator : binary.m_mutators)
-		{
-			lines.pushBackSprintf(ANKI_TAB "%-32s ", &mutator.m_name[0]);
-			for(U32 i = 0; i < mutator.m_values.getSize(); ++i)
-			{
-				lines.pushBackSprintf((i < mutator.m_values.getSize() - 1) ? "%d," : "%d", mutator.m_values[i]);
-			}
-			lines.pushBack("\n");
-		}
-	}
-	else
-	{
-		lines.pushBack(ANKI_TAB "N/A\n");
-	}
-
-	lines.pushBack("\n**BINARIES**\n");
-	U32 count = 0;
-	for(const ShaderProgramBinaryCodeBlock& code : binary.m_codeBlocks)
-	{
-		spirv_cross::CompilerGLSL::Options options;
-		options.vulkan_semantics = true;
-
-		const unsigned int* spvb = reinterpret_cast<const unsigned int*>(code.m_binary.getBegin());
-		ANKI_ASSERT((code.m_binary.getSize() % (sizeof(unsigned int))) == 0);
-		std::vector<unsigned int> spv(spvb, spvb + code.m_binary.getSize() / sizeof(unsigned int));
-		spirv_cross::CompilerGLSL compiler(spv);
-		compiler.set_common_options(options);
-
-		std::string glsl = compiler.compile();
-		StringListAuto sourceLines(alloc);
-		sourceLines.splitString(glsl.c_str(), '\n');
-		StringAuto newGlsl(alloc);
-		sourceLines.join("\n" ANKI_TAB ANKI_TAB, newGlsl);
-
-		lines.pushBackSprintf(ANKI_TAB "#%u \n" ANKI_TAB ANKI_TAB "%s\n", count++, newGlsl.cstr());
-	}
-
-	lines.pushBack("\n**SHADER VARIANTS**\n");
-	count = 0;
-	for(const ShaderProgramBinaryVariant& variant : binary.m_variants)
-	{
-		lines.pushBackSprintf(ANKI_TAB "#%u\n", count++);
-
-		// Uniform blocks
-		if(variant.m_uniformBlocks.getSize() > 0)
-		{
-			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Uniform blocks\n");
-			for(const ShaderProgramBinaryBlockInstance& instance : variant.m_uniformBlocks)
-			{
-				disassembleBlock(instance, binary.m_uniformBlocks[instance.m_index], lines);
-			}
-		}
-
-		// Storage blocks
-		if(variant.m_storageBlocks.getSize() > 0)
-		{
-			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Storage blocks\n");
-			for(const ShaderProgramBinaryBlockInstance& instance : variant.m_storageBlocks)
-			{
-				disassembleBlock(instance, binary.m_storageBlocks[instance.m_index], lines);
-			}
-		}
-
-		// Opaque
-		if(variant.m_opaques.getSize() > 0)
-		{
-			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Opaque\n");
-			for(const ShaderProgramBinaryOpaqueInstance& instance : variant.m_opaques)
-			{
-				const ShaderProgramBinaryOpaque& o = binary.m_opaques[instance.m_index];
-				lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB "%-32s set %4u binding %4u type %12s arraySize %4u\n",
-					o.m_name.getBegin(),
-					o.m_set,
-					o.m_binding,
-					shaderVariableDataTypeToString(o.m_type).cstr(),
-					instance.m_arraySize);
-			}
-		}
-
-		// Push constants
-		if(variant.m_pushConstantBlock)
-		{
-			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Push constants\n");
-			disassembleBlock(*variant.m_pushConstantBlock, *binary.m_pushConstantBlock, lines);
-		}
-
-		// Constants
-		if(variant.m_constants.getSize() > 0)
-		{
-			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Specialization constants\n");
-			for(const ShaderProgramBinaryConstantInstance& instance : variant.m_constants)
-			{
-				const ShaderProgramBinaryConstant& c = binary.m_constants[instance.m_index];
-				lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB "%-32s type %8s id %4u\n",
-					c.m_name.getBegin(),
-					shaderVariableDataTypeToString(c.m_type).cstr(),
-					c.m_constantId);
-			}
-		}
-
-		// Binary indices
-		lines.pushBack(ANKI_TAB ANKI_TAB "Binaries ");
-		for(ShaderType shaderType : EnumIterable<ShaderType>())
-		{
-			if(variant.m_codeBlockIndices[shaderType] < MAX_U32)
-			{
-				lines.pushBackSprintf("%u", variant.m_codeBlockIndices[shaderType]);
-			}
-			else
-			{
-				lines.pushBack("-");
-			}
-
-			if(shaderType != ShaderType::LAST)
-			{
-				lines.pushBack(",");
-			}
-		}
-		lines.pushBack("\n");
-	}
-
-	// Mutations
-	lines.pushBack("\n**MUTATIONS**\n");
-	count = 0;
-	for(const ShaderProgramBinaryMutation& mutation : binary.m_mutations)
-	{
-		lines.pushBackSprintf(ANKI_TAB "#%-4u variantIndex %4u values (", count++, mutation.m_variantIndex);
-		if(mutation.m_values.getSize() > 0)
-		{
-			for(U32 i = 0; i < mutation.m_values.getSize(); ++i)
-			{
-				lines.pushBackSprintf((i < mutation.m_values.getSize() - 1) ? "%s %4d, " : "%s %4d",
-					binary.m_mutators[i].m_name.getBegin(),
-					I32(mutation.m_values[i]));
-			}
-
-			lines.pushBack(")");
-		}
-		else
-		{
-			lines.pushBack("N/A)");
-		}
-
-		lines.pushBack("\n");
-	}
-
-	lines.join("", humanReadable);
-}
-
-#undef ANKI_TAB
-
 } // end namespace anki

+ 3 - 4
src/anki/shader_compiler/ShaderProgramCompiler.h

@@ -5,7 +5,7 @@
 
 #pragma once
 
-#include <anki/shader_compiler/ShaderProgramBinary.h>
+#include <anki/shader_compiler/ShaderProgramDump.h>
 #include <anki/util/String.h>
 #include <anki/gr/Common.h>
 
@@ -22,6 +22,7 @@ class ShaderProgramBinaryWrapper : public NonCopyable
 	friend Error compileShaderProgramInternal(CString fname,
 		ShaderProgramFilesystemInterface& fsystem,
 		ShaderProgramPostParseInterface* postParseCallback,
+		ShaderProgramAsyncTaskInterface* taskManager,
 		GenericMemoryPoolAllocator<U8> tempAllocator,
 		const GpuDeviceCapabilities& gpuCapabilities,
 		const BindlessLimits& bindlessLimits,
@@ -60,13 +61,11 @@ private:
 ANKI_USE_RESULT Error compileShaderProgram(CString fname,
 	ShaderProgramFilesystemInterface& fsystem,
 	ShaderProgramPostParseInterface* postParseCallback,
+	ShaderProgramAsyncTaskInterface* taskManager,
 	GenericMemoryPoolAllocator<U8> tempAllocator,
 	const GpuDeviceCapabilities& gpuCapabilities,
 	const BindlessLimits& bindlessLimits,
 	ShaderProgramBinaryWrapper& binary);
-
-/// Create a human readable representation of the shader binary.
-void dumpShaderProgramBinary(const ShaderProgramBinary& binary, StringAuto& humanReadable);
 /// @}
 
 } // end namespace anki

+ 199 - 0
src/anki/shader_compiler/ShaderProgramDump.cpp

@@ -0,0 +1,199 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/shader_compiler/ShaderProgramDump.h>
+#include <anki/util/Serializer.h>
+#include <anki/util/StringList.h>
+#include <SPIRV-Cross/spirv_glsl.hpp>
+
+namespace anki
+{
+
+#define ANKI_TAB "    "
+
+static void disassembleBlock(
+	const ShaderProgramBinaryBlockInstance& instance, const ShaderProgramBinaryBlock& block, StringListAuto& lines)
+{
+	lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB "%-32s set %4u binding %4u size %4u\n",
+		block.m_name.getBegin(),
+		block.m_set,
+		block.m_binding,
+		instance.m_size);
+
+	for(U32 i = 0; i < instance.m_variables.getSize(); ++i)
+	{
+		const ShaderProgramBinaryVariableInstance& varInstance = instance.m_variables[i];
+		const ShaderProgramBinaryVariable& var = block.m_variables[varInstance.m_index];
+
+		lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB ANKI_TAB "%-48s type %8s blockInfo %d,%d,%d,%d\n",
+			var.m_name.getBegin(),
+			shaderVariableDataTypeToString(var.m_type).cstr(),
+			varInstance.m_blockInfo.m_offset,
+			varInstance.m_blockInfo.m_arraySize,
+			varInstance.m_blockInfo.m_arrayStride,
+			varInstance.m_blockInfo.m_matrixStride);
+	}
+}
+
+void dumpShaderProgramBinary(const ShaderProgramBinary& binary, StringAuto& humanReadable)
+{
+	GenericMemoryPoolAllocator<U8> alloc = humanReadable.getAllocator();
+	StringListAuto lines(alloc);
+
+	lines.pushBack("**MUTATORS**\n");
+	if(binary.m_mutators.getSize() > 0)
+	{
+		for(const ShaderProgramBinaryMutator& mutator : binary.m_mutators)
+		{
+			lines.pushBackSprintf(ANKI_TAB "%-32s ", &mutator.m_name[0]);
+			for(U32 i = 0; i < mutator.m_values.getSize(); ++i)
+			{
+				lines.pushBackSprintf((i < mutator.m_values.getSize() - 1) ? "%d," : "%d", mutator.m_values[i]);
+			}
+			lines.pushBack("\n");
+		}
+	}
+	else
+	{
+		lines.pushBack(ANKI_TAB "N/A\n");
+	}
+
+	lines.pushBack("\n**BINARIES**\n");
+	U32 count = 0;
+	for(const ShaderProgramBinaryCodeBlock& code : binary.m_codeBlocks)
+	{
+		spirv_cross::CompilerGLSL::Options options;
+		options.vulkan_semantics = true;
+
+		const unsigned int* spvb = reinterpret_cast<const unsigned int*>(code.m_binary.getBegin());
+		ANKI_ASSERT((code.m_binary.getSize() % (sizeof(unsigned int))) == 0);
+		std::vector<unsigned int> spv(spvb, spvb + code.m_binary.getSize() / sizeof(unsigned int));
+		spirv_cross::CompilerGLSL compiler(spv);
+		compiler.set_common_options(options);
+
+		std::string glsl = compiler.compile();
+		StringListAuto sourceLines(alloc);
+		sourceLines.splitString(glsl.c_str(), '\n');
+		StringAuto newGlsl(alloc);
+		sourceLines.join("\n" ANKI_TAB ANKI_TAB, newGlsl);
+
+		lines.pushBackSprintf(ANKI_TAB "#%u \n" ANKI_TAB ANKI_TAB "%s\n", count++, newGlsl.cstr());
+	}
+
+	lines.pushBack("\n**SHADER VARIANTS**\n");
+	count = 0;
+	for(const ShaderProgramBinaryVariant& variant : binary.m_variants)
+	{
+		lines.pushBackSprintf(ANKI_TAB "#%u\n", count++);
+
+		// Uniform blocks
+		if(variant.m_uniformBlocks.getSize() > 0)
+		{
+			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Uniform blocks\n");
+			for(const ShaderProgramBinaryBlockInstance& instance : variant.m_uniformBlocks)
+			{
+				disassembleBlock(instance, binary.m_uniformBlocks[instance.m_index], lines);
+			}
+		}
+
+		// Storage blocks
+		if(variant.m_storageBlocks.getSize() > 0)
+		{
+			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Storage blocks\n");
+			for(const ShaderProgramBinaryBlockInstance& instance : variant.m_storageBlocks)
+			{
+				disassembleBlock(instance, binary.m_storageBlocks[instance.m_index], lines);
+			}
+		}
+
+		// Opaque
+		if(variant.m_opaques.getSize() > 0)
+		{
+			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Opaque\n");
+			for(const ShaderProgramBinaryOpaqueInstance& instance : variant.m_opaques)
+			{
+				const ShaderProgramBinaryOpaque& o = binary.m_opaques[instance.m_index];
+				lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB "%-32s set %4u binding %4u type %12s arraySize %4u\n",
+					o.m_name.getBegin(),
+					o.m_set,
+					o.m_binding,
+					shaderVariableDataTypeToString(o.m_type).cstr(),
+					instance.m_arraySize);
+			}
+		}
+
+		// Push constants
+		if(variant.m_pushConstantBlock)
+		{
+			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Push constants\n");
+			disassembleBlock(*variant.m_pushConstantBlock, *binary.m_pushConstantBlock, lines);
+		}
+
+		// Constants
+		if(variant.m_constants.getSize() > 0)
+		{
+			lines.pushBackSprintf(ANKI_TAB ANKI_TAB "Specialization constants\n");
+			for(const ShaderProgramBinaryConstantInstance& instance : variant.m_constants)
+			{
+				const ShaderProgramBinaryConstant& c = binary.m_constants[instance.m_index];
+				lines.pushBackSprintf(ANKI_TAB ANKI_TAB ANKI_TAB "%-32s type %8s id %4u\n",
+					c.m_name.getBegin(),
+					shaderVariableDataTypeToString(c.m_type).cstr(),
+					c.m_constantId);
+			}
+		}
+
+		// Binary indices
+		lines.pushBack(ANKI_TAB ANKI_TAB "Binaries ");
+		for(ShaderType shaderType : EnumIterable<ShaderType>())
+		{
+			if(variant.m_codeBlockIndices[shaderType] < MAX_U32)
+			{
+				lines.pushBackSprintf("%u", variant.m_codeBlockIndices[shaderType]);
+			}
+			else
+			{
+				lines.pushBack("-");
+			}
+
+			if(shaderType != ShaderType::LAST)
+			{
+				lines.pushBack(",");
+			}
+		}
+		lines.pushBack("\n");
+	}
+
+	// Mutations
+	lines.pushBack("\n**MUTATIONS**\n");
+	count = 0;
+	for(const ShaderProgramBinaryMutation& mutation : binary.m_mutations)
+	{
+		lines.pushBackSprintf(ANKI_TAB "#%-4u variantIndex %5u values (", count++, mutation.m_variantIndex);
+		if(mutation.m_values.getSize() > 0)
+		{
+			for(U32 i = 0; i < mutation.m_values.getSize(); ++i)
+			{
+				lines.pushBackSprintf((i < mutation.m_values.getSize() - 1) ? "%s %4d, " : "%s %4d",
+					binary.m_mutators[i].m_name.getBegin(),
+					I32(mutation.m_values[i]));
+			}
+
+			lines.pushBack(")");
+		}
+		else
+		{
+			lines.pushBack("N/A)");
+		}
+
+		lines.pushBack("\n");
+	}
+
+	lines.join("", humanReadable);
+}
+
+#undef ANKI_TAB
+
+} // end namespace anki

+ 19 - 0
src/anki/shader_compiler/ShaderProgramDump.h

@@ -0,0 +1,19 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/shader_compiler/ShaderProgramBinary.h>
+#include <anki/util/String.h>
+
+namespace anki
+{
+
+/// @addtogroup shader_compiler
+/// @{
+
+/// Create a human readable representation of the shader binary.
+void dumpShaderProgramBinary(const ShaderProgramBinary& binary, StringAuto& humanReadable);
+/// @}
+
+} // end namespace anki

+ 28 - 28
src/anki/shader_compiler/ShaderProgramParser.cpp

@@ -69,55 +69,55 @@ static const char* SHADER_HEADER = R"(#version 450 core
 #define _ANKI_CONCATENATE(a, b) a##b
 #define ANKI_CONCATENATE(a, b) _ANKI_CONCATENATE(a, b)
 
-#define ANKI_SPECIALIZATION_CONSTANT_X(type, n, id, defltVal) \
+#define _ANKI_SCONST_X(type, n, id, defltVal) \
 	layout(constant_id = id) const type n = defltVal; \
 	const U32 ANKI_CONCATENATE(n, _CONST_ID) = id
 
-#define ANKI_SPECIALIZATION_CONSTANT_X2(type, componentType, n, id, defltVal) \
+#define _ANKI_SCONST_X2(type, componentType, n, id, defltVal, constWorkaround) \
 	layout(constant_id = id + 0) const componentType ANKI_CONCATENATE(_anki_const_0_2_, n) = defltVal[0]; \
 	layout(constant_id = id + 1) const componentType ANKI_CONCATENATE(_anki_const_1_2_, n) = defltVal[1]; \
-	const componentType ANKI_CONCATENATE(n, _X) = ANKI_CONCATENATE(_anki_const_0_2_, n) + componentType(0); \
-	const componentType ANKI_CONCATENATE(n, _Y) = ANKI_CONCATENATE(_anki_const_1_2_, n) + componentType(0); \
-	const type n = type(ANKI_CONCATENATE(n, _X), ANKI_CONCATENATE(n, _Y)); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _X) = ANKI_CONCATENATE(_anki_const_0_2_, n) + componentType(0); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _Y) = ANKI_CONCATENATE(_anki_const_1_2_, n) + componentType(0); \
+	constWorkaround type n = type(ANKI_CONCATENATE(n, _X), ANKI_CONCATENATE(n, _Y)); \
 	const UVec2 ANKI_CONCATENATE(n, _CONST_ID) = UVec2(id, id + 1)
 
-#define ANKI_SPECIALIZATION_CONSTANT_X3(type, componentType, n, id, defltVal) \
+#define _ANKI_SCONST_X3(type, componentType, n, id, defltVal, constWorkaround) \
 	layout(constant_id = id + 0) const componentType ANKI_CONCATENATE(_anki_const_0_3_, n) = defltVal[0]; \
 	layout(constant_id = id + 1) const componentType ANKI_CONCATENATE(_anki_const_1_3_, n) = defltVal[1]; \
 	layout(constant_id = id + 2) const componentType ANKI_CONCATENATE(_anki_const_2_3_, n) = defltVal[2]; \
-	const componentType ANKI_CONCATENATE(n, _X) = ANKI_CONCATENATE(_anki_const_0_3_, n) + componentType(0); \
-	const componentType ANKI_CONCATENATE(n, _Y) = ANKI_CONCATENATE(_anki_const_1_3_, n) + componentType(0); \
-	const componentType ANKI_CONCATENATE(n, _Z) = ANKI_CONCATENATE(_anki_const_2_3_, n) + componentType(0); \
-	const type n = type(ANKI_CONCATENATE(n, _X), ANKI_CONCATENATE(n, _Y), ANKI_CONCATENATE(n, _Z)); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _X) = ANKI_CONCATENATE(_anki_const_0_3_, n) + componentType(0); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _Y) = ANKI_CONCATENATE(_anki_const_1_3_, n) + componentType(0); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _Z) = ANKI_CONCATENATE(_anki_const_2_3_, n) + componentType(0); \
+	constWorkaround type n = type(ANKI_CONCATENATE(n, _X), ANKI_CONCATENATE(n, _Y), ANKI_CONCATENATE(n, _Z)); \
 	const UVec3 ANKI_CONCATENATE(n, _CONST_ID) = UVec3(id, id + 1, id + 2)
 
-#define ANKI_SPECIALIZATION_CONSTANT_X4(type, componentType, n, id, defltVal) \
+#define _ANKI_SCONST_X4(type, componentType, n, id, defltVal, constWorkaround) \
 	layout(constant_id = id + 0) const componentType ANKI_CONCATENATE(_anki_const_0_4_, n) = defltVal[0]; \
 	layout(constant_id = id + 1) const componentType ANKI_CONCATENATE(_anki_const_1_4_, n) = defltVal[1]; \
 	layout(constant_id = id + 2) const componentType ANKI_CONCATENATE(_anki_const_2_4_, n) = defltVal[2]; \
 	layout(constant_id = id + 3) const componentType ANKI_CONCATENATE(_anki_const_3_4_, n) = defltVal[3]; \
-	const componentType ANKI_CONCATENATE(n, _X) = ANKI_CONCATENATE(_anki_const_0_4_, n) + componentType(0); \
-	const componentType ANKI_CONCATENATE(n, _Y) = ANKI_CONCATENATE(_anki_const_1_4_, n) + componentType(0); \
-	const componentType ANKI_CONCATENATE(n, _Z) = ANKI_CONCATENATE(_anki_const_2_4_, n) + componentType(0); \
-	const componentType ANKI_CONCATENATE(n, _W) = ANKI_CONCATENATE(_anki_const_3_4_, n) + componentType(0); \
-	const type n = type(ANKI_CONCATENATE(n, _X), ANKI_CONCATENATE(n, _Y), ANKI_CONCATENATE(n, _Z), \
+	constWorkaround componentType ANKI_CONCATENATE(n, _X) = ANKI_CONCATENATE(_anki_const_0_4_, n) + componentType(0); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _Y) = ANKI_CONCATENATE(_anki_const_1_4_, n) + componentType(0); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _Z) = ANKI_CONCATENATE(_anki_const_2_4_, n) + componentType(0); \
+	constWorkaround componentType ANKI_CONCATENATE(n, _W) = ANKI_CONCATENATE(_anki_const_3_4_, n) + componentType(0); \
+	constWorkaround type n = type(ANKI_CONCATENATE(n, _X), ANKI_CONCATENATE(n, _Y), ANKI_CONCATENATE(n, _Z), \
 		ANKI_CONCATENATE(n, _W)); \
 	const UVec4 ANKI_CONCATENATE(n, _CONST_ID) = UVec4(id, id + 1, id + 2, id + 3)
 
-#define ANKI_SPECIALIZATION_CONSTANT_I32(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X(I32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_IVEC2(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X2(IVec2, I32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_IVEC3(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X3(IVec3, I32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_IVEC4(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X4(IVec4, I32, n, id, defltVal)
+#define ANKI_SPECIALIZATION_CONSTANT_I32(n, id, defltVal) _ANKI_SCONST_X(I32, n, id, defltVal)
+#define ANKI_SPECIALIZATION_CONSTANT_IVEC2(n, id, defltVal) _ANKI_SCONST_X2(IVec2, I32, n, id, defltVal, const)
+#define ANKI_SPECIALIZATION_CONSTANT_IVEC3(n, id, defltVal) _ANKI_SCONST_X3(IVec3, I32, n, id, defltVal, const)
+#define ANKI_SPECIALIZATION_CONSTANT_IVEC4(n, id, defltVal) _ANKI_SCONST_X4(IVec4, I32, n, id, defltVal, const)
 
-#define ANKI_SPECIALIZATION_CONSTANT_U32(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X(U32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_UVEC2(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X2(UVec2, U32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_UVEC3(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X3(UVec3, U32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_UVEC4(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X4(UVec4, U32, n, id, defltVal)
+#define ANKI_SPECIALIZATION_CONSTANT_U32(n, id, defltVal) _ANKI_SCONST_X(U32, n, id, defltVal)
+#define ANKI_SPECIALIZATION_CONSTANT_UVEC2(n, id, defltVal) _ANKI_SCONST_X2(UVec2, U32, n, id, defltVal, const)
+#define ANKI_SPECIALIZATION_CONSTANT_UVEC3(n, id, defltVal) _ANKI_SCONST_X3(UVec3, U32, n, id, defltVal, const)
+#define ANKI_SPECIALIZATION_CONSTANT_UVEC4(n, id, defltVal) _ANKI_SCONST_X4(UVec4, U32, n, id, defltVal, const)
 
-#define ANKI_SPECIALIZATION_CONSTANT_F32(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X(F32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_VEC2(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X2(Vec2, F32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_VEC3(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X3(Vec3, F32, n, id, defltVal)
-#define ANKI_SPECIALIZATION_CONSTANT_VEC4(n, id, defltVal) ANKI_SPECIALIZATION_CONSTANT_X4(Vec4, F32, n, id, defltVal)
+#define ANKI_SPECIALIZATION_CONSTANT_F32(n, id, defltVal) _ANKI_SCONST_X(F32, n, id, defltVal)
+#define ANKI_SPECIALIZATION_CONSTANT_VEC2(n, id, defltVal) _ANKI_SCONST_X2(Vec2, F32, n, id, defltVal,)
+#define ANKI_SPECIALIZATION_CONSTANT_VEC3(n, id, defltVal) _ANKI_SCONST_X3(Vec3, F32, n, id, defltVal,)
+#define ANKI_SPECIALIZATION_CONSTANT_VEC4(n, id, defltVal) _ANKI_SCONST_X4(Vec4, F32, n, id, defltVal,)
 )";
 
 ShaderProgramParser::ShaderProgramParser(CString fname,

+ 1 - 1
src/anki/shader_compiler/ShaderProgramReflection.cpp

@@ -353,7 +353,7 @@ Error SpirvReflector::blockReflection(
 		const Bool err1 = nameSame && (!bindingSame || !sizeSame);
 		if(err0 || err1)
 		{
-			ANKI_SHADER_COMPILER_LOGE("Linking error");
+			ANKI_SHADER_COMPILER_LOGE("Linking error. Blocks %s and %s", other.m_name.cstr(), newBlock.m_name.cstr());
 			return Error::USER_DATA;
 		}
 

+ 10 - 5
src/anki/util/DynamicArray.h

@@ -281,15 +281,14 @@ public:
 		m_capacity = 0;
 	}
 
+	/// Resizes the storage but DOESN'T CONSTRUCT ANY ELEMENTS. It only moves or destroys.
+	template<typename TAllocator>
+	void resizeStorage(TAllocator alloc, Size newSize);
+
 protected:
 	Value* m_data;
 	Size m_size;
 	Size m_capacity = 0;
-
-private:
-	/// Resizes the storage but DOESN'T CONSTRUCT ANY ELEMENTS. It only moves or destroys.
-	template<typename TAllocator>
-	void resizeStorage(TAllocator& alloc, Size newSize);
 };
 
 /// Dynamic array with automatic destruction. It's the same as DynamicArray but it holds the allocator in order to
@@ -441,6 +440,12 @@ public:
 		// Don't touch the m_alloc
 	}
 
+	/// @copydoc DynamicArray::resizeStorage
+	void resizeStorage(Size newSize)
+	{
+		Base::resizeStorage(m_alloc, newSize);
+	}
+
 	/// Get the allocator.
 	const GenericMemoryPoolAllocator<T>& getAllocator() const
 	{

+ 1 - 1
src/anki/util/DynamicArray.inl.h

@@ -23,7 +23,7 @@ DynamicArray<T, TSize>& DynamicArray<T, TSize>::operator=(DynamicArrayAuto<T, TS
 
 template<typename T, typename TSize>
 template<typename TAllocator>
-void DynamicArray<T, TSize>::resizeStorage(TAllocator& alloc, Size newSize)
+void DynamicArray<T, TSize>::resizeStorage(TAllocator alloc, Size newSize)
 {
 	if(newSize > m_capacity)
 	{

+ 89 - 4
tests/shader_compiler/ShaderProgramCompiler.cpp

@@ -5,6 +5,7 @@
 
 #include <tests/framework/Framework.h>
 #include <anki/shader_compiler/ShaderProgramCompiler.h>
+#include <anki/util/ThreadHive.h>
 
 ANKI_TEST(ShaderCompiler, ShaderProgramCompilerSimple)
 {
@@ -93,11 +94,53 @@ void main()
 	} fsystem;
 
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	const U32 threadCount = 8;
+	ThreadHive hive(threadCount, alloc);
+
+	class TaskManager : public ShaderProgramAsyncTaskInterface
+	{
+	public:
+		ThreadHive* m_hive = nullptr;
+		HeapAllocator<U8> m_alloc;
+
+		void enqueueTask(void (*callback)(void* userData), void* userData)
+		{
+			struct Ctx
+			{
+				void (*m_callback)(void* userData);
+				void* m_userData;
+				HeapAllocator<U8> m_alloc;
+			};
+			Ctx* ctx = m_alloc.newInstance<Ctx>();
+			ctx->m_callback = callback;
+			ctx->m_userData = userData;
+			ctx->m_alloc = m_alloc;
+
+			m_hive->submitTask(
+				[](void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore) {
+					Ctx* ctx = static_cast<Ctx*>(userData);
+					ctx->m_callback(ctx->m_userData);
+					auto alloc = ctx->m_alloc;
+					alloc.deleteInstance(ctx);
+				},
+				ctx);
+		}
+
+		Error joinTasks()
+		{
+			m_hive->waitAllTasks();
+			return Error::NONE;
+		}
+	} taskManager;
+	taskManager.m_hive = &hive;
+	taskManager.m_alloc = alloc;
+
 	ShaderProgramBinaryWrapper binary(alloc);
 	BindlessLimits bindlessLimits;
 	GpuDeviceCapabilities gpuCapabilities;
-	ANKI_TEST_EXPECT_NO_ERR(
-		compileShaderProgram("test.glslp", fsystem, nullptr, alloc, gpuCapabilities, bindlessLimits, binary));
+	ANKI_TEST_EXPECT_NO_ERR(compileShaderProgram(
+		"test.glslp", fsystem, nullptr, &taskManager, alloc, gpuCapabilities, bindlessLimits, binary));
 
 #if 1
 	StringAuto dis(alloc);
@@ -248,11 +291,53 @@ void main()
 	} fsystem;
 
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	const U32 threadCount = 24;
+	ThreadHive hive(threadCount, alloc);
+
+	class TaskManager : public ShaderProgramAsyncTaskInterface
+	{
+	public:
+		ThreadHive* m_hive = nullptr;
+		HeapAllocator<U8> m_alloc;
+
+		void enqueueTask(void (*callback)(void* userData), void* userData)
+		{
+			struct Ctx
+			{
+				void (*m_callback)(void* userData);
+				void* m_userData;
+				HeapAllocator<U8> m_alloc;
+			};
+			Ctx* ctx = m_alloc.newInstance<Ctx>();
+			ctx->m_callback = callback;
+			ctx->m_userData = userData;
+			ctx->m_alloc = m_alloc;
+
+			m_hive->submitTask(
+				[](void* userData, U32 threadId, ThreadHive& hive, ThreadHiveSemaphore* signalSemaphore) {
+					Ctx* ctx = static_cast<Ctx*>(userData);
+					ctx->m_callback(ctx->m_userData);
+					auto alloc = ctx->m_alloc;
+					alloc.deleteInstance(ctx);
+				},
+				ctx);
+		}
+
+		Error joinTasks()
+		{
+			m_hive->waitAllTasks();
+			return Error::NONE;
+		}
+	} taskManager;
+	taskManager.m_hive = &hive;
+	taskManager.m_alloc = alloc;
+
 	ShaderProgramBinaryWrapper binary(alloc);
 	BindlessLimits bindlessLimits;
 	GpuDeviceCapabilities gpuCapabilities;
-	ANKI_TEST_EXPECT_NO_ERR(
-		compileShaderProgram("test.glslp", fsystem, nullptr, alloc, gpuCapabilities, bindlessLimits, binary));
+	ANKI_TEST_EXPECT_NO_ERR(compileShaderProgram(
+		"test.glslp", fsystem, nullptr, &taskManager, alloc, gpuCapabilities, bindlessLimits, binary));
 
 #if 1
 	StringAuto dis(alloc);