Browse Source

Optimize VGPRs on light shading and other shaders

Panagiotis Christopoulos Charitos 7 years ago
parent
commit
09ae8f9235

+ 21 - 19
programs/LightShading.ankiprog

@@ -82,11 +82,6 @@ void main()
 	vec4 worldPos4 = u_invViewProjMat * vec4(ndc, depth, 1.0);
 	vec4 worldPos4 = u_invViewProjMat * vec4(ndc, depth, 1.0);
 	vec3 worldPos = worldPos4.xyz / worldPos4.w;
 	vec3 worldPos = worldPos4.xyz / worldPos4.w;
 
 
-	// Decode GBuffer
-	GbufferInfo gbuffer;
-	readGBuffer(u_msRt0, u_msRt1, u_msRt2, in_uv, 0.0, gbuffer);
-	gbuffer.subsurface = max(gbuffer.subsurface, SUBSURFACE_MIN);
-
 	// Get first light index
 	// Get first light index
 	uint idxOffset;
 	uint idxOffset;
 	{
 	{
@@ -97,17 +92,26 @@ void main()
 		idxOffset = u_clusters[clusterIdx];
 		idxOffset = u_clusters[clusterIdx];
 	}
 	}
 
 
-	// Skip decals
-	uint count = u_lightIndices[idxOffset++];
-	idxOffset += count;
+	// Decode GBuffer
+	GbufferInfo gbuffer;
+	readGBuffer(u_msRt0, u_msRt1, u_msRt2, in_uv, 0.0, gbuffer);
+	gbuffer.subsurface = max(gbuffer.subsurface, SUBSURFACE_MIN);
 
 
 	// Ambient and emissive color
 	// Ambient and emissive color
-	vec3 outC = gbuffer.diffuse * gbuffer.emission;
+	out_color = gbuffer.diffuse * gbuffer.emission;
+
+	// Indirect
+	out_color += textureLod(u_indirectRt, in_uv, 0.0).rgb;
+
+	// Skip decals
+	uint count = u_lightIndices[idxOffset];
+	idxOffset += count + 1u;
 
 
 	// Point lights
 	// Point lights
 	vec3 viewDir = normalize(u_cameraPos - worldPos);
 	vec3 viewDir = normalize(u_cameraPos - worldPos);
 	count = u_lightIndices[idxOffset++];
 	count = u_lightIndices[idxOffset++];
-	while(count-- != 0)
+	uint idxOffsetEnd = idxOffset + count;
+	ANKI_LOOP while(idxOffset < idxOffsetEnd)
 	{
 	{
 		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
 		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
 
 
@@ -116,19 +120,20 @@ void main()
 		if(light.diffuseColorTileSize.w >= 0.0)
 		if(light.diffuseColorTileSize.w >= 0.0)
 		{
 		{
 			float shadow = computeShadowFactorOmni(frag2Light, 
 			float shadow = computeShadowFactorOmni(frag2Light, 
-				light.radiusPad3.x, 
-				light.atlasTilesPad2.xy, 
+				light.radiusPad1.x, 
+				light.atlasTiles, 
 				light.diffuseColorTileSize.w,
 				light.diffuseColorTileSize.w,
 				u_shadowTex);
 				u_shadowTex);
 			lambert *= shadow;
 			lambert *= shadow;
 		}
 		}
 
 
-		outC += (diffC + specC) * light.diffuseColorTileSize.rgb * (att * max(gbuffer.subsurface, lambert));
+		out_color += (diffC + specC) * light.diffuseColorTileSize.rgb * (att * max(gbuffer.subsurface, lambert));
 	}
 	}
 
 
 	// Spot lights
 	// Spot lights
 	count = u_lightIndices[idxOffset++];
 	count = u_lightIndices[idxOffset++];
-	while(count-- != 0)
+	idxOffsetEnd = idxOffset + count;
+	ANKI_LOOP while(idxOffset < idxOffsetEnd)
 	{
 	{
 		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
 		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
 
 
@@ -145,13 +150,10 @@ void main()
 			lambert *= shadow;
 			lambert *= shadow;
 		}
 		}
 
 
-		outC += (diffC + specC) * light.diffuseColorShadowmapId.rgb * (att * spot * max(gbuffer.subsurface, lambert));
+		out_color += 
+			(diffC + specC) * light.diffuseColorShadowmapId.rgb * (att * spot * max(gbuffer.subsurface, lambert));
 	}
 	}
 
 
-	// Indirect
-	outC += textureLod(u_indirectRt, in_uv, 0.0).rgb;
-
-	out_color = outC;
 #if 0
 #if 0
 	count = scount;
 	count = scount;
 	if(count == 0)
 	if(count == 0)

+ 2 - 2
programs/VolumetricFog.ankiprog

@@ -76,8 +76,8 @@ vec3 computeLightColor(vec3 fragPos, uint plightCount, uint plightIdx, uint slig
 		if(light.diffuseColorTileSize.w >= 0.0)
 		if(light.diffuseColorTileSize.w >= 0.0)
 		{
 		{
 			factor *= computeShadowFactorOmni(frag2Light, 
 			factor *= computeShadowFactorOmni(frag2Light, 
-				light.radiusPad3.x, 
-				light.atlasTilesPad2.xy,
+				light.radiusPad1.x, 
+				light.atlasTiles,
 				light.diffuseColorTileSize.w,
 				light.diffuseColorTileSize.w,
 				u_shadowTex);
 				u_shadowTex);
 		}
 		}

+ 10 - 10
shaders/ClusterLightCommon.glsl

@@ -32,10 +32,10 @@ struct PointLight
 {
 {
 	vec4 posRadius; // xyz: Light pos in world space. w: The 1/(radius^2)
 	vec4 posRadius; // xyz: Light pos in world space. w: The 1/(radius^2)
 	vec4 diffuseColorTileSize; // xyz: diff color, w: tile size in the shadow atlas
 	vec4 diffuseColorTileSize; // xyz: diff color, w: tile size in the shadow atlas
-	vec4 radiusPad3; // x: radius
-	uvec4 atlasTilesPad2; // x: encodes 6 uints with atlas tile indices in the x dir. y: same for y dir.
+	vec2 radiusPad1; // x: radius
+	uvec2 atlasTiles; // x: encodes 6 uints with atlas tile indices in the x dir. y: same for y dir.
 };
 };
-const uint POINT_LIGHT_SIZEOF = (4 * 4) * 4;
+const uint SIZEOF_POINT_LIGHT = 3 * SIZEOF_VEC4;
 
 
 // Spot light
 // Spot light
 struct SpotLight
 struct SpotLight
@@ -46,7 +46,7 @@ struct SpotLight
 	vec4 outerCosInnerCos;
 	vec4 outerCosInnerCos;
 	mat4 texProjectionMat;
 	mat4 texProjectionMat;
 };
 };
-const uint SPOT_LIGHT_SIZEOF = (4 * 4 + 16) * 4;
+const uint SIZEOF_SPOT_LIGHT = 4 * SIZEOF_VEC4 + SIZEOF_MAT4;
 
 
 // Representation of a reflection probe
 // Representation of a reflection probe
 struct ReflectionProbe
 struct ReflectionProbe
@@ -57,7 +57,7 @@ struct ReflectionProbe
 	// Slice in u_reflectionsTex vector.
 	// Slice in u_reflectionsTex vector.
 	vec4 cubemapIndexPad3;
 	vec4 cubemapIndexPad3;
 };
 };
-const uint REFLECTION_PROBE_SIZEOF = (2 * 4) * 4;
+const uint SIZEOF_REFLECTION_PROBE = 2 * SIZEOF_VEC4;
 
 
 // Decal
 // Decal
 struct Decal
 struct Decal
@@ -67,7 +67,7 @@ struct Decal
 	mat4 texProjectionMat;
 	mat4 texProjectionMat;
 	vec4 blendFactors;
 	vec4 blendFactors;
 };
 };
-const uint DECAL_SIZEOF = (3 * 4 + 16) * 4;
+const uint SIZEOF_DECAL = 3 * SIZEOF_VEC4 + SIZEOF_MAT4;
 
 
 //
 //
 // Common uniforms
 // Common uniforms
@@ -113,12 +113,12 @@ const uint _NEXT_TEX_BINDING_2 = LIGHT_TEX_BINDING + 1;
 
 
 layout(ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING), std140) uniform u1_
 layout(ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING), std140) uniform u1_
 {
 {
-	PointLight u_pointLights[UBO_MAX_SIZE / POINT_LIGHT_SIZEOF];
+	PointLight u_pointLights[UBO_MAX_SIZE / SIZEOF_POINT_LIGHT];
 };
 };
 
 
 layout(ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING + 1), std140, row_major) uniform u2_
 layout(ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING + 1), std140, row_major) uniform u2_
 {
 {
-	SpotLight u_spotLights[UBO_MAX_SIZE / SPOT_LIGHT_SIZEOF];
+	SpotLight u_spotLights[UBO_MAX_SIZE / SIZEOF_SPOT_LIGHT];
 };
 };
 
 
 layout(ANKI_TEX_BINDING(LIGHT_SET, LIGHT_TEX_BINDING + 0)) uniform highp sampler2D u_shadowTex;
 layout(ANKI_TEX_BINDING(LIGHT_SET, LIGHT_TEX_BINDING + 0)) uniform highp sampler2D u_shadowTex;
@@ -136,7 +136,7 @@ const uint _NEXT_TEX_BINDING_3 = _NEXT_TEX_BINDING_2 + 3;
 
 
 layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_2)) uniform u3_
 layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_2)) uniform u3_
 {
 {
-	ReflectionProbe u_reflectionProbes[UBO_MAX_SIZE / REFLECTION_PROBE_SIZEOF];
+	ReflectionProbe u_reflectionProbes[UBO_MAX_SIZE / SIZEOF_REFLECTION_PROBE];
 };
 };
 
 
 layout(ANKI_TEX_BINDING(LIGHT_SET, _NEXT_TEX_BINDING_2 + 0)) uniform samplerCubeArray u_reflectionsTex;
 layout(ANKI_TEX_BINDING(LIGHT_SET, _NEXT_TEX_BINDING_2 + 0)) uniform samplerCubeArray u_reflectionsTex;
@@ -153,7 +153,7 @@ const uint _NEXT_TEX_BINDING_3 = _NEXT_TEX_BINDING_2;
 #if defined(LIGHT_DECALS)
 #if defined(LIGHT_DECALS)
 layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_3)) uniform u4_
 layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_3)) uniform u4_
 {
 {
-	Decal u_decals[UBO_MAX_SIZE / DECAL_SIZEOF];
+	Decal u_decals[UBO_MAX_SIZE / SIZEOF_DECAL];
 };
 };
 
 
 layout(ANKI_TEX_BINDING(LIGHT_SET, _NEXT_TEX_BINDING_3 + 0)) uniform sampler2D u_diffDecalTex;
 layout(ANKI_TEX_BINDING(LIGHT_SET, _NEXT_TEX_BINDING_3 + 0)) uniform sampler2D u_diffDecalTex;

+ 5 - 2
shaders/Common.glsl

@@ -41,6 +41,9 @@ const uint MAX_U32 = 0xFFFFFFFFu;
 const float PI = 3.14159265358979323846;
 const float PI = 3.14159265358979323846;
 const uint UBO_MAX_SIZE = 16384u;
 const uint UBO_MAX_SIZE = 16384u;
 
 
+const uint SIZEOF_VEC4 = 4 * 4;
+const uint SIZEOF_MAT4 = 4 * SIZEOF_VEC4;
+
 // Macros
 // Macros
 #define UV_TO_NDC(x_) ((x_)*2.0 - 1.0)
 #define UV_TO_NDC(x_) ((x_)*2.0 - 1.0)
 #define NDC_TO_UV(x_) ((x_)*0.5 + 0.5)
 #define NDC_TO_UV(x_) ((x_)*0.5 + 0.5)
@@ -66,8 +69,8 @@ const uint UBO_MAX_SIZE = 16384u;
 #define PASS_EZ 2
 #define PASS_EZ 2
 
 
 // Other
 // Other
-#if defined(ANKI_ARB_SHADER_BALLOT) && AMD_VK_READ_FIRST_INVOCATION_COMPILER_CRASH == 0
-#	define UNIFORM(x_) readFirstInvocationARB(x_)
+#if defined(ANKI_BACKEND_VULKAN) && ANKI_BACKEND_MAJOR >= 1 && ANKI_BACKEND_MINOR >= 1
+#	define UNIFORM(x_) subgroupBroadcastFirst(x_)
 #else
 #else
 #	define UNIFORM(x_) x_
 #	define UNIFORM(x_) x_
 #endif
 #endif

+ 1 - 1
shaders/ForwardShadingCommonFrag.glsl

@@ -70,7 +70,7 @@ vec3 computeLightColor(vec3 diffCol, vec3 worldPos)
 		if(light.diffuseColorTileSize.w >= 0.0)
 		if(light.diffuseColorTileSize.w >= 0.0)
 		{
 		{
 			shadow = computeShadowFactorOmni(
 			shadow = computeShadowFactorOmni(
-				frag2Light, light.radiusPad3.x, light.atlasTilesPad2.xy, light.diffuseColorTileSize.w, u_shadowTex);
+				frag2Light, light.radiusPad1.x, light.atlasTiles, light.diffuseColorTileSize.w, u_shadowTex);
 		}
 		}
 #endif
 #endif
 
 

+ 20 - 1
shaders/LightFunctions.glsl

@@ -21,6 +21,16 @@ vec3 F_Unreal(vec3 specular, float VoH)
 	return specular + (1.0 - specular) * pow(2.0, (-5.55473 * VoH - 6.98316) * VoH);
 	return specular + (1.0 - specular) * pow(2.0, (-5.55473 * VoH - 6.98316) * VoH);
 }
 }
 
 
+// Fresnel Schlick: "An Inexpensive BRDF Model for Physically-Based Rendering"
+// It has lower VGRPs than F_Unreal
+vec3 F_Schlick(vec3 specular, float VoH)
+{
+	float a = 1.0 - VoH;
+	float a2 = a * a;
+	float a5 = a2 * a2 * a; // a5 = a^5
+	return /*saturate(50.0 * specular.g) */ a5 + (1.0 - a5) * specular;
+}
+
 // D(n,h) aka NDF: GGX Trowbridge-Reitz
 // D(n,h) aka NDF: GGX Trowbridge-Reitz
 float D_GGX(float roughness, float NoH)
 float D_GGX(float roughness, float NoH)
 {
 {
@@ -45,7 +55,7 @@ vec3 envBRDF(vec3 specular, float roughness, sampler2D integrationLut, float NoV
 	float a = roughness * roughness;
 	float a = roughness * roughness;
 	float a2 = a * a;
 	float a2 = a * a;
 	vec2 envBRDF = textureLod(integrationLut, vec2(a2, NoV), 0.0).xy;
 	vec2 envBRDF = textureLod(integrationLut, vec2(a2, NoV), 0.0).xy;
-	return specular * envBRDF.x + min(1.0, 50.0 * specular.g) * envBRDF.y;
+	return specular * envBRDF.x + /*min(1.0, 50.0 * specular.g) */ envBRDF.y;
 }
 }
 
 
 vec3 diffuseLambert(vec3 diffuse)
 vec3 diffuseLambert(vec3 diffuse)
@@ -63,8 +73,17 @@ vec3 computeSpecularColorBrdf(GbufferInfo gbuffer, vec3 viewDir, vec3 frag2Light
 	float NoH = max(EPSILON, dot(gbuffer.normal, H));
 	float NoH = max(EPSILON, dot(gbuffer.normal, H));
 	float NoV = max(EPSILON, dot(gbuffer.normal, viewDir));
 	float NoV = max(EPSILON, dot(gbuffer.normal, viewDir));
 
 
+	// F
+#if 0
 	vec3 F = F_Unreal(gbuffer.specular, VoH);
 	vec3 F = F_Unreal(gbuffer.specular, VoH);
+#else
+	vec3 F = F_Schlick(gbuffer.specular, VoH);
+#endif
+
+	// D
 	float D = D_GGX(gbuffer.roughness, NoH);
 	float D = D_GGX(gbuffer.roughness, NoH);
+
+	// Vis
 	float V = V_Schlick(gbuffer.roughness, NoV, NoL);
 	float V = V_Schlick(gbuffer.roughness, NoV, NoL);
 
 
 	return F * (V * D);
 	return F * (V * D);

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

@@ -559,7 +559,7 @@ Error App::initDirs(const ConfigSet& cfg)
 	m_cacheDir.sprintf(m_heapAlloc, "%s/cache", &m_settingsDir[0]);
 	m_cacheDir.sprintf(m_heapAlloc, "%s/cache", &m_settingsDir[0]);
 
 
 	const Bool cacheDirExists = directoryExists(m_cacheDir.toCString());
 	const Bool cacheDirExists = directoryExists(m_cacheDir.toCString());
-	if(cfg.getNumber("clearCaches") && cacheDirExists)
+	if(cfg.getNumber("core.clearCaches") && cacheDirExists)
 	{
 	{
 		ANKI_CORE_LOGI("Will delete the cache dir and start fresh: %s", &m_cacheDir[0]);
 		ANKI_CORE_LOGI("Will delete the cache dir and start fresh: %s", &m_cacheDir[0]);
 		ANKI_CHECK(removeDirectory(m_cacheDir.toCString()));
 		ANKI_CHECK(removeDirectory(m_cacheDir.toCString()));

+ 5 - 4
src/anki/core/Config.cpp

@@ -45,8 +45,6 @@ Config::Config()
 	// Globals
 	// Globals
 	newOption("width", 1280);
 	newOption("width", 1280);
 	newOption("height", 768);
 	newOption("height", 768);
-	newOption("tessellation", true);
-	newOption("clearCaches", false);
 
 
 	// Resource
 	// Resource
 	newOption("rsrc.maxTextureSize", 1024 * 1024);
 	newOption("rsrc.maxTextureSize", 1024 * 1024);
@@ -55,8 +53,6 @@ Config::Config()
 	newOption("rsrc.transferScratchMemorySize", 256_MB);
 	newOption("rsrc.transferScratchMemorySize", 256_MB);
 
 
 	// Window
 	// Window
-	newOption("window.glmajor", 4);
-	newOption("window.glminor", 5);
 	newOption("window.fullscreenDesktopResolution", false);
 	newOption("window.fullscreenDesktopResolution", false);
 	newOption("window.debugContext", false);
 	newOption("window.debugContext", false);
 	newOption("window.vsync", false);
 	newOption("window.vsync", false);
@@ -64,6 +60,10 @@ Config::Config()
 
 
 	// GR
 	// GR
 	newOption("gr.diskShaderCacheMaxSize", 10_MB);
 	newOption("gr.diskShaderCacheMaxSize", 10_MB);
+	newOption("gr.vkminor", 0);
+	newOption("gr.vkmajor", 1);
+	newOption("gr.glmajor", 4);
+	newOption("gr.glminor", 5);
 
 
 	// Core
 	// Core
 	newOption("core.uniformPerFrameMemorySize", 16_MB);
 	newOption("core.uniformPerFrameMemorySize", 16_MB);
@@ -72,6 +72,7 @@ Config::Config()
 	newOption("core.textureBufferPerFrameMemorySize", 1_MB);
 	newOption("core.textureBufferPerFrameMemorySize", 1_MB);
 	newOption("core.mainThreadCount", getCpuCoresCount() / 2);
 	newOption("core.mainThreadCount", getCpuCoresCount() / 2);
 	newOption("core.displayStats", false);
 	newOption("core.displayStats", false);
+	newOption("core.clearCaches", false);
 }
 }
 
 
 Config::~Config()
 Config::~Config()

+ 7 - 4
src/anki/gr/Common.h

@@ -132,11 +132,14 @@ public:
 	/// GPU vendor.
 	/// GPU vendor.
 	GpuVendor m_gpuVendor = GpuVendor::UNKNOWN;
 	GpuVendor m_gpuVendor = GpuVendor::UNKNOWN;
 
 
-	/// Device supports subgroup operations.
-	Bool8 m_shaderSubgroups = false;
+	/// API version.
+	U8 m_minorApiVersion = 0;
 
 
-	/// Pad it because valgrind complains.
-	U8 _m_padding[2] = {};
+	/// API version.
+	U8 m_majorApiVersion = 0;
+
+	// WARNING Remember to pad it because valgrind complains.
+	U8 _m_padding[1];
 };
 };
 
 
 /// The type of the allocator for heap allocations
 /// The type of the allocator for heap allocations

+ 8 - 6
src/anki/gr/ShaderCompiler.cpp

@@ -31,6 +31,8 @@ static const Array<const char*, U(ShaderType::COUNT)> SHADER_NAME = {
 
 
 static const char* SHADER_HEADER = R"(#version 450 core
 static const char* SHADER_HEADER = R"(#version 450 core
 #define ANKI_BACKEND_%s 1
 #define ANKI_BACKEND_%s 1
+#define ANKI_BACKEND_MINOR %u
+#define ANKI_BACKEND_MAJOR %u
 #define ANKI_VENDOR_%s 1
 #define ANKI_VENDOR_%s 1
 #define ANKI_%s_SHADER 1
 #define ANKI_%s_SHADER 1
 
 
@@ -62,11 +64,10 @@ static const char* SHADER_HEADER = R"(#version 450 core
 #	define ANKI_LOOP [[dont_unroll]]
 #	define ANKI_LOOP [[dont_unroll]]
 #	define ANKI_BRANCH [[branch]]
 #	define ANKI_BRANCH [[branch]]
 #	define ANKI_FLATTEN [[flatten]]
 #	define ANKI_FLATTEN [[flatten]]
-#endif
 
 
-#if %u
-#	extension GL_ARB_shader_ballot : require
-#	define ANKI_ARB_SHADER_BALLOT 1
+#	if ANKI_BACKEND_MAJOR == 1 && ANKI_BACKEND_MINOR >= 1
+#		extension GL_KHR_shader_subgroup_ballot : require
+#	endif
 #endif
 #endif
 
 
 %s)";
 %s)";
@@ -228,6 +229,7 @@ static ANKI_USE_RESULT Error genSpirv(const ShaderCompiler::BuildContext& ctx, s
 	glslang::TShader shader(stage);
 	glslang::TShader shader(stage);
 	Array<const char*, 1> csrc = {{&ctx.m_src[0]}};
 	Array<const char*, 1> csrc = {{&ctx.m_src[0]}};
 	shader.setStrings(&csrc[0], 1);
 	shader.setStrings(&csrc[0], 1);
+	shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
 	if(!shader.parse(&GLSLANG_LIMITS, 100, false, messages))
 	if(!shader.parse(&GLSLANG_LIMITS, 100, false, messages))
 	{
 	{
 		ShaderCompiler::logShaderErrorCode(shader.getInfoLog(), ctx.m_src, ctx.m_alloc);
 		ShaderCompiler::logShaderErrorCode(shader.getInfoLog(), ctx.m_src, ctx.m_alloc);
@@ -300,6 +302,8 @@ Error ShaderCompiler::compile(CString source, const ShaderCompilerOptions& optio
 
 
 	fullSrc.sprintf(SHADER_HEADER,
 	fullSrc.sprintf(SHADER_HEADER,
 		(options.m_outLanguage == ShaderLanguage::GLSL) ? "GL" : "VULKAN",
 		(options.m_outLanguage == ShaderLanguage::GLSL) ? "GL" : "VULKAN",
+		options.m_gpuCapabilities.m_minorApiVersion,
+		options.m_gpuCapabilities.m_majorApiVersion,
 		&GPU_VENDOR_STR[options.m_gpuCapabilities.m_gpuVendor][0],
 		&GPU_VENDOR_STR[options.m_gpuCapabilities.m_gpuVendor][0],
 		SHADER_NAME[options.m_shaderType],
 		SHADER_NAME[options.m_shaderType],
 		// GL bindings
 		// GL bindings
@@ -313,8 +317,6 @@ Error ShaderCompiler::compile(CString source, const ShaderCompilerOptions& optio
 		MAX_TEXTURE_BINDINGS,
 		MAX_TEXTURE_BINDINGS,
 		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS,
 		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS,
 		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS,
 		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS,
-		// Ballot
-		!!(options.m_gpuCapabilities.m_shaderSubgroups) ? 1u : 0u,
 		&source[0]);
 		&source[0]);
 
 
 	ctx.m_src = fullSrc.toCString();
 	ctx.m_src = fullSrc.toCString();

+ 2 - 1
src/anki/gr/gl/GrManagerImpl.cpp

@@ -52,13 +52,14 @@ Error GrManagerImpl::init(GrManagerInitInfo& init, GrAllocator<U8> alloc)
 
 
 	// Misc
 	// Misc
 	m_capabilities.m_gpuVendor = m_state->m_gpu;
 	m_capabilities.m_gpuVendor = m_state->m_gpu;
-	m_capabilities.m_shaderSubgroups = !!(m_state->m_extensions & GlExtensions::ARB_SHADER_BALLOT);
 	m_capabilities.m_uniformBufferBindOffsetAlignment = m_state->m_uboAlignment;
 	m_capabilities.m_uniformBufferBindOffsetAlignment = m_state->m_uboAlignment;
 	m_capabilities.m_uniformBufferMaxRange = m_state->m_uniBlockMaxSize;
 	m_capabilities.m_uniformBufferMaxRange = m_state->m_uniBlockMaxSize;
 	m_capabilities.m_storageBufferBindOffsetAlignment = m_state->m_ssboAlignment;
 	m_capabilities.m_storageBufferBindOffsetAlignment = m_state->m_ssboAlignment;
 	m_capabilities.m_storageBufferMaxRange = m_state->m_storageBlockMaxSize;
 	m_capabilities.m_storageBufferMaxRange = m_state->m_storageBlockMaxSize;
 	m_capabilities.m_textureBufferBindOffsetAlignment = m_state->m_tboAlignment;
 	m_capabilities.m_textureBufferBindOffsetAlignment = m_state->m_tboAlignment;
 	m_capabilities.m_textureBufferMaxRange = m_state->m_tboMaxRange;
 	m_capabilities.m_textureBufferMaxRange = m_state->m_tboMaxRange;
+	m_capabilities.m_majorApiVersion = U(init.m_config->getNumber("gr.glmajor"));
+	m_capabilities.m_minorApiVersion = U(init.m_config->getNumber("gr.glmajor"));
 
 
 	return Error::NONE;
 	return Error::NONE;
 }
 }

+ 4 - 4
src/anki/gr/gl/GrManagerImplSdl.cpp

@@ -36,8 +36,8 @@ public:
 		m_window = init.m_window->getNative().m_window;
 		m_window = init.m_window->getNative().m_window;
 
 
 		ANKI_GL_LOGI("Creating GL %u.%u context...",
 		ANKI_GL_LOGI("Creating GL %u.%u context...",
-			U(init.m_config->getNumber("window.glmajor")),
-			U(init.m_config->getNumber("window.glminor")));
+			U(init.m_config->getNumber("gr.glmajor")),
+			U(init.m_config->getNumber("gr.glminor")));
 
 
 		if(init.m_config->getNumber("window.debugContext"))
 		if(init.m_config->getNumber("window.debugContext"))
 		{
 		{
@@ -48,8 +48,8 @@ public:
 			}
 			}
 		}
 		}
 
 
-		if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, init.m_config->getNumber("window.glmajor"))
-			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, init.m_config->getNumber("window.glminor"))
+		if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, init.m_config->getNumber("gr.glmajor"))
+			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, init.m_config->getNumber("gr.glminor"))
 			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE))
 			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE))
 		{
 		{
 			ANKI_GL_LOGE("SDL_GL_SetAttribute() failed");
 			ANKI_GL_LOGE("SDL_GL_SetAttribute() failed");

+ 40 - 4
src/anki/gr/vulkan/GrManagerImpl.cpp

@@ -204,8 +204,6 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	m_descrFactory.init(getAllocator(), m_device);
 	m_descrFactory.init(getAllocator(), m_device);
 	m_pplineLayoutFactory.init(getAllocator(), m_device);
 	m_pplineLayoutFactory.init(getAllocator(), m_device);
 
 
-	m_capabilities.m_shaderSubgroups = !!(m_extensions & VulkanExtensions::EXT_SHADER_SUBGROUP_BALLOT);
-
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
@@ -219,7 +217,8 @@ Error GrManagerImpl::initInstance(const GrManagerInitInfo& init)
 	app.applicationVersion = 1;
 	app.applicationVersion = 1;
 	app.pEngineName = "AnKi 3D Engine";
 	app.pEngineName = "AnKi 3D Engine";
 	app.engineVersion = (ANKI_VERSION_MAJOR << 16) | ANKI_VERSION_MINOR;
 	app.engineVersion = (ANKI_VERSION_MAJOR << 16) | ANKI_VERSION_MINOR;
-	app.apiVersion = VK_MAKE_VERSION(1, 0, 3);
+	app.apiVersion =
+		VK_MAKE_VERSION(U32(init.m_config->getNumber("gr.vkmajor")), U32(init.m_config->getNumber("gr.vkminor")), 0);
 
 
 	VkInstanceCreateInfo ci = {};
 	VkInstanceCreateInfo ci = {};
 	ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
 	ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
@@ -418,6 +417,9 @@ Error GrManagerImpl::initInstance(const GrManagerInitInfo& init)
 		max<U32>(ANKI_SAFE_ALIGNMENT, m_devProps.limits.minTexelBufferOffsetAlignment);
 		max<U32>(ANKI_SAFE_ALIGNMENT, m_devProps.limits.minTexelBufferOffsetAlignment);
 	m_capabilities.m_textureBufferMaxRange = MAX_U32;
 	m_capabilities.m_textureBufferMaxRange = MAX_U32;
 
 
+	m_capabilities.m_majorApiVersion = init.m_config->getNumber("gr.vkmajor");
+	m_capabilities.m_minorApiVersion = init.m_config->getNumber("gr.vkminor");
+
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
@@ -523,7 +525,8 @@ Error GrManagerImpl::initDevice(const GrManagerInitInfo& init)
 				m_extensions |= VulkanExtensions::EXT_SHADER_SUBGROUP_BALLOT;
 				m_extensions |= VulkanExtensions::EXT_SHADER_SUBGROUP_BALLOT;
 				extensionsToEnable[extensionsToEnableCount++] = VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME;
 				extensionsToEnable[extensionsToEnableCount++] = VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME;
 			}
 			}
-			else if(CString(extensionInfos[extCount].extensionName) == VK_AMD_SHADER_INFO_EXTENSION_NAME)
+			else if(CString(extensionInfos[extCount].extensionName) == VK_AMD_SHADER_INFO_EXTENSION_NAME
+					&& init.m_config->getNumber("core.displayStats"))
 			{
 			{
 				m_extensions |= VulkanExtensions::AMD_SHADER_INFO;
 				m_extensions |= VulkanExtensions::AMD_SHADER_INFO;
 				extensionsToEnable[extensionsToEnableCount++] = VK_AMD_SHADER_INFO_EXTENSION_NAME;
 				extensionsToEnable[extensionsToEnableCount++] = VK_AMD_SHADER_INFO_EXTENSION_NAME;
@@ -885,4 +888,37 @@ VkBool32 GrManagerImpl::debugReportCallbackEXT(VkDebugReportFlagsEXT flags,
 	return false;
 	return false;
 }
 }
 
 
+void GrManagerImpl::printPipelineShaderInfo(VkPipeline ppline, CString name, ShaderTypeBit stages) const
+{
+	if(m_pfnGetShaderInfoAMD)
+	{
+		VkShaderStatisticsInfoAMD stats = {};
+		size_t size = sizeof(stats);
+
+		ANKI_VK_LOGI("Pipeline \"%s\" stats:", name.cstr());
+
+		for(ShaderType type = ShaderType::FIRST; type < ShaderType::COUNT; ++type)
+		{
+			ShaderTypeBit stage = stages & ShaderTypeBit(1 << type);
+			if(!stage)
+			{
+				continue;
+			}
+
+			VkResult err = m_pfnGetShaderInfoAMD(
+				m_device, ppline, convertShaderTypeBit(stage), VK_SHADER_INFO_TYPE_STATISTICS_AMD, &size, &stats);
+
+			if(!err)
+			{
+				ANKI_VK_LOGI("\tStage %u: VGRPS %u/%u, SGRPS %u/%u",
+					U32(type),
+					stats.resourceUsage.numUsedVgprs,
+					stats.numAvailableVgprs,
+					stats.resourceUsage.numUsedSgprs,
+					stats.numAvailableSgprs);
+			}
+		}
+	}
+}
+
 } // end namespace anki
 } // end namespace anki

+ 1 - 20
src/anki/gr/vulkan/GrManagerImpl.h

@@ -212,26 +212,7 @@ public:
 	}
 	}
 	/// @}
 	/// @}
 
 
-	void printPipelineShaderInfo(VkPipeline ppline, CString name) const
-	{
-		if(m_pfnGetShaderInfoAMD)
-		{
-			VkShaderStatisticsInfoAMD stats = {};
-			size_t size = sizeof(stats);
-			VkResult err = m_pfnGetShaderInfoAMD(
-				m_device, ppline, VK_SHADER_STAGE_ALL, VK_SHADER_INFO_TYPE_STATISTICS_AMD, &size, &stats);
-
-			if(!err)
-			{
-				ANKI_VK_LOGI("Pipeline \"%s\" stats: VGRPS %u/%u, SGRPS %u/%u",
-					name.cstr(),
-					stats.resourceUsage.numUsedVgprs,
-					stats.numAvailableVgprs,
-					stats.resourceUsage.numUsedSgprs,
-					stats.numAvailableSgprs);
-			}
-		}
-	}
+	void printPipelineShaderInfo(VkPipeline ppline, CString name, ShaderTypeBit stages) const;
 
 
 private:
 private:
 	U64 m_frame = 0;
 	U64 m_frame = 0;

+ 6 - 0
src/anki/gr/vulkan/Pipeline.cpp

@@ -4,6 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <anki/gr/vulkan/Pipeline.h>
 #include <anki/gr/vulkan/Pipeline.h>
+#include <anki/gr/vulkan/GrManagerImpl.h>
 #include <anki/gr/common/Misc.h>
 #include <anki/gr/common/Misc.h>
 #include <anki/core/Trace.h>
 #include <anki/core/Trace.h>
 
 
@@ -441,6 +442,11 @@ void PipelineFactory::newPipeline(PipelineStateTracker& state, Pipeline& ppline,
 
 
 		m_pplines.emplace(m_alloc, hash, pp);
 		m_pplines.emplace(m_alloc, hash, pp);
 		ppline.m_handle = pp.m_handle;
 		ppline.m_handle = pp.m_handle;
+
+		// Print shader info
+		const ShaderProgramImpl& shaderImpl = static_cast<const ShaderProgramImpl&>(*state.m_state.m_prog);
+		shaderImpl.getGrManagerImpl().printPipelineShaderInfo(
+			pp.m_handle, shaderImpl.getName(), shaderImpl.getStages());
 	}
 	}
 }
 }
 
 

+ 2 - 0
src/anki/gr/vulkan/Pipeline.h

@@ -175,6 +175,8 @@ public:
 /// Track changes in the static state.
 /// Track changes in the static state.
 class PipelineStateTracker : public NonCopyable
 class PipelineStateTracker : public NonCopyable
 {
 {
+	friend class PipelineFactory;
+
 public:
 public:
 	PipelineStateTracker()
 	PipelineStateTracker()
 	{
 	{

+ 3 - 4
src/anki/gr/vulkan/ShaderProgramImpl.cpp

@@ -29,7 +29,6 @@ ShaderProgramImpl::~ShaderProgramImpl()
 Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 {
 {
 	ANKI_ASSERT(inf.isValid());
 	ANKI_ASSERT(inf.isValid());
-	ShaderTypeBit shaderMask = ShaderTypeBit::NONE;
 	m_shaders = inf.m_shaders;
 	m_shaders = inf.m_shaders;
 
 
 	// Merge bindings
 	// Merge bindings
@@ -46,7 +45,7 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 				continue;
 				continue;
 			}
 			}
 
 
-			shaderMask |= static_cast<ShaderTypeBit>(1 << stype);
+			m_stages |= static_cast<ShaderTypeBit>(1 << stype);
 
 
 			const ShaderImpl& simpl = *scast<const ShaderImpl*>(m_shaders[stype].get());
 			const ShaderImpl& simpl = *scast<const ShaderImpl*>(m_shaders[stype].get());
 
 
@@ -116,7 +115,7 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 
 
 	// Get some masks
 	// Get some masks
 	//
 	//
-	const Bool graphicsProg = !!(shaderMask & ShaderTypeBit::VERTEX);
+	const Bool graphicsProg = !!(m_stages & ShaderTypeBit::VERTEX);
 	if(graphicsProg)
 	if(graphicsProg)
 	{
 	{
 		m_refl.m_attributeMask = scast<const ShaderImpl*>(m_shaders[ShaderType::VERTEX].get())->m_attributeMask;
 		m_refl.m_attributeMask = scast<const ShaderImpl*>(m_shaders[ShaderType::VERTEX].get())->m_attributeMask;
@@ -180,7 +179,7 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 
 
 		ANKI_VK_CHECK(vkCreateComputePipelines(
 		ANKI_VK_CHECK(vkCreateComputePipelines(
 			getDevice(), getGrManagerImpl().getPipelineCache(), 1, &ci, nullptr, &m_computePpline));
 			getDevice(), getGrManagerImpl().getPipelineCache(), 1, &ci, nullptr, &m_computePpline));
-		getGrManagerImpl().printPipelineShaderInfo(m_computePpline, getName());
+		getGrManagerImpl().printPipelineShaderInfo(m_computePpline, getName(), ShaderTypeBit::COMPUTE);
 	}
 	}
 
 
 	return Error::NONE;
 	return Error::NONE;

+ 7 - 0
src/anki/gr/vulkan/ShaderProgramImpl.h

@@ -82,8 +82,15 @@ public:
 		return m_computePpline;
 		return m_computePpline;
 	}
 	}
 
 
+	ShaderTypeBit getStages() const
+	{
+		ANKI_ASSERT(!!m_stages);
+		return m_stages;
+	}
+
 private:
 private:
 	Array<ShaderPtr, U(ShaderType::COUNT)> m_shaders;
 	Array<ShaderPtr, U(ShaderType::COUNT)> m_shaders;
+	ShaderTypeBit m_stages = ShaderTypeBit::NONE;
 
 
 	Array<VkPipelineShaderStageCreateInfo, U(ShaderType::COUNT) - 1> m_shaderCreateInfos;
 	Array<VkPipelineShaderStageCreateInfo, U(ShaderType::COUNT) - 1> m_shaderCreateInfos;
 	U32 m_shaderCreateInfoCount = 0;
 	U32 m_shaderCreateInfoCount = 0;

+ 4 - 4
src/anki/renderer/LightBin.cpp

@@ -30,8 +30,8 @@ class ShaderPointLight
 public:
 public:
 	Vec4 m_posRadius;
 	Vec4 m_posRadius;
 	Vec4 m_diffuseColorTileSize;
 	Vec4 m_diffuseColorTileSize;
-	Vec4 m_radiusPad3;
-	UVec4 m_atlasTilesPad2;
+	Vec2 m_radiusPad1;
+	UVec2 m_atlasTiles;
 };
 };
 
 
 class ShaderSpotLight
 class ShaderSpotLight
@@ -665,10 +665,10 @@ void LightBin::writeAndBinPointLight(
 	else
 	else
 	{
 	{
 		slight.m_diffuseColorTileSize.w() = lightEl.m_atlasTileSize;
 		slight.m_diffuseColorTileSize.w() = lightEl.m_atlasTileSize;
-		slight.m_atlasTilesPad2 = UVec4(lightEl.m_atlasTiles.x(), lightEl.m_atlasTiles.y(), 0, 0);
+		slight.m_atlasTiles = UVec2(lightEl.m_atlasTiles.x(), lightEl.m_atlasTiles.y());
 	}
 	}
 
 
-	slight.m_radiusPad3 = Vec4(lightEl.m_radius);
+	slight.m_radiusPad1 = Vec2(lightEl.m_radius);
 
 
 	// Now bin it
 	// Now bin it
 	Sphere sphere(lightEl.m_worldPosition.xyz0(), lightEl.m_radius);
 	Sphere sphere(lightEl.m_worldPosition.xyz0(), lightEl.m_radius);

+ 0 - 2
src/anki/renderer/Renderer.cpp

@@ -82,8 +82,6 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_lodDistances[1] = config.getNumber("r.lodDistance1");
 	m_lodDistances[1] = config.getNumber("r.lodDistance1");
 	m_frameCount = 0;
 	m_frameCount = 0;
 
 
-	m_tessellation = config.getNumber("tessellation");
-
 	// A few sanity checks
 	// A few sanity checks
 	if(m_width < 10 || m_height < 10)
 	if(m_width < 10 || m_height < 10)
 	{
 	{

+ 0 - 6
src/anki/renderer/Renderer.h

@@ -220,11 +220,6 @@ anki_internal:
 		return *m_ui;
 		return *m_ui;
 	}
 	}
 
 
-	Bool getTessellationEnabled() const
-	{
-		return m_tessellation;
-	}
-
 	/// My version of gluUnproject
 	/// My version of gluUnproject
 	/// @param windowCoords Window screen coords
 	/// @param windowCoords Window screen coords
 	/// @param modelViewMat The modelview matrix
 	/// @param modelViewMat The modelview matrix
@@ -374,7 +369,6 @@ private:
 	U32 m_height;
 	U32 m_height;
 
 
 	Array<F32, MAX_LOD_COUNT - 1> m_lodDistances; ///< Distance that used to calculate the LOD
 	Array<F32, MAX_LOD_COUNT - 1> m_lodDistances; ///< Distance that used to calculate the LOD
-	Bool8 m_tessellation;
 
 
 	RenderableDrawer m_sceneDrawer;
 	RenderableDrawer m_sceneDrawer;
 
 

+ 1 - 1
src/anki/resource/ImageLoader.cpp

@@ -558,7 +558,7 @@ Error ImageLoader::load(ResourceFilePtr file, const CString& filename, U32 maxTe
 {
 {
 	// get the extension
 	// get the extension
 	StringAuto ext(m_alloc);
 	StringAuto ext(m_alloc);
-	getFileExtension(filename, m_alloc, ext);
+	getFilepathExtension(filename, m_alloc, ext);
 
 
 	if(ext.isEmpty())
 	if(ext.isEmpty())
 	{
 	{

+ 12 - 2
src/anki/resource/ShaderProgramResource.cpp

@@ -7,6 +7,7 @@
 #include <anki/misc/Xml.h>
 #include <anki/misc/Xml.h>
 #include <anki/resource/ShaderLoader.h>
 #include <anki/resource/ShaderLoader.h>
 #include <anki/resource/RenderingKey.h>
 #include <anki/resource/RenderingKey.h>
+#include <anki/util/Filesystem.h>
 
 
 namespace anki
 namespace anki
 {
 {
@@ -1091,8 +1092,17 @@ void ShaderProgramResource::initVariant(ConstWeakArray<ShaderProgramResourceMuta
 	StringAuto shaderHeader(getTempAllocator());
 	StringAuto shaderHeader(getTempAllocator());
 	shaderHeaderSrc.join("", shaderHeader);
 	shaderHeaderSrc.join("", shaderHeader);
 
 
+	// Create the program name
+	StringAuto progName(getTempAllocator());
+	getFilepathFilename(getFilename(), getTempAllocator(), progName);
+	char* cprogName = const_cast<char*>(progName.cstr());
+	if(progName.getLength() > MAX_GR_OBJECT_NAME_LENGTH)
+	{
+		cprogName[MAX_GR_OBJECT_NAME_LENGTH] = '\0';
+	}
+
 	// Create the shaders and the program
 	// Create the shaders and the program
-	ShaderProgramInitInfo progInf("RsrcProg");
+	ShaderProgramInitInfo progInf(cprogName);
 	for(ShaderType i = ShaderType::FIRST; i < ShaderType::COUNT; ++i)
 	for(ShaderType i = ShaderType::FIRST; i < ShaderType::COUNT; ++i)
 	{
 	{
 		if(!m_sources[i])
 		if(!m_sources[i])
@@ -1117,7 +1127,7 @@ void ShaderProgramResource::initVariant(ConstWeakArray<ShaderProgramResourceMuta
 			ANKI_RESOURCE_LOGF("Shader compilation failed");
 			ANKI_RESOURCE_LOGF("Shader compilation failed");
 		}
 		}
 
 
-		ShaderInitInfo inf("RsrcShader");
+		ShaderInitInfo inf(cprogName);
 		inf.m_shaderType = i;
 		inf.m_shaderType = i;
 		inf.m_binary = ConstWeakArray<U8>(&bin[0], bin.getSize());
 		inf.m_binary = ConstWeakArray<U8>(&bin[0], bin.getSize());
 
 

+ 20 - 2
src/anki/util/Filesystem.cpp

@@ -8,11 +8,29 @@
 namespace anki
 namespace anki
 {
 {
 
 
-void getFileExtension(const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out)
+void getFilepathExtension(const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out)
 {
 {
-	const char* pc = std::strrchr(&filename[0], '.');
+	out.destroy(alloc);
+	const char* pc = std::strrchr(filename.cstr(), '.');
+
+	if(pc == nullptr)
+	{
+		// Do nothing
+	}
+	else
+	{
+		++pc;
+		if(*pc != '\0')
+		{
+			out.create(alloc, CString(pc));
+		}
+	}
+}
 
 
+void getFilepathFilename(const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out)
+{
 	out.destroy(alloc);
 	out.destroy(alloc);
+	const char* pc = std::strrchr(filename.cstr(), '/');
 
 
 	if(pc == nullptr)
 	if(pc == nullptr)
 	{
 	{

+ 5 - 2
src/anki/util/Filesystem.h

@@ -16,8 +16,11 @@ namespace anki
 /// Return true if a file exists
 /// Return true if a file exists
 Bool fileExists(const CString& filename);
 Bool fileExists(const CString& filename);
 
 
-/// Get file extension.
-void getFileExtension(const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out);
+/// Get path extension.
+void getFilepathExtension(const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out);
+
+/// Get path filename.
+void getFilepathFilename(const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out);
 
 
 /// Return true if directory exists?
 /// Return true if directory exists?
 Bool directoryExists(const CString& dir);
 Bool directoryExists(const CString& dir);

+ 1 - 1
thirdparty

@@ -1 +1 @@
-Subproject commit 940c8fa270cd31574e79cf755dcf202855509a6e
+Subproject commit 6bb9a4db171dc231ccf4b8d19862bd91a959bf11