瀏覽代碼

Add HLSL compiling support

Panagiotis Christopoulos Charitos 3 年之前
父節點
當前提交
bd097f8845

+ 1 - 0
AnKi/ShaderCompiler/Common.h

@@ -19,6 +19,7 @@ namespace anki {
 #define ANKI_SHADER_COMPILER_LOGE(...) ANKI_LOG("SHCO", kError, __VA_ARGS__)
 #define ANKI_SHADER_COMPILER_LOGW(...) ANKI_LOG("SHCO", kWarning, __VA_ARGS__)
 #define ANKI_SHADER_COMPILER_LOGF(...) ANKI_LOG("SHCO", kFatal, __VA_ARGS__)
+#define ANKI_SHADER_COMPILER_LOGV(...) ANKI_LOG("SHCO", kVerbose, __VA_ARGS__)
 
 constexpr U32 kMaxShaderBinaryNameLength = 127;
 

+ 159 - 0
AnKi/ShaderCompiler/Dxc.cpp

@@ -0,0 +1,159 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/ShaderCompiler/Dxc.h>
+#include <AnKi/Util/Process.h>
+#include <AnKi/Util/Filesystem.h>
+#include <AnKi/Util/File.h>
+
+namespace anki {
+
+namespace {
+
+class CleanupFile
+{
+public:
+	StringRaii m_fileToDelete;
+
+	CleanupFile(BaseMemoryPool* pool, CString filename)
+		: m_fileToDelete(pool, filename)
+	{
+	}
+
+	~CleanupFile()
+	{
+		if(!m_fileToDelete.isEmpty())
+		{
+			const Error err = removeFile(m_fileToDelete);
+			if(!err)
+			{
+				ANKI_SHADER_COMPILER_LOGV("Deleted %s", m_fileToDelete.cstr());
+			}
+		}
+	}
+};
+
+} // end anonymous namespace
+
+static CString profile(ShaderType shaderType)
+{
+	switch(shaderType)
+	{
+	case ShaderType::kVertex:
+		return "vs_6_7";
+		break;
+	case ShaderType::kFragment:
+		return "ps_6_7";
+		break;
+	case ShaderType::kTessellationEvaluation:
+		return "ds_6_7";
+		break;
+	case ShaderType::kTessellationControl:
+		return "ds_6_7";
+		break;
+	case ShaderType::kGeometry:
+		return "gs_6_7";
+		break;
+	case ShaderType::kCompute:
+		return "cs_6_7";
+		break;
+	case ShaderType::kRayGen:
+	case ShaderType::kAnyHit:
+	case ShaderType::kClosestHit:
+	case ShaderType::kMiss:
+	case ShaderType::kIntersection:
+	case ShaderType::kCallable:
+		return "lib_6_7";
+		break;
+	default:
+		ANKI_ASSERT(0);
+	};
+
+	return "";
+}
+
+Error compileHlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tmpPool, DynamicArrayRaii<U8>& spirv,
+						 StringRaii& errorMessage)
+{
+	srand(U32(time(0)));
+	const I32 r = rand();
+
+	StringRaii tmpDir(&tmpPool);
+	ANKI_CHECK(getTempDirectory(tmpDir));
+
+	// Store src to a file
+	StringRaii hlslFilename(&tmpPool);
+	hlslFilename.sprintf("%s/%d.hlsl", tmpDir.cstr(), r);
+
+	File hlslFile;
+	ANKI_CHECK(hlslFile.open(hlslFilename, FileOpenFlag::kWrite));
+	CleanupFile hlslFileCleanup(&tmpPool, hlslFilename);
+	ANKI_CHECK(hlslFile.writeText(src));
+	hlslFile.close();
+
+	// Call DXC
+	StringRaii spvFilename(&tmpPool);
+	spvFilename.sprintf("%s/%d.spv", tmpDir.cstr(), r);
+
+	DynamicArrayRaii<StringRaii> dxcArgs(&tmpPool);
+	dxcArgs.emplaceBack(&tmpPool, "-Wall");
+	dxcArgs.emplaceBack(&tmpPool, "-Wextra");
+	dxcArgs.emplaceBack(&tmpPool, "-Wconversion");
+	dxcArgs.emplaceBack(&tmpPool, "-Werror");
+	dxcArgs.emplaceBack(&tmpPool, "-Wfatal-errors");
+	dxcArgs.emplaceBack(&tmpPool, "-Wno-unused-const-variable");
+	dxcArgs.emplaceBack(&tmpPool, "-enable-16bit-types");
+	dxcArgs.emplaceBack(&tmpPool, "-HV");
+	dxcArgs.emplaceBack(&tmpPool, "2021");
+	dxcArgs.emplaceBack(&tmpPool, "-E");
+	dxcArgs.emplaceBack(&tmpPool, "main");
+	dxcArgs.emplaceBack(&tmpPool, "-Fo");
+	dxcArgs.emplaceBack(&tmpPool, spvFilename);
+	dxcArgs.emplaceBack(&tmpPool, "-T");
+	dxcArgs.emplaceBack(&tmpPool, profile(shaderType));
+	dxcArgs.emplaceBack(&tmpPool, "-spirv");
+	dxcArgs.emplaceBack(&tmpPool, "-fspv-target-env=vulkan1.1spirv1.4");
+	dxcArgs.emplaceBack(&tmpPool, hlslFilename);
+
+	DynamicArrayRaii<CString> dxcArgs2(&tmpPool, dxcArgs.getSize());
+	for(U32 i = 0; i < dxcArgs.getSize(); ++i)
+	{
+		dxcArgs2[i] = dxcArgs[i];
+	}
+
+	Process proc;
+	ANKI_CHECK(proc.start(ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Windows64/dxc.exe", dxcArgs2));
+
+	I32 exitCode;
+	ProcessStatus status;
+	ANKI_CHECK(proc.wait(60.0_sec, &status, &exitCode));
+
+	if(!(status == ProcessStatus::kNotRunning && exitCode == 0))
+	{
+		if(exitCode != 0)
+		{
+			ANKI_CHECK(proc.readFromStderr(errorMessage));
+		}
+
+		if(errorMessage.isEmpty())
+		{
+			errorMessage = "Unknown error";
+		}
+
+		return Error::kFunctionFailed;
+	}
+
+	CleanupFile spvFileCleanup(&tmpPool, spvFilename);
+
+	// Read the spirv back
+	File spvFile;
+	ANKI_CHECK(spvFile.open(spvFilename, FileOpenFlag::kRead));
+	spirv.resize(U32(spvFile.getSize()));
+	ANKI_CHECK(spvFile.read(&spirv[0], spirv.getSizeInBytes()));
+
+	return Error::kNone;
+}
+
+} // end namespace anki

+ 21 - 0
AnKi/ShaderCompiler/Dxc.h

@@ -0,0 +1,21 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/ShaderCompiler/Common.h>
+#include <AnKi/Util/String.h>
+
+namespace anki {
+
+/// @addtogroup shader_compiler
+/// @{
+
+/// Compile HLSL to SPIR-V.
+Error compileHlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tmpPool, DynamicArrayRaii<U8>& spirv,
+						 StringRaii& errorMessage);
+/// @}
+
+} // end namespace anki

+ 3 - 2
AnKi/ShaderCompiler/Glslang.cpp

@@ -276,8 +276,8 @@ Error preprocessGlsl(CString in, StringRaii& out)
 	return Error::kNone;
 }
 
-Error compilerGlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tmpPool, DynamicArrayRaii<U8>& spirv,
-						  StringRaii& errorMessage)
+Error compileGlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tmpPool, DynamicArrayRaii<U8>& spirv,
+						 StringRaii& errorMessage)
 {
 #if ANKI_GLSLANG_DUMP
 	// Dump it
@@ -304,6 +304,7 @@ Error compilerGlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tm
 	shader.setStrings(&csrc[0], 1);
 	shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
 	shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_4);
+	shader.setOverrideVersion(460);
 	if(!shader.parse(&GLSLANG_LIMITS, 100, false, messages))
 	{
 		createErrorLog(shader.getInfoLog(), src, tmpPool, errorMessage);

+ 2 - 2
AnKi/ShaderCompiler/Glslang.h

@@ -17,8 +17,8 @@ namespace anki {
 Error preprocessGlsl(CString in, StringRaii& out);
 
 /// Compile glsl to SPIR-V.
-Error compilerGlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tmpPool, DynamicArrayRaii<U8>& spirv,
-						  StringRaii& errorMessage);
+Error compileGlslToSpirv(CString src, ShaderType shaderType, BaseMemoryPool& tmpPool, DynamicArrayRaii<U8>& spirv,
+						 StringRaii& errorMessage);
 /// @}
 
 } // end namespace anki

+ 12 - 3
AnKi/ShaderCompiler/ShaderProgramCompiler.cpp

@@ -6,6 +6,7 @@
 #include <AnKi/ShaderCompiler/ShaderProgramCompiler.h>
 #include <AnKi/ShaderCompiler/ShaderProgramParser.h>
 #include <AnKi/ShaderCompiler/Glslang.h>
+#include <AnKi/ShaderCompiler/Dxc.h>
 #include <AnKi/ShaderCompiler/ShaderProgramReflection.h>
 #include <AnKi/Util/Serializer.h>
 #include <AnKi/Util/HashMap.h>
@@ -180,8 +181,16 @@ static Error compileSpirv(ConstWeakArray<MutatorValue> mutation, const ShaderPro
 		}
 
 		// Compile
-		ANKI_CHECK(compilerGlslToSpirv(parserVariant.getSource(shaderType), shaderType, tempPool, spirv[shaderType],
-									   errorLog));
+		if(!parser.isHlsl())
+		{
+			ANKI_CHECK(compileGlslToSpirv(parserVariant.getSource(shaderType), shaderType, tempPool, spirv[shaderType],
+										  errorLog));
+		}
+		else
+		{
+			ANKI_CHECK(compileHlslToSpirv(parserVariant.getSource(shaderType), shaderType, tempPool, spirv[shaderType],
+										  errorLog));
+		}
 		ANKI_ASSERT(spirv[shaderType].getSize() > 0);
 	}
 
@@ -996,7 +1005,7 @@ Error compileShaderProgramInternal(CString fname, ShaderProgramFilesystemInterfa
 	binaryW.m_binary = newInstance<ShaderProgramBinary>(binaryPool);
 	ShaderProgramBinary& binary = *binaryW.m_binary;
 	binary = {};
-	memcpy(&binary.m_magic[0], SHADER_BINARY_MAGIC, 8);
+	memcpy(&binary.m_magic[0], kShaderBinaryMagic, 8);
 
 	// Parse source
 	ShaderProgramParser parser(fname, &fsystem, &tempPool, compilerOptions);

+ 3 - 3
AnKi/ShaderCompiler/ShaderProgramCompiler.h

@@ -14,8 +14,8 @@ namespace anki {
 /// @addtogroup shader_compiler
 /// @{
 
-constexpr const char* SHADER_BINARY_MAGIC = "ANKISDR8"; ///< WARNING: If changed change SHADER_BINARY_VERSION
-constexpr U32 SHADER_BINARY_VERSION = 8;
+inline constexpr const char* kShaderBinaryMagic = "ANKISDR8"; //! WARNING: If changed change kShaderBinaryVersion
+constexpr U32 kShaderBinaryVersion = 8;
 
 /// A wrapper over the POD ShaderProgramBinary class.
 /// @memberof ShaderProgramCompiler
@@ -72,7 +72,7 @@ Error ShaderProgramBinaryWrapper::deserializeFromAnyFile(TFile& file)
 
 	m_singleAllocation = true;
 
-	if(memcmp(SHADER_BINARY_MAGIC, &m_binary->m_magic[0], strlen(SHADER_BINARY_MAGIC)) != 0)
+	if(memcmp(kShaderBinaryMagic, &m_binary->m_magic[0], strlen(kShaderBinaryMagic)) != 0)
 	{
 		ANKI_SHADER_COMPILER_LOGE("Corrupted or wrong version of shader binary.");
 		return Error::kUserData;

+ 41 - 7
AnKi/ShaderCompiler/ShaderProgramParser.cpp

@@ -19,8 +19,7 @@ inline constexpr Array<CString, U32(ShaderType::kCount)> kShaderStageNames = {
 	{"VERTEX", "TESSELLATION_CONTROL", "TESSELLATION_EVALUATION", "GEOMETRY", "FRAGMENT", "COMPUTE", "RAY_GEN",
 	 "ANY_HIT", "CLOSEST_HIT", "MISS", "INTERSECTION", "CALLABLE"}};
 
-inline constexpr char kShaderHeader[] = R"(#version 460 core
-#define ANKI_%s_SHADER 1
+inline constexpr char kShaderHeader[] = R"(#define ANKI_%s_SHADER 1
 #define ANKI_PLATFORM_MOBILE %d
 #define ANKI_FORCE_FULL_FP_PRECISION %d
 
@@ -418,7 +417,7 @@ Error ShaderProgramParser::parseInclude(const StringRaii* begin, const StringRai
 	return Error::kNone;
 }
 
-Error ShaderProgramParser::parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth)
+Error ShaderProgramParser::parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth, U32 lineNumber)
 {
 	// Tokenize
 	DynamicArrayRaii<StringRaii> tokens(m_pool);
@@ -440,6 +439,8 @@ Error ShaderProgramParser::parseLine(CString line, CString fname, Bool& foundPra
 	{
 		// We _must_ have an #include
 		ANKI_CHECK(parseInclude(token + 1, end, line, fname, depth));
+
+		m_codeLines.pushBackSprintf("#line %u \"%s\"", lineNumber + 2, fname.cstr());
 	}
 	else if((token < end) && ((foundAloneHash && *token == "pragma") || *token == "#pragma"))
 	{
@@ -526,6 +527,10 @@ Error ShaderProgramParser::parseLine(CString line, CString fname, Bool& foundPra
 				ANKI_CHECK(checkActiveStruct());
 				ANKI_CHECK(parsePragmaMember(token + 1, end, line, fname));
 			}
+			else if(*token == "hlsl")
+			{
+				ANKI_CHECK(parsePragmaHlsl(token + 1, end, line, fname));
+			}
 			else
 			{
 				ANKI_PP_ERROR_MALFORMED();
@@ -823,10 +828,25 @@ Error ShaderProgramParser::parsePragmaStructEnd(const StringRaii* begin, const S
 	return Error::kNone;
 }
 
+Error ShaderProgramParser::parsePragmaHlsl(const StringRaii* begin, const StringRaii* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	// Check tokens
+	if(begin != end)
+	{
+		ANKI_PP_ERROR_MALFORMED();
+	}
+
+	m_hlsl = true;
+
+	return Error::kNone;
+}
+
 Error ShaderProgramParser::parseFile(CString fname, U32 depth)
 {
 	// First check the depth
-	if(depth > MAX_INCLUDE_DEPTH)
+	if(depth > kMaxIncludeDepth)
 	{
 		ANKI_SHADER_COMPILER_LOGE("The include depth is too high. Probably circular includance");
 	}
@@ -838,25 +858,34 @@ Error ShaderProgramParser::parseFile(CString fname, U32 depth)
 	ANKI_CHECK(m_fsystem->readAllText(fname, txt));
 
 	StringListRaii lines(m_pool);
-	lines.splitString(txt.toCString(), '\n');
+	lines.splitString(txt.toCString(), '\n', true);
 	if(lines.getSize() < 1)
 	{
 		ANKI_SHADER_COMPILER_LOGE("Source is empty");
 	}
 
+	m_codeLines.pushBackSprintf("#line 0 \"%s\"", fname.cstr());
+
 	// Parse lines
+	U32 lineCount = 0;
 	for(const String& line : lines)
 	{
-		if(line.find("pragma") != String::kNpos || line.find("include") != String::kNpos)
+		if(line.isEmpty())
+		{
+			m_codeLines.pushBack(" ");
+		}
+		else if(line.find("pragma") != String::kNpos || line.find("include") != String::kNpos)
 		{
 			// Possibly a preprocessor directive we care
-			ANKI_CHECK(parseLine(line.toCString(), fname, foundPragmaOnce, depth));
+			ANKI_CHECK(parseLine(line.toCString(), fname, foundPragmaOnce, depth, lineCount));
 		}
 		else
 		{
 			// Just append the line
 			m_codeLines.pushBack(line.toCString());
 		}
+
+		++lineCount;
 	}
 
 	if(foundPragmaOnce)
@@ -916,6 +945,11 @@ Error ShaderProgramParser::parse()
 		}
 	}
 
+	if(!m_hlsl)
+	{
+		m_codeLines.pushFront(StringRaii(m_pool, "#extension  GL_GOOGLE_cpp_style_line_directive : enable"));
+	}
+
 	// Create the code lines
 	if(m_codeLines.getSize())
 	{

+ 11 - 2
AnKi/ShaderCompiler/ShaderProgramParser.h

@@ -112,6 +112,7 @@ private:
 /// #pragma anki ray_type NUMBER
 /// #pragma anki reflect NAME
 /// #pragma anki skip_mutation MUTATOR0 VALUE0 MUTATOR1 VALUE1 [MUTATOR2 VALUE2 ...]
+/// #pragma anki hlsl // By default it's GLSL
 ///
 /// #pragma anki struct NAME
 /// #	pragma anki member [ANKI_RP] TYPE NAME [if MUTATOR_NAME is MUTATOR_VALUE]
@@ -176,6 +177,11 @@ public:
 		return m_ghostStructs;
 	}
 
+	Bool isHlsl() const
+	{
+		return m_hlsl;
+	}
+
 	/// Generates the common header that will be used by all AnKi shaders.
 	static void generateAnkiShaderHeader(ShaderType shaderType, const ShaderCompilerOptions& compilerOptions,
 										 StringRaii& header);
@@ -196,7 +202,7 @@ private:
 		}
 	};
 
-	static constexpr U32 MAX_INCLUDE_DEPTH = 8;
+	static constexpr U32 kMaxIncludeDepth = 8;
 
 	BaseMemoryPool* m_pool = nullptr;
 	StringRaii m_fname;
@@ -221,8 +227,10 @@ private:
 	DynamicArrayRaii<GhostStruct> m_ghostStructs = {m_pool};
 	Bool m_insideStruct = false;
 
+	Bool m_hlsl = false;
+
 	Error parseFile(CString fname, U32 depth);
-	Error parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth);
+	Error parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth, U32 lineNumber);
 	Error parseInclude(const StringRaii* begin, const StringRaii* end, CString line, CString fname, U32 depth);
 	Error parsePragmaMutator(const StringRaii* begin, const StringRaii* end, CString line, CString fname);
 	Error parsePragmaStart(const StringRaii* begin, const StringRaii* end, CString line, CString fname);
@@ -234,6 +242,7 @@ private:
 	Error parsePragmaStructBegin(const StringRaii* begin, const StringRaii* end, CString line, CString fname);
 	Error parsePragmaStructEnd(const StringRaii* begin, const StringRaii* end, CString line, CString fname);
 	Error parsePragmaMember(const StringRaii* begin, const StringRaii* end, CString line, CString fname);
+	Error parsePragmaHlsl(const StringRaii* begin, const StringRaii* end, CString line, CString fname);
 
 	void tokenizeLine(CString line, DynamicArrayRaii<StringRaii>& tokens) const;
 

+ 3 - 4
AnKi/Shaders/Common.glsl

@@ -8,14 +8,13 @@
 #pragma once
 
 #include <AnKi/Shaders/Include/Common.h>
-#include <AnKi/Shaders/TextureFunctions.glsl>
+#if ANKI_GLSL
+#	include <AnKi/Shaders/TextureFunctions.glsl>
+#endif
 
 // Macros
 #define UV_TO_NDC(x_) ((x_)*2.0 - 1.0)
 #define NDC_TO_UV(x_) ((x_)*0.5 + 0.5)
-#define saturate(x_) clamp((x_), 0.0, 1.0)
-#define saturateRp(x) min(x, F32(kMaxF16))
-#define mad(a_, b_, c_) fma((a_), (b_), (c_))
 
 // Techniques
 #define RENDERING_TECHNIQUE_GBUFFER 0

+ 80 - 41
AnKi/Shaders/Functions.glsl

@@ -11,7 +11,7 @@
 Vec3 dither(Vec3 col, F32 C)
 {
 	Vec3 vDither = Vec3(dot(Vec2(171.0, 231.0), gl_FragCoord.xy));
-	vDither.rgb = fract(vDither.rgb / Vec3(103.0, 71.0, 97.0));
+	vDither.rgb = frac(vDither.rgb / Vec3(103.0, 71.0, 97.0));
 
 	col = col * (255.0 / C) + vDither.rgb;
 	col = floor(col) / 255.0;
@@ -23,7 +23,7 @@ Vec3 dither(Vec3 col, F32 C)
 F32 dither(F32 col, F32 C)
 {
 	F32 vDither = dot(Vec2(171.0, 231.0), gl_FragCoord.xy);
-	vDither = fract(vDither / 103.0);
+	vDither = frac(vDither / 103.0);
 
 	col = col * (255.0 / C) + vDither;
 	col = floor(col) / 255.0;
@@ -72,8 +72,8 @@ Vec4 projectPerspective(Vec4 vec, F32 m00, F32 m11, F32 m22, F32 m23)
 // Stolen from shadertoy.com/view/4tyGDD
 Vec4 textureCatmullRom4Samples(texture2D tex, sampler sampl, Vec2 uv, Vec2 texSize)
 {
-	const Vec2 halff = 2.0 * fract(0.5 * uv * texSize - 0.25) - 1.0;
-	const Vec2 f = fract(halff);
+	const Vec2 halff = 2.0 * frac(0.5 * uv * texSize - 0.25) - 1.0;
+	const Vec2 f = frac(halff);
 	const Vec2 sum0 = (2.0 * f - 3.5) * f + 0.5;
 	const Vec2 sum1 = (2.0 * f - 2.5) * f - 0.5;
 	Vec4 w = Vec4(f * sum0 + 1.0, f * sum1);
@@ -87,7 +87,11 @@ Vec4 textureCatmullRom4Samples(texture2D tex, sampler sampl, Vec2 uv, Vec2 texSi
 #endif
 
 // Stolen from shadertoy.com/view/4df3Dn
+#if ANKI_GLSL
 Vec4 textureBicubic(texture2D tex, sampler sampl, Vec2 uv, F32 lod, Vec2 texSize)
+#else
+Vec4 textureBicubic(Texture2D tex, SamplerState sampl, Vec2 uv, F32 lod, Vec2 texSize)
+#endif
 {
 #define w0(a) ((1.0 / 6.0) * ((a) * ((a) * (-(a) + 3.0) - 3.0) + 1.0))
 #define w1(a) ((1.0 / 6.0) * ((a) * (a) * (3.0 * (a)-6.0) + 4.0))
@@ -97,11 +101,15 @@ Vec4 textureBicubic(texture2D tex, sampler sampl, Vec2 uv, F32 lod, Vec2 texSize
 #define g1(a) (w2(a) + w3(a))
 #define h0(a) (-1.0 + w1(a) / (w0(a) + w1(a)))
 #define h1(a) (1.0 + w3(a) / (w2(a) + w3(a)))
-#define texSample(uv) textureLod(tex, sampl, uv, lod)
+#if ANKI_GLSL
+#	define texSample(uv) textureLod(tex, sampl, uv, lod)
+#else
+#	define texSample(uv) tex.SampleLevel(sampl, uv, lod)
+#endif
 
 	uv = uv * texSize + 0.5;
 	const Vec2 iuv = floor(uv);
-	const Vec2 fuv = fract(uv);
+	const Vec2 fuv = frac(uv);
 
 	const F32 g0x = g0(fuv.x);
 	const F32 g1x = g1(fuv.x);
@@ -130,9 +138,10 @@ Vec4 textureBicubic(texture2D tex, sampler sampl, Vec2 uv, F32 lod, Vec2 texSize
 
 F32 rand(Vec2 n)
 {
-	return 0.5 + 0.5 * fract(sin(dot(n, Vec2(12.9898, 78.233))) * 43758.5453);
+	return 0.5 + 0.5 * frac(sin(dot(n, Vec2(12.9898, 78.233))) * 43758.5453);
 }
 
+#if ANKI_GLSL
 Vec4 nearestDepthUpscale(Vec2 uv, texture2D depthFull, texture2D depthHalf, texture2D colorTex,
 						 sampler linearAnyClampSampler, Vec2 linearDepthCf, F32 depthThreshold)
 {
@@ -231,6 +240,7 @@ Vec4 bilateralUpsample(texture2D depthHigh, texture2D depthLow, texture2D colorL
 
 	return sum / normalize;
 }
+#endif
 
 Vec3 getCubemapDirection(const Vec2 norm, const U32 faceIdx)
 {
@@ -250,7 +260,7 @@ Vec2 convertCubeUvs(const Vec3 v, out F32 faceIndex)
 	F32 mag;
 	Vec2 uv;
 
-	if(all(greaterThanEqual(absV.zz, absV.xy)))
+	if(absV.z >= absV.x && absV.z >= absV.y)
 	{
 		faceIndex = (v.z < 0.0) ? 5.0 : 4.0;
 		uv = Vec2((v.z < 0.0) ? -v.x : v.x, -v.y);
@@ -279,7 +289,7 @@ Vec2 convertCubeUvsu(const Vec3 v, out U32 faceIndex)
 	F32 mag;
 	Vec2 uv;
 
-	if(all(greaterThanEqual(absV.zz, absV.xy)))
+	if(absV.z >= absV.x && absV.z >= absV.y)
 	{
 		faceIndex = (v.z < 0.0) ? 5u : 4u;
 		uv = Vec2((v.z < 0.0) ? -v.x : v.x, -v.y);
@@ -301,17 +311,20 @@ Vec2 convertCubeUvsu(const Vec3 v, out U32 faceIndex)
 	return 0.5 / mag * uv + 0.5;
 }
 
+#if ANKI_GLSL
 ANKI_RP Vec3 grayScale(const ANKI_RP Vec3 col)
 {
 	const ANKI_RP F32 grey = (col.r + col.g + col.b) * (1.0 / 3.0);
 	return Vec3(grey);
 }
+#endif
 
 Vec3 saturateColor(const Vec3 col, const F32 factor)
 {
 	const Vec3 lumCoeff = Vec3(0.2125, 0.7154, 0.0721);
-	const Vec3 intensity = Vec3(dot(col, lumCoeff));
-	return mix(intensity, col, factor);
+	const F32 d = dot(col, lumCoeff);
+	const Vec3 intensity = Vec3(d, d, d);
+	return lerp(intensity, col, factor);
 }
 
 Vec3 gammaCorrection(Vec3 gamma, Vec3 col)
@@ -319,6 +332,7 @@ Vec3 gammaCorrection(Vec3 gamma, Vec3 col)
 	return pow(col, 1.0 / gamma);
 }
 
+#if ANKI_GLSL
 // Can use 0.15 for sharpenFactor
 Vec3 readSharpen(texture2D tex, sampler sampl, Vec2 uv, F32 sharpenFactor, Bool detailed)
 {
@@ -348,9 +362,9 @@ Vec3 readErosion(texture2D tex, sampler sampl, const Vec2 uv)
 {
 	Vec3 minValue = textureLod(tex, sampl, uv, 0.0).rgb;
 
-#define ANKI_EROSION(x, y) \
-	col2 = textureLodOffset(sampler2D(tex, sampl), uv, 0.0, IVec2(x, y)).rgb; \
-	minValue = min(col2, minValue);
+#	define ANKI_EROSION(x, y) \
+		col2 = textureLodOffset(sampler2D(tex, sampl), uv, 0.0, IVec2(x, y)).rgb; \
+		minValue = min(col2, minValue);
 
 	Vec3 col2;
 	ANKI_EROSION(1, 1);
@@ -362,10 +376,11 @@ Vec3 readErosion(texture2D tex, sampler sampl, const Vec2 uv)
 	ANKI_EROSION(-1, 0);
 	ANKI_EROSION(0, -1);
 
-#undef ANKI_EROSION
+#	undef ANKI_EROSION
 
 	return minValue;
 }
+#endif
 
 // 5 color heatmap from a factor.
 Vec3 heatmap(const F32 factor)
@@ -375,19 +390,19 @@ Vec3 heatmap(const F32 factor)
 
 	if(intPart < 1.0)
 	{
-		return mix(Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 1.0), fractional);
+		return lerp(Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 1.0), fractional);
 	}
 	else if(intPart < 2.0)
 	{
-		return mix(Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0), fractional);
+		return lerp(Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0), fractional);
 	}
 	else if(intPart < 3.0)
 	{
-		return mix(Vec3(0.0, 1.0, 0.0), Vec3(1.0, 1.0, 0.0), fractional);
+		return lerp(Vec3(0.0, 1.0, 0.0), Vec3(1.0, 1.0, 0.0), fractional);
 	}
 	else
 	{
-		return mix(Vec3(1.0, 1.0, 0.0), Vec3(1.0, 0.0, 0.0), fractional);
+		return lerp(Vec3(1.0, 1.0, 0.0), Vec3(1.0, 0.0, 0.0), fractional);
 	}
 }
 
@@ -425,7 +440,7 @@ Bool incorrectColor(const Vec3 c)
 
 F32 areaElement(const F32 x, const F32 y)
 {
-	return atan(x * y, sqrt(x * x + y * y + 1.0));
+	return atan2(x * y, sqrt(x * x + y * y + 1.0));
 }
 
 // Compute the solid angle of a cube. Solid angle is the area of a sphere when projected into a cubemap. It's also the
@@ -433,7 +448,8 @@ F32 areaElement(const F32 x, const F32 y)
 // http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
 F32 cubeCoordSolidAngle(Vec2 norm, F32 cubeFaceSize)
 {
-	const Vec2 invSize = Vec2(1.0 / cubeFaceSize);
+	const F32 s = 1.0f / cubeFaceSize;
+	const Vec2 invSize = Vec2(s, s);
 	const Vec2 v0 = norm - invSize;
 	const Vec2 v1 = norm + invSize;
 	return areaElement(v0.x, v0.y) - areaElement(v0.x, v1.y) - areaElement(v1.x, v0.y) + areaElement(v1.x, v1.y);
@@ -441,7 +457,7 @@ F32 cubeCoordSolidAngle(Vec2 norm, F32 cubeFaceSize)
 
 // A convenience function to skip out of bounds invocations on post-process compute shaders. Both the arguments should
 // be constexpr.
-#if defined(ANKI_COMPUTE_SHADER)
+#if defined(ANKI_COMPUTE_SHADER) && ANKI_GLSL
 Bool skipOutOfBoundsInvocations(UVec2 workgroupSize, UVec2 globalInvocationCount)
 {
 	if((globalInvocationCount.x % workgroupSize.x) != 0u || (globalInvocationCount.y % workgroupSize.y) != 0u)
@@ -480,7 +496,7 @@ Mat3 rotationFromDirection(Vec3 zAxis)
 #endif
 }
 
-#if defined(ANKI_COMPUTE_SHADER)
+#if defined(ANKI_COMPUTE_SHADER) && ANKI_GLSL
 // See getOptimalGlobalInvocationId8x8Amd
 U32 _ABfiM(U32 src, U32 ins, U32 bits)
 {
@@ -562,11 +578,12 @@ F32 gaussianWeight(F32 s, F32 x)
 	return p;
 }
 
+#if ANKI_GLSL
 Vec4 bilinearFiltering(texture2D tex, sampler nearestSampler, Vec2 uv, F32 lod, Vec2 textureSize)
 {
 	const Vec2 texelSize = 1.0 / textureSize;
 	const Vec2 unnormTexCoord = (uv * textureSize) - 0.5;
-	const Vec2 f = fract(unnormTexCoord);
+	const Vec2 f = frac(unnormTexCoord);
 	const Vec2 snapTexCoord = (floor(unnormTexCoord) + 0.5) / textureSize;
 	const Vec4 s1 = textureLod(tex, nearestSampler, uv, lod);
 	const Vec4 s2 = textureLod(tex, nearestSampler, uv + Vec2(texelSize.x, 0.0), lod);
@@ -574,12 +591,13 @@ Vec4 bilinearFiltering(texture2D tex, sampler nearestSampler, Vec2 uv, F32 lod,
 	const Vec4 s4 = textureLod(tex, nearestSampler, uv + texelSize, lod);
 	return mix(mix(s1, s2, f.x), mix(s3, s4, f.x), f.y);
 }
+#endif
 
 // https://www.shadertoy.com/view/WsfBDf
 Vec3 animateBlueNoise(Vec3 inputBlueNoise, U32 frameIdx)
 {
 	const F32 goldenRatioConjugate = 0.61803398875;
-	return fract(inputBlueNoise + F32(frameIdx % 64u) * goldenRatioConjugate);
+	return frac(inputBlueNoise + F32(frameIdx % 64u) * goldenRatioConjugate);
 }
 
 #if defined(ANKI_FRAGMENT_SHADER)
@@ -594,7 +612,8 @@ F32 computeMipLevel(Vec2 normalizedUvs)
 }
 #endif
 
-#if defined(U64)
+#if ANKI_GLSL
+#	if ANKI_SUPPORTS_64BIT
 /// The regular findLSB in glslang has some issues since it invokes a builtin that is only supposed to be used with
 /// 32bit input. This is an alternative implementation but it expects that the input is not zero.
 I32 findLSB2(U64 v)
@@ -603,13 +622,14 @@ I32 findLSB2(U64 v)
 	const I32 lsb2 = findLSB(U32(v >> 32ul));
 	return (lsb1 >= 0) ? lsb1 : lsb2 + 32;
 }
-#endif
+#	endif
 
 /// Define an alternative findLSB to go in pair with the 64bit version.
 I32 findLSB2(U32 v)
 {
 	return findLSB(v);
 }
+#endif
 
 /// Encode the shading rate to be stored in an SRI. The rates should be power of two, can't be zero and can't exceed 4.
 /// So the possible values are 1,2,4
@@ -620,23 +640,23 @@ U32 encodeVrsRate(UVec2 rateXY)
 
 Vec3 visualizeVrsRate(UVec2 rate)
 {
-	if(rate == UVec2(1u))
+	if(all(rate == UVec2(1u, 1u)))
 	{
 		return Vec3(1.0, 0.0, 0.0);
 	}
-	else if(rate == UVec2(2u, 1u) || rate == UVec2(1u, 2u))
+	else if(all(rate == UVec2(2u, 1u)) || all(rate == UVec2(1u, 2u)))
 	{
 		return Vec3(1.0, 0.5, 0.0);
 	}
-	else if(rate == UVec2(2u) || rate == UVec2(4u, 1u) || rate == UVec2(1u, 4u))
+	else if(all(rate == UVec2(2u, 2u)) || all(rate == UVec2(4u, 1u)) || all(rate == UVec2(1u, 4u)))
 	{
 		return Vec3(1.0, 1.0, 0.0);
 	}
-	else if(rate == UVec2(4u, 2u) || rate == UVec2(2u, 4u))
+	else if(all(rate == UVec2(4u, 2u)) || all(rate == UVec2(2u, 4u)))
 	{
 		return Vec3(0.65, 1.0, 0.0);
 	}
-	else if(rate == UVec2(4u))
+	else if(all(rate == UVec2(4u, 4u)))
 	{
 		return Vec3(0.0, 1.0, 0.0);
 	}
@@ -658,41 +678,60 @@ UVec2 decodeVrsRate(U32 texel)
 /// 3D coordinates to equirectangular 2D coordinates.
 Vec2 equirectangularMapping(Vec3 v)
 {
-	Vec2 uv = Vec2(atan(v.z, v.x), asin(v.y));
-	uv *= vec2(0.1591, 0.3183);
+	Vec2 uv = Vec2(atan2(v.z, v.x), asin(v.y));
+	uv *= Vec2(0.1591, 0.3183);
 	uv += 0.5;
 	return uv;
 }
 
 Vec3 linearToSRgb(Vec3 linearRgb)
 {
-	linearRgb = max(Vec3(6.10352e-5), linearRgb);
-	return min(linearRgb * 12.92, pow(max(linearRgb, 0.00313067), Vec3(1.0 / 2.4)) * 1.055 - 0.055);
+	const F32 a = 6.10352e-5;
+	const F32 b = 1.0 / 2.4;
+	linearRgb = max(Vec3(a, a, a), linearRgb);
+	return min(linearRgb * 12.92, pow(max(linearRgb, 0.00313067), Vec3(b, b, b)) * 1.055 - 0.055);
 }
 
 Vec3 sRgbToLinear(Vec3 sRgb)
 {
+#if ANKI_GLSL
 	const bvec3 cutoff = lessThan(sRgb, Vec3(0.04045));
 	const Vec3 higher = pow((sRgb + 0.055) / 1.055, Vec3(2.4));
 	const Vec3 lower = sRgb / 12.92;
 	return mix(higher, lower, cutoff);
+#else
+	const bool3 cutoff = sRgb < Vec3(0.04045, 0.04045, 0.04045);
+	const Vec3 higher = pow((sRgb + 0.055) / 1.055, Vec3(2.4, 2.4, 2.4));
+	const Vec3 lower = sRgb / 12.92;
+	return lerp(higher, lower, cutoff);
+#endif
 }
 
+#if ANKI_GLSL
 ANKI_RP Vec3 filmGrain(ANKI_RP Vec3 color, Vec2 uv, ANKI_RP F32 strength, ANKI_RP F32 time)
 {
 	const F32 x = (uv.x + 4.0) * (uv.y + 4.0) * time;
 	const F32 grain = 1.0 - (mod((mod(x, 13.0) + 1.0) * (mod(x, 123.0) + 1.0), 0.01) - 0.005) * strength;
 	return color * grain;
 }
+#else
+template<typename TVec3, typename TFloat>
+TVec3 filmGrain(TVec3 color, Vec2 uv, TFloat strength, TFloat time)
+{
+	const TFloat x = (uv.x + 4.0) * (uv.y + 4.0) * time;
+	const TFloat grain = 1.0 - (mod((mod(x, 13.0) + 1.0) * (mod(x, 123.0) + 1.0), 0.01) - 0.005) * strength;
+	return color * grain;
+}
+#endif
 
 /// Sin approximation: https://www.desmos.com/calculator/svgcjfskne
-ANKI_RP F32 fastSin(ANKI_RP F32 x)
+F32 fastSin(F32 x)
 {
-	const ANKI_RP F32 k2Pi = 2.0 * kPi;
-	const ANKI_RP F32 kPiOver2 = kPi / 2.0;
+	const F32 k2Pi = 2.0 * kPi;
+	const F32 kPiOver2 = kPi / 2.0;
 
 	x = (x + kPiOver2) / (k2Pi) + 0.75;
-	x = fract(x);
+	x = frac(x);
 	x = x * 2.0 - 1.0;
 	x = x * abs(x) - x;
 	x *= 4.0;
@@ -700,7 +739,7 @@ ANKI_RP F32 fastSin(ANKI_RP F32 x)
 }
 
 /// Cos approximation
-ANKI_RP F32 fastCos(ANKI_RP F32 x)
+F32 fastCos(F32 x)
 {
 	return fastSin(x + kPi / 2.0);
 }

+ 131 - 6
AnKi/Shaders/Include/Common.h

@@ -10,6 +10,10 @@
 
 #	include <AnKi/Math.h>
 
+#	define ANKI_HLSL 0
+#	define ANKI_GLSL 0
+#	define ANKI_CPP 1
+
 #	define ANKI_BEGIN_NAMESPACE namespace anki {
 #	define ANKI_END_NAMESPACE }
 #	define ANKI_SHADER_FUNC_INLINE inline
@@ -17,9 +21,7 @@
 #	define ANKI_SHADER_STATIC_ASSERT(cond_) static_assert(cond_)
 
 ANKI_BEGIN_NAMESPACE
-
 using Address = U64;
-
 using ScalarVec4 = Array<F32, 4>;
 using ScalarMat3x4 = Array<F32, 12>;
 using ScalarMat4 = Array<F32, 16>;
@@ -27,8 +29,121 @@ ANKI_END_NAMESPACE
 
 #	define ANKI_RP
 
+//! == HLSL ============================================================================================================
+#elif defined(__HLSL_VERSION)
+#	define ANKI_HLSL 1
+#	define ANKI_GLSL 0
+#	define ANKI_CPP 0
+
+#	define ANKI_BEGIN_NAMESPACE
+#	define ANKI_END_NAMESPACE
+#	define ANKI_SHADER_FUNC_INLINE
+
+#	define ANKI_SHADER_STATIC_ASSERT(cond_)
+
+#	define constexpr static const
+
+typedef float F32;
+constexpr uint kSizeof_F32 = 4u;
+typedef float2 Vec2;
+constexpr uint kSizeof_Vec2 = 8u;
+typedef float3 Vec3;
+constexpr uint kSizeof_Vec3 = 12u;
+typedef float4 Vec4;
+constexpr uint kSizeof_Vec4 = 16u;
+
+typedef float16_t F16;
+constexpr uint kSizeof_F16 = 2u;
+typedef float16_t2 HVec2;
+constexpr uint kSizeof_HVec2 = 4u;
+typedef float16_t3 HVec3;
+constexpr uint kSizeof_HVec3 = 6u;
+typedef float16_t4 HVec4;
+constexpr uint kSizeof_HVec4 = 8u;
+
+typedef uint16_t U16;
+constexpr uint kSizeof_U16 = 2u;
+typedef uint16_t2 U16Vec2;
+constexpr uint kSizeof_U16Vec2 = 4u;
+typedef uint16_t3 U16Vec3;
+constexpr uint kSizeof_U16Vec3 = 6u;
+typedef uint16_t4 U16Vec4;
+constexpr uint kSizeof_U16Vec4 = 8u;
+
+typedef int16_t I16;
+constexpr uint kSizeof_I16 = 2u;
+typedef int16_t2 I16Vec2;
+constexpr uint kSizeof_I16Vec2 = 4u;
+typedef int16_t3 I16Vec3;
+constexpr uint kSizeof_I16Vec3 = 6u;
+typedef int16_t4 I16Vec4;
+constexpr uint kSizeof_I16Vec4 = 8u;
+
+typedef uint U32;
+constexpr uint kSizeof_U32 = 4u;
+typedef uint32_t2 UVec2;
+constexpr uint kSizeof_UVec2 = 8u;
+typedef uint32_t3 UVec3;
+constexpr uint kSizeof_UVec3 = 12u;
+typedef uint32_t4 UVec4;
+constexpr uint kSizeof_UVec4 = 16u;
+
+typedef int I32;
+constexpr uint kSizeof_I32 = 4u;
+typedef int32_t2 IVec2;
+constexpr uint kSizeof_IVec2 = 8u;
+typedef int32_t3 IVec3;
+constexpr uint kSizeof_IVec3 = 12u;
+typedef int32_t4 IVec4;
+constexpr uint kSizeof_IVec4 = 16u;
+
+#	if ANKI_SUPPORTS_64BIT
+typedef uint64_t U64;
+constexpr uint kSizeof_U64 = 8u;
+typedef uint64_t2 U64Vec2;
+constexpr uint kSizeof_U64Vec2 = 16u;
+typedef uint64_t3 U64Vec3;
+constexpr uint kSizeof_U64Vec3 = 24u;
+typedef uint64_t4 U64Vec4;
+constexpr uint kSizeof_U64Vec4 = 32u;
+
+typedef int64_t I64;
+constexpr uint kSizeof_I64 = 8u;
+typedef int64_t2 I64Vec2;
+constexpr uint kSizeof_I64Vec2 = 16u;
+typedef int64_t3 I64Vec3;
+constexpr uint kSizeof_I64Vec3 = 24u;
+typedef int64_t4 I64Vec4;
+constexpr uint kSizeof_I64Vec4 = 32u;
+#	endif
+
+typedef float3x3 Mat3;
+
+typedef bool Bool;
+
+#	if 0
+typedef min16float RF32;
+typedef min16float2 RVec2;
+typedef min16float3 RVec3;
+typedef min16float4 RVec4;
+#	endif
+
+constexpr F32 kEpsilonf = 0.000001f;
+constexpr F16 kEpsilonhf = (F16)0.0001f; // Divisions by this should be OK according to http://weitz.de/ieee/
+
+constexpr U32 kMaxU32 = 0xFFFFFFFFu;
+constexpr F32 kMaxF32 = 3.402823e+38;
+constexpr F16 kMaxF16 = (F16)65504.0;
+constexpr F16 kMinF16 = (F16)0.00006104;
+
+constexpr F32 kPi = 3.14159265358979323846f;
+
 //! == GLSL ============================================================================================================
 #else
+#	define ANKI_HLSL 0
+#	define ANKI_GLSL 1
+#	define ANKI_CPP 0
+
 #	define ANKI_BEGIN_NAMESPACE
 #	define ANKI_END_NAMESPACE
 #	define ANKI_SHADER_FUNC_INLINE
@@ -281,12 +396,19 @@ Vec4 pow(Vec4 a, F32 b)
 {
 	return pow(a, Vec4(b));
 }
-#endif
 
-//! == Consts ==========================================================================================================
-ANKI_BEGIN_NAMESPACE
+Bool all(Bool b)
+{
+	return b;
+}
+
+#	define saturate(x_) clamp((x_), 0.0, 1.0)
+#	define saturateRp(x) min(x, F32(kMaxF16))
+#	define mad(a_, b_, c_) fma((a_), (b_), (c_))
+#	define frac(x) fract(x)
+#	define lerp(a, b, t) mix(a, b, t)
+#	define atan2(x, y) atan(x, y)
 
-#if !defined(__cplusplus)
 constexpr F32 kEpsilonf = 0.000001f;
 constexpr F16 kEpsilonhf = 0.0001hf; // Divisions by this should be OK according to http://weitz.de/ieee/
 constexpr ANKI_RP F32 kEpsilonRp = F32(kEpsilonhf);
@@ -299,6 +421,9 @@ constexpr F16 kMinF16 = 0.00006104hf;
 constexpr F32 kPi = 3.14159265358979323846f;
 #endif
 
+//! == Consts ==========================================================================================================
+ANKI_BEGIN_NAMESPACE
+
 /// The renderer will group drawcalls into instances up to this number.
 constexpr U32 kMaxInstanceCount = 64u;
 

+ 1 - 1
AnKi/Util/String.h

@@ -889,7 +889,7 @@ public:
 		destroy();
 		if(!b.isEmpty())
 		{
-			create(b.getBegin(), b.getEnd() - 1);
+			create(b.getBegin(), b.getEnd());
 		}
 		return *this;
 	}

+ 1 - 1
Tests/Gr/GrCommon.h

@@ -21,7 +21,7 @@ inline ShaderPtr createShader(CString src, ShaderType type, GrManager& gr,
 	header.append(src);
 	DynamicArrayRaii<U8> spirv(&pool);
 	StringRaii errorLog(&pool);
-	ANKI_TEST_EXPECT_NO_ERR(compilerGlslToSpirv(header, type, pool, spirv, errorLog));
+	ANKI_TEST_EXPECT_NO_ERR(compileGlslToSpirv(header, type, pool, spirv, errorLog));
 
 	ShaderInitInfo initInf(type, spirv);
 	initInf.m_constValues = specVals;

+ 5 - 5
Tools/Shader/ShaderProgramBinaryDumpMain.cpp

@@ -14,18 +14,18 @@ Options:
 -stats : Print performance statistics for all shaders. By default it doesn't
 )";
 
-static Error parseCommandLineArgs(int argc, char** argv, Bool& dumpStats, StringRaii& filename)
+static Error parseCommandLineArgs(WeakArray<char*> argv, Bool& dumpStats, StringRaii& filename)
 {
 	// Parse config
-	if(argc < 2)
+	if(argv.getSize() < 2)
 	{
 		return Error::kUserData;
 	}
 
 	dumpStats = false;
-	filename = argv[argc - 1];
+	filename = argv[argv.getSize() - 1];
 
-	for(I i = 1; i < argc - 1; i++)
+	for(U32 i = 1; i < argv.getSize() - 1; i++)
 	{
 		if(strcmp(argv[i], "-stats") == 0)
 		{
@@ -194,7 +194,7 @@ int main(int argc, char** argv)
 	HeapMemoryPool pool(allocAligned, nullptr);
 	StringRaii filename(&pool);
 	Bool dumpStats;
-	if(parseCommandLineArgs(argc, argv, dumpStats, filename))
+	if(parseCommandLineArgs(WeakArray<char*>(argv, argc), dumpStats, filename))
 	{
 		ANKI_LOGE(kUsage, argv[0]);
 		return 1;