Browse Source

Move to DX coordinate system. What a pain...

Panagiotis Christopoulos Charitos 1 year ago
parent
commit
bf7937f356

+ 1 - 0
AnKi/Core/GpuMemory/GpuSceneBuffer.h

@@ -8,6 +8,7 @@
 #include <AnKi/Core/Common.h>
 #include <AnKi/Gr/Utils/SegregatedListsGpuMemoryPool.h>
 #include <AnKi/Resource/ShaderProgramResource.h>
+#include <AnKi/Gr/GrManager.h>
 
 namespace anki {
 

+ 1 - 0
AnKi/Core/GpuMemory/RebarTransientMemoryPool.h

@@ -7,6 +7,7 @@
 
 #include <AnKi/Core/Common.h>
 #include <AnKi/Gr/Buffer.h>
+#include <AnKi/Gr/GrManager.h>
 
 namespace anki {
 

+ 2 - 16
AnKi/Gr/BackendCommon/GraphicsStateTracker.h

@@ -273,12 +273,7 @@ public:
 		}
 	}
 
-	void beginRenderPass(ConstWeakArray<Format> colorFormats, Format depthStencilFormat, UVec2 rtsSize
-#if ANKI_GR_BACKEND_VULKAN
-						 ,
-						 Bool rendersToSwapchain
-#endif
-	)
+	void beginRenderPass(ConstWeakArray<Format> colorFormats, Format depthStencilFormat, UVec2 rtsSize)
 	{
 		m_staticState.m_misc.m_colorRtFormats.fill(Format::kNone);
 		m_staticState.m_misc.m_colorRtMask.unsetAll();
@@ -292,11 +287,7 @@ public:
 
 		m_hashes.m_misc = 0; // Always mark it dirty because calling beginRenderPass is a rare occurance and we want to avoid extra checks
 
-		if(m_rtsSize != rtsSize
-#if ANKI_GR_BACKEND_VULKAN
-		   || m_staticState.m_misc.m_rendersToSwapchain != rendersToSwapchain
-#endif
-		)
+		if(m_rtsSize != rtsSize)
 		{
 			m_rtsSize = rtsSize;
 
@@ -304,10 +295,6 @@ public:
 			m_dynState.m_scissorDirty = true;
 			m_dynState.m_viewportDirty = true;
 		}
-
-#if ANKI_GR_BACKEND_VULKAN
-		m_staticState.m_misc.m_rendersToSwapchain = rendersToSwapchain;
-#endif
 	}
 
 	void bindShaderProgram(ShaderProgram* prog)
@@ -558,7 +545,6 @@ private:
 			BitSet<kMaxColorRenderTargets> m_colorRtMask = {false};
 
 #if ANKI_GR_BACKEND_VULKAN
-			Bool m_rendersToSwapchain = false;
 			Bool m_pipelineStatisticsEnabled = false;
 #endif
 		} m_misc;

+ 2 - 1
AnKi/Gr/D3D/D3DDescriptor.h

@@ -194,7 +194,8 @@ public:
 	U32 getBindlessIndex(const DescriptorHeapHandle& handle) const
 	{
 		handle.validate();
-		ANKI_ASSERT(handle.m_cpuHandle.ptr == m_gpuPersistent.m_cbvSrvUav.m_cpuHeapStart.ptr);
+		ANKI_ASSERT(handle.m_heapCpuStart.ptr == m_gpuPersistent.m_cbvSrvUav.m_cpuHeapStart.ptr);
+		ANKI_ASSERT(handle.m_heapGpuStart.ptr == m_gpuPersistent.m_cbvSrvUav.m_gpuHeapStart.ptr);
 		const PtrSize idx = (handle.m_cpuHandle.ptr - m_gpuPersistent.m_cbvSrvUav.m_cpuHeapStart.ptr) / m_gpuPersistent.m_cbvSrvUav.m_descriptorSize;
 		return U32(idx);
 	}

+ 2 - 0
AnKi/Gr/D3D/D3DTexture.cpp

@@ -33,6 +33,8 @@ U32 Texture::getOrCreateBindlessTextureIndex(const TextureSubresourceDesc& subre
 	if(view.m_bindlessIndex == kMaxU32)
 	{
 		view.m_bindlessHandle = DescriptorFactory::getSingleton().allocatePersistent(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, true);
+		getDevice().CopyDescriptorsSimple(1, view.m_bindlessHandle.getCpuOffset(), view.m_handle.getCpuOffset(),
+										  D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
 		view.m_bindlessIndex = DescriptorFactory::getSingleton().getBindlessIndex(view.m_bindlessHandle);
 	}
 

+ 2 - 7
AnKi/Gr/Vulkan/VkCommandBuffer.cpp

@@ -468,7 +468,6 @@ void CommandBuffer::beginRenderPass(ConstWeakArray<RenderTarget> colorRts, Rende
 	// Set the render area
 	ANKI_ASSERT(minx < fbWidth && miny < fbHeight);
 
-	const Bool flipViewport = drawsToSwapchain;
 	const U32 maxx = min<U32>(minx + width, fbWidth);
 	const U32 maxy = min<U32>(miny + height, fbHeight);
 	width = maxx - minx;
@@ -476,16 +475,12 @@ void CommandBuffer::beginRenderPass(ConstWeakArray<RenderTarget> colorRts, Rende
 	ANKI_ASSERT(minx + width <= fbWidth && miny + height <= fbHeight);
 
 	info.renderArea.offset.x = minx;
-	if(flipViewport)
-	{
-		ANKI_ASSERT(height <= fbHeight);
-	}
-	info.renderArea.offset.y = (flipViewport) ? fbHeight - (miny + height) : miny;
+	info.renderArea.offset.y = miny;
 	info.renderArea.extent.width = width;
 	info.renderArea.extent.height = height;
 
 	// State bookkeeping
-	self.m_graphicsState.beginRenderPass({colorFormats.getBegin(), colorRts.getSize()}, dsFormat, UVec2(fbWidth, fbHeight), drawsToSwapchain);
+	self.m_graphicsState.beginRenderPass({colorFormats.getBegin(), colorRts.getSize()}, dsFormat, UVec2(fbWidth, fbHeight));
 	if(drawsToSwapchain)
 	{
 		self.m_renderedToDefaultFb = true;

+ 7 - 18
AnKi/Gr/Vulkan/VkGraphicsState.cpp

@@ -14,7 +14,7 @@ namespace anki {
 static NumericCVar<PtrSize> g_diskShaderCacheMaxSizeCVar(CVarSubsystem::kGr, "DiskShaderCacheMaxSize", 128_MB, 1_MB, 1_GB,
 														 "Max size of the pipeline cache file");
 
-static VkViewport computeViewport(U32* viewport, U32 fbWidth, U32 fbHeight, Bool flipvp)
+static VkViewport computeViewport(U32* viewport, U32 fbWidth, U32 fbHeight)
 {
 	const U32 minx = viewport[0];
 	const U32 miny = viewport[1];
@@ -24,17 +24,12 @@ static VkViewport computeViewport(U32* viewport, U32 fbWidth, U32 fbHeight, Bool
 	ANKI_ASSERT(minx + width <= fbWidth);
 	ANKI_ASSERT(miny + height <= fbHeight);
 
-	const VkViewport s = {.x = F32(minx),
-						  .y = (flipvp) ? F32(fbHeight - miny) : F32(miny), // Move to the bottom
-						  .width = F32(width),
-						  .height = (flipvp) ? -F32(height) : F32(height),
-						  .minDepth = 0.0f,
-						  .maxDepth = 1.0f};
+	const VkViewport s = {.x = F32(minx), .y = F32(height + miny), .width = F32(width), .height = -F32(height), .minDepth = 0.0f, .maxDepth = 1.0f};
 
 	return s;
 }
 
-static VkRect2D computeScissor(U32* scissor, U32 fbWidth, U32 fbHeight, Bool flipvp)
+static VkRect2D computeScissor(U32* scissor, U32 fbWidth, U32 fbHeight)
 {
 	const U32 minx = scissor[0];
 	const U32 miny = scissor[1];
@@ -48,7 +43,7 @@ static VkRect2D computeScissor(U32* scissor, U32 fbWidth, U32 fbHeight, Bool fli
 	out.extent.width = width;
 	out.extent.height = height;
 	out.offset.x = minx;
-	out.offset.y = (flipvp) ? (fbHeight - (miny + height)) : miny;
+	out.offset.y = fbHeight - (miny + height);
 
 	return out;
 }
@@ -140,20 +135,14 @@ void GraphicsPipelineFactory::flushState(GraphicsStateTracker& state, VkCommandB
 	{
 		ANKI_ASSERT(dynState.m_viewport[2] != 0 && dynState.m_viewport[3] != 0);
 		dynState.m_viewportDirty = false;
-
-		const Bool flipVp = staticState.m_misc.m_rendersToSwapchain;
-		const VkViewport vp = computeViewport(dynState.m_viewport.getBegin(), state.m_rtsSize.x(), state.m_rtsSize.y(), flipVp);
-
+		const VkViewport vp = computeViewport(dynState.m_viewport.getBegin(), state.m_rtsSize.x(), state.m_rtsSize.y());
 		vkCmdSetViewport(cmdb, 0, 1, &vp);
 	}
 
 	if(dynState.m_scissorDirty)
 	{
 		dynState.m_scissorDirty = false;
-
-		const Bool flipVp = staticState.m_misc.m_rendersToSwapchain;
-		const VkRect2D rect = computeScissor(dynState.m_scissor.getBegin(), state.m_rtsSize.x(), state.m_rtsSize.y(), flipVp);
-
+		const VkRect2D rect = computeScissor(dynState.m_scissor.getBegin(), state.m_rtsSize.x(), state.m_rtsSize.y());
 		vkCmdSetScissor(cmdb, 0, 1, &rect);
 	}
 
@@ -258,7 +247,7 @@ void GraphicsPipelineFactory::flushState(GraphicsStateTracker& state, VkCommandB
 	rastCi.rasterizerDiscardEnable = false;
 	rastCi.polygonMode = convertFillMode(staticState.m_rast.m_fillMode);
 	rastCi.cullMode = convertCullMode(staticState.m_rast.m_cullMode);
-	rastCi.frontFace = (!staticState.m_misc.m_rendersToSwapchain) ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; // For viewport flip
+	rastCi.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
 	rastCi.depthBiasEnable = staticState.m_rast.m_depthBiasEnabled;
 	rastCi.lineWidth = 1.0f;
 	ci.pRasterizationState = &rastCi;

+ 2 - 2
AnKi/Renderer/IndirectDiffuseProbes.cpp

@@ -402,7 +402,7 @@ void IndirectDiffuseProbes::populateRenderGraph(RenderingContext& rctx)
 
 					if(doShadows)
 					{
-						const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+						const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
 						dsInfo.m_dirLightMatrix = biasMat4 * cascadeViewProjMat;
 					}
 					else
@@ -468,7 +468,7 @@ void IndirectDiffuseProbes::populateRenderGraph(RenderingContext& rctx)
 				U32 x, y, z;
 				unflatten3dArrayIndex(probeToRefresh->getCellCountsPerDimension().x(), probeToRefresh->getCellCountsPerDimension().y(),
 									  probeToRefresh->getCellCountsPerDimension().z(), cellIdx, x, y, z);
-				consts.m_volumeTexel = IVec3(x, y, z);
+				consts.m_volumeTexel = IVec3(x, probeToRefresh->getCellCountsPerDimension().y() - y - 1, z);
 
 				consts.m_nextTexelOffsetInU = probeToRefresh->getCellCountsPerDimension().x();
 				cmdb.setFastConstants(&consts, sizeof(consts));

+ 1 - 1
AnKi/Renderer/ProbeReflections.cpp

@@ -385,7 +385,7 @@ void ProbeReflections::populateRenderGraph(RenderingContext& rctx)
 				dsInfo.m_viewport = UVec4(0, 0, m_lightShading.m_tileSize, m_lightShading.m_tileSize);
 				dsInfo.m_effectiveShadowDistance = probeToRefresh->getShadowsRenderRadius();
 
-				const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+				const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
 				dsInfo.m_dirLightMatrix = biasMat4 * cascadeViewProjMat;
 
 				dsInfo.m_visibleLightsBuffer = visResult;

+ 1 - 1
AnKi/Renderer/ShadowMapping.cpp

@@ -135,7 +135,7 @@ Mat4 ShadowMapping::createSpotLightTextureMatrix(const UVec4& viewport) const
 	ANKI_ASSERT(viewport[2] == viewport[3]);
 	const F32 sizeTextureSpace = F32(viewport[2]) / atlasSize;
 
-	const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+	const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
 
 	return Mat4(sizeTextureSpace, 0.0f, 0.0f, uv.x(), 0.0f, sizeTextureSpace, 0.0f, uv.y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)
 		   * biasMat4;

+ 1 - 1
AnKi/Renderer/Utils/GpuVisibility.cpp

@@ -549,7 +549,7 @@ void GpuVisibility::populateRenderGraphInternal(Bool distanceBased, BaseGpuVisib
 		NonGraphicsRenderPass& pass = rgraph.newNonGraphicsRenderPass(generateTempPassName("GPU vis zero: %s", in.m_passesName.cstr()));
 		pass.newBufferDependency(zeroMemDep, BufferUsageBit::kCopyDestination);
 
-		pass.setWork([stage1Mem, stage2Mem, stage3Mem, this](RenderPassWorkContext& rpass) {
+		pass.setWork([stage1Mem, stage2Mem, stage3Mem](RenderPassWorkContext& rpass) {
 			CommandBuffer& cmdb = *rpass.m_commandBuffer;
 
 			constexpr Bool debugZeroing = false; // For debugging purposes zero everything

+ 1 - 1
AnKi/Resource/ImageResource.cpp

@@ -113,7 +113,7 @@ Error ImageResource::load(const ResourceFilename& filename, Bool async)
 			init.m_format = Format::kR8G8B8_Unorm;
 			break;
 		case ImageBinaryDataCompression::kS3tc:
-			init.m_format = Format::kBC1_Rgb_Unorm_Block;
+			init.m_format = Format::kBC1_Rgba_Unorm_Block;
 			break;
 		case ImageBinaryDataCompression::kAstc:
 			if(loader.getAstcBlockSize() == UVec2(4u))

+ 1 - 1
AnKi/Scene/Components/DecalComponent.cpp

@@ -59,7 +59,7 @@ Error DecalComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 		const Mat4 projMat = Mat4::calculateOrthographicProjectionMatrix(halfBoxSize.x(), -halfBoxSize.x(), halfBoxSize.y(), -halfBoxSize.y(),
 																		 kClusterObjectFrustumNearPlane, m_boxSize.z());
 
-		const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+		const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
 
 		m_biasProjViewMat = biasMat4 * projMat * viewMat;
 

+ 1 - 1
AnKi/Scene/Components/LightComponent.cpp

@@ -171,7 +171,7 @@ Error LightComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 
 		if(reallyShadow)
 		{
-			const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+			const Mat4 biasMat4(0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
 			const Mat4 proj = Mat4::calculatePerspectiveProjectionMatrix(m_spot.m_outerAngle, m_spot.m_outerAngle, kClusterObjectFrustumNearPlane,
 																		 m_spot.m_distance);
 			const Mat4 uvToAtlas(m_shadowAtlasUvViewports[0].z(), 0.0f, 0.0f, m_shadowAtlasUvViewports[0].x(), 0.0f, m_shadowAtlasUvViewports[0].w(),

+ 6 - 6
AnKi/Scene/Frustum.cpp

@@ -8,12 +8,12 @@
 
 namespace anki {
 
-Array<Mat3x4, 6> Frustum::m_omnidirectionalRotations = {Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, -kPi / 2.0f, 0.0f)) * Mat3(Euler(0.0f, 0.0f, kPi))),
-														Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, kPi / 2.0f, 0.0f)) * Mat3(Euler(0.0f, 0.0f, kPi))),
-														Mat3x4(Vec3(0.0f), Mat3(Euler(kPi / 2.0f, 0.0f, 0.0f))),
-														Mat3x4(Vec3(0.0f), Mat3(Euler(-kPi / 2.0f, 0.0f, 0.0f))),
-														Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, kPi, 0.0f)) * Mat3(Euler(0.0f, 0.0f, kPi))),
-														Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, 0.0f, kPi)))};
+Array<Mat3x4, 6> Frustum::m_omnidirectionalRotations = {Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, -kPi / 2.0f, 0.0f))), // +x
+														Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, kPi / 2.0f, 0.0f))), // -x
+														Mat3x4(Vec3(0.0f), Mat3(Euler(kPi / 2.0f, 0.0f, 0.0f))), // +y
+														Mat3x4(Vec3(0.0f), Mat3(Euler(-kPi / 2.0f, 0.0f, 0.0f))), // -y
+														Mat3x4(Vec3(0.0f), Mat3::getIdentity()), // -z
+														Mat3x4(Vec3(0.0f), Mat3(Euler(0.0f, kPi, 0.0f)))}; // +z
 
 Frustum::Frustum()
 {

+ 2 - 2
AnKi/Shaders/ApplyIrradianceToReflection.ankiprog

@@ -23,7 +23,7 @@ RWTexture2D<RVec4> g_cubeTex[6u] : register(u0); // RWTexture2D because there is
 	const UVec2 dispatchThreadId = min(svDispatchThreadId.xy, cubeSizeu - 1u);
 
 	const Vec2 uv = (Vec2(dispatchThreadId) + 0.5) / Vec2(cubeSize);
-	const Vec3 sampleUv = getCubemapDirection(uvToNdc(uv), faceIdx);
+	const Vec3 sampleUv = getCubemapDirection(uv, faceIdx);
 
 	// Read the gbuffer
 	GbufferInfo gbuffer = (GbufferInfo)0;
@@ -33,7 +33,7 @@ RWTexture2D<RVec4> g_cubeTex[6u] : register(u0); // RWTexture2D because there is
 
 	// Sample
 	const RVec3 irradiance = sampleAmbientDice(g_irradianceDice[0u].xyz, g_irradianceDice[1u].xyz, g_irradianceDice[2u].xyz, g_irradianceDice[3u].xyz,
-											   g_irradianceDice[4u].xyz, g_irradianceDice[5u].xyz, gbuffer.m_normal);
+											   g_irradianceDice[4u].xyz, g_irradianceDice[5u].xyz, gbuffer.m_normal * Vec3(1.0, 1.0, -1.0));
 
 	// Compute the indirect term
 	const RVec3 indirect = gbuffer.m_diffuse * irradiance;

+ 8 - 4
AnKi/Shaders/Common.hlsl

@@ -72,15 +72,19 @@
 ANKI_BINDLESS3()
 
 template<typename T>
-T uvToNdc(T x)
+T uvToNdc(T uv)
 {
-	return x * 2.0f - 1.0f;
+	T ndc = uv * 2.0f - 1.0f;
+	ndc.y *= -1.0f;
+	return ndc;
 }
 
 template<typename T>
-T ndcToUv(T x)
+T ndcToUv(T ndc)
 {
-	return x * 0.5f + 0.5f;
+	T uv = ndc * 0.5f + 0.5f;
+	uv.y = 1.0f - uv.y;
+	return uv;
 }
 
 // Define min3, max3, min4, max4 functions

+ 41 - 51
AnKi/Shaders/Functions.hlsl

@@ -225,9 +225,16 @@ Vec4 bilateralUpsample(Texture2D depthHigh, Texture2D depthLow, Texture2D colorL
 	return sum / normalize;
 }
 
-/// Compute the UV that can be passed to a cube texture. The norm is in [-1, 1].
-Vec3 getCubemapDirection(const Vec2 norm, const U32 faceIdx)
-{
+/// Compute the UV that can be passed to a cube texture.
+/// (0.5, 0) returns {1, 0, 0}
+/// (0.5, 1) returns {-1, 0, 0}
+/// (0.5, 2) returns {0, 1, 0}
+/// (0.5, 3) returns {0, -1, 0}
+/// (0.5, 4) returns {0, 0, 1}
+/// (0.5, 5) returns {0, 0, -1}
+Vec3 getCubemapDirection(const Vec2 uv, const U32 faceIdx)
+{
+	const Vec2 norm = uv * 2.0 - 1.0;
 	Vec3 zDir = Vec3((faceIdx <= 1u) ? 1 : 0, (faceIdx & 2u) >> 1u, (faceIdx & 4u) >> 2u);
 	zDir *= (((faceIdx & 1u) == 1u) ? -1.0 : 1.0);
 	const Vec3 yDir = (faceIdx == 2u) ? Vec3(0.0, 0.0, 1.0) : (faceIdx == 3u) ? Vec3(0.0, 0.0, -1.0) : Vec3(0.0, -1.0, 0.0);
@@ -235,62 +242,44 @@ Vec3 getCubemapDirection(const Vec2 norm, const U32 faceIdx)
 	return normalize(norm.x * xDir + norm.y * yDir + zDir);
 }
 
-// Convert 3D cubemap coordinates to 2D plus face index. v doesn't need to be normalized.
-Vec2 convertCubeUvs(const Vec3 v, out F32 faceIndex)
-{
-	const Vec3 absV = abs(v);
-	F32 mag;
-	Vec2 uv;
-
-	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);
-		mag = absV.z;
-	}
-	else if(absV.y >= absV.x)
-	{
-		faceIndex = (v.y < 0.0) ? 3.0 : 2.0;
-		uv = Vec2(v.x, (v.y < 0.0) ? -v.z : v.z);
-		mag = absV.y;
-	}
-	else
-	{
-		faceIndex = (v.x < 0.0) ? 1.0 : 0.0;
-		uv = Vec2((v.x < 0.0) ? v.z : -v.z, -v.y);
-		mag = absV.x;
-	}
-
-	return 0.5 / mag * uv + 0.5;
-}
-
-// Same as convertCubeUvs but it returns the faceIndex as unsigned I32.
-Vec2 convertCubeUvsu(const Vec3 v, out U32 faceIndex)
-{
-	const Vec3 absV = abs(v);
-	F32 mag;
-	Vec2 uv;
-
-	if(absV.z >= absV.x && absV.z >= absV.y)
+/// Convert 3D cubemap coordinates to 2D plus face index. vec doesn't need to be normalized. It's the opposite of getCubemapDirection.
+/// This is the exact same thing AMD is doing (v_cubeid and co) with a small difference. AMD for some reason adds 1.5 to the final result instead of
+/// 0.5.
+template<typename T>
+Vec2 convertCubeUvs(const Vec3 vec, out T faceIndex)
+{
+	F32 u, v;
+	const F32 x = vec.x;
+	const F32 y = vec.y;
+	const F32 z = vec.z;
+	const F32 ax = abs(vec.x);
+	const F32 ay = abs(vec.y);
+	const F32 az = abs(vec.z);
+	F32 major;
+
+	if(az >= ax && az >= ay)
 	{
-		faceIndex = (v.z < 0.0) ? 5u : 4u;
-		uv = Vec2((v.z < 0.0) ? -v.x : v.x, -v.y);
-		mag = absV.z;
+		major = az;
+		u = (z < 0.0f) ? -x : x;
+		v = -y;
+		faceIndex = (z < 0.0f) ? (T)5 : (T)4;
 	}
-	else if(absV.y >= absV.x)
+	else if(ay >= ax)
 	{
-		faceIndex = (v.y < 0.0) ? 3u : 2u;
-		uv = Vec2(v.x, (v.y < 0.0) ? -v.z : v.z);
-		mag = absV.y;
+		major = ay;
+		u = x;
+		v = (y < 0.0f) ? -z : z;
+		faceIndex = (y < 0.0f) ? (T)3 : (T)2;
 	}
 	else
 	{
-		faceIndex = (v.x < 0.0) ? 1u : 0u;
-		uv = Vec2((v.x < 0.0) ? v.z : -v.z, -v.y);
-		mag = absV.x;
+		major = ax;
+		u = (x < 0.0f) ? z : -z;
+		v = -y;
+		faceIndex = (x < 0.0f) ? (T)1 : (T)0;
 	}
 
-	return 0.5 / mag * uv + 0.5;
+	return Vec2(u, v) / (major * 2.0f) + 0.5f;
 }
 
 template<typename T>
@@ -703,6 +692,7 @@ vector<T, 3> filmGrain(vector<T, 3> color, Vec2 uv, T strength, F32 time)
 
 /// Perturb normal, see http://www.thetenthplanet.de/archives/1180
 /// Does normal mapping in the fragment shader. It assumes that green is up. viewDir and geometricNormal need to be in the same space.
+/// viewDir is the -(eye - vertexPos)
 RVec3 perturbNormal(RVec3 tangentNormal, Vec3 viewDir, Vec2 uv, Vec3 geometricNormal)
 {
 	tangentNormal.y = -tangentNormal.y; // Green is up

+ 2 - 6
AnKi/Shaders/GBufferGeneric.ankiprog

@@ -59,10 +59,6 @@
 #define SW_MESHLETS (ANKI_TECHNIQUE_GBufferSwMeshletRendering || ANKI_TECHNIQUE_ShadowsSwMeshletRendering)
 
 #define VISUALIZE_MESHLETS (0 && GBUFFER)
-#define MESHLET_BACKFACE_CULLING 0
-#define MESHLET_OUTSIDE_OF_SCREEN_CULLING 1
-#define MESHLET_NO_SAMPLING_POINT_CULLING 1
-#define MESHLET_HZB_CULLING 1
 #define PRIMITIVE_BACKFACE_CULLING 1
 #define PRIMITIVE_NO_SAMPLING_POINTS_CULLING 1
 #define PRIMITIVE_ANY_CULLING (PRIMITIVE_BACKFACE_CULLING || PRIMITIVE_NO_SAMPLING_POINTS_CULLING)
@@ -414,7 +410,7 @@ main(U32 svGroupId : SV_GROUPID, U32 svGroupIndex : SV_GROUPINDEX, out vertices
 			const Vec2 eb = c - a;
 			const Vec2 ec = b - a;
 
-			cull = cull || (eb.x * ec.y >= eb.y * ec.x);
+			cull = cull || (eb.x * ec.y < eb.y * ec.x);
 #	endif
 
 #	if PRIMITIVE_NO_SAMPLING_POINTS_CULLING
@@ -537,7 +533,7 @@ PixelOut main(
 
 #		if NORMAL_TEX
 	const RVec3 nAtTangentspace = normalize((BINDLESS(localConstants.m_normalTex).Sample(g_globalSampler, uv).rgb - 0.5) * 2.0);
-	const Vec3 viewDir = normalize(g_globalConstants.m_cameraTransform.getTranslationPart() - vertInput.m_worldPos);
+	const Vec3 viewDir = -normalize(g_globalConstants.m_cameraTransform.getTranslationPart() - vertInput.m_worldPos);
 	const RVec3 normal = perturbNormal(nAtTangentspace, viewDir, uv, normalize(vertInput.m_normal));
 #		else
 	const RVec3 normal = normalize(vertInput.m_normal);

+ 1 - 1
AnKi/Shaders/HzbMaxDepthProject.ankiprog

@@ -53,7 +53,7 @@ Vec4 main(U32 svVertexId : SV_VERTEXID, U32 svInstanceId : SV_INSTANCEID) : SV_P
 
 	// Y
 	ndc.y = F32(tileY);
-	if(svVertexId == 3 || svVertexId == 2 || svVertexId == 7 || svVertexId == 6)
+	if(svVertexId == 0 || svVertexId == 1 || svVertexId == 4 || svVertexId == 5)
 	{
 		// Top side, move the point
 		ndc.y += 1.0f;

+ 10 - 12
AnKi/Shaders/IrradianceDice.ankiprog

@@ -46,14 +46,13 @@ RWStructuredBuffer<BufferOut> g_irradianceDisceResults : register(u0);
 #endif
 
 constexpr U32 kMinWaveSize = 8u;
-groupshared Vec3 s_integrationResults[6u][kThreadgroupSize / kMinWaveSize];
+groupshared Vec3 s_integrationResults[6u][kThreadgroupSize / kMinWaveSize]; // In cube coords
 groupshared U32 s_waveIndexInsideThreadGroup;
 
 RVec3 sampleLightShadingTexture(const U32 face, UVec3 svGroupThreadId)
 {
 	const Vec2 uv = (Vec2(svGroupThreadId.x, svGroupThreadId.y) + 0.5) / F32(THREDGROUP_SIZE_SQRT);
-	const Vec2 ndc = uvToNdc(uv);
-	const Vec3 cubeUvw = getCubemapDirection(ndc, face);
+	const Vec3 cubeUvw = getCubemapDirection(uv, face);
 
 	return g_lightShadingTexCube.SampleLevel(g_nearestAnyClampSampler, cubeUvw, 0.0).rgb;
 }
@@ -67,21 +66,20 @@ RVec3 sampleLightShadingTexture(const U32 face, UVec3 svGroupThreadId)
 
 	// Compute the NDC used in cubeCoordSolidAngle
 	const Vec2 faceUv = (Vec2(svGroupThreadId.xy) + 0.5) / threadgroupSizeSqrtf;
-	const Vec2 ndc = uvToNdc(faceUv);
 
 	// Compute result for a pixel
 	Vec3 resultFaces[6u];
 	for(U32 f = 0u; f < 6u; ++f)
 	{
 		// Get the direction of the dice face
-		const Vec3 diceDir = getCubemapDirection(Vec2(0.0, 0.0), f);
+		const Vec3 diceDir = getCubemapDirection(0.5, f) * Vec3(1.0, 1.0, -1.0);
 
-		const Vec3 r = getCubemapDirection(ndc, f);
+		const Vec3 r = getCubemapDirection(faceUv, f) * Vec3(1.0, 1.0, -1.0);
 
 		// Compute integral part
 		const RF32 lambert = max(0.0, dot(r, diceDir));
 		const RVec3 lightShading = sampleLightShadingTexture(f, svGroupThreadId);
-		const RVec3 irradiance = lightShading * lambert * cubeCoordSolidAngle(ndc, threadgroupSizeSqrtf);
+		const RVec3 irradiance = lightShading * lambert * cubeCoordSolidAngle(uvToNdc(faceUv), threadgroupSizeSqrtf);
 
 		// Store
 		resultFaces[f] = irradiance;
@@ -122,15 +120,15 @@ RVec3 sampleLightShadingTexture(const U32 face, UVec3 svGroupThreadId)
 	for(U32 f = 0u; f < 6u; ++f)
 	{
 		// Get the direction of the dice face
-		const Vec3 diceDir = getCubemapDirection(Vec2(0.0, 0.0), f);
+		const Vec3 diceDir = getCubemapDirection(0.5, f) * Vec3(1.0, 1.0, -1.0);
 
-		const Vec3 r = getCubemapDirection(ndc, f);
+		const Vec3 r = getCubemapDirection(faceUv, f) * Vec3(1.0, 1.0, -1.0);
 
 		// Compute integral part
 		const RF32 lambert = max(0.0, dot(r, diceDir));
 
 		// Read the gbuffer
-		const Vec3 gbufferUv = getCubemapDirection(ndc, f);
+		const Vec3 gbufferUv = getCubemapDirection(faceUv, f);
 		GbufferInfo gbuffer = (GbufferInfo)0;
 		unpackGBufferNoVelocity(g_gbufferTex[0u].SampleLevel(g_nearestAnyClampSampler, gbufferUv, 0.0),
 								g_gbufferTex[1u].SampleLevel(g_nearestAnyClampSampler, gbufferUv, 0.0),
@@ -139,12 +137,12 @@ RVec3 sampleLightShadingTexture(const U32 face, UVec3 svGroupThreadId)
 		// Sample irradiance
 		RVec3 firstBounceIrradiance =
 			sampleAmbientDice(s_integrationResults[0][0], s_integrationResults[1][0], s_integrationResults[2][0], s_integrationResults[3][0],
-							  s_integrationResults[4][0], s_integrationResults[5][0], gbuffer.m_normal);
+							  s_integrationResults[4][0], s_integrationResults[5][0], gbuffer.m_normal * Vec3(1.0, 1.0, -1.0));
 		firstBounceIrradiance = gbuffer.m_diffuse * firstBounceIrradiance;
 
 		// Compute 2nd bounce
 		const RVec3 lightShading = sampleLightShadingTexture(f, svGroupThreadId);
-		const RVec3 irradiance = (firstBounceIrradiance + lightShading * lambert) * cubeCoordSolidAngle(ndc, threadgroupSizeSqrtf);
+		const RVec3 irradiance = (firstBounceIrradiance + lightShading * lambert) * cubeCoordSolidAngle(uvToNdc(faceUv), threadgroupSizeSqrtf);
 
 		// Store
 		resultFaces[f] = irradiance;

+ 5 - 2
AnKi/Shaders/LightFunctions.hlsl

@@ -178,7 +178,7 @@ RF32 computeShadowFactorPointLightGeneric(PointLight light, Vec3 frag2Light, Tex
 
 	// Convert cube coords
 	U32 faceIdxu;
-	Vec2 uv = convertCubeUvsu(dir, faceIdxu);
+	Vec2 uv = convertCubeUvs(dir * Vec3(1.0, 1.0, -1.0), faceIdxu);
 
 	// Get the atlas offset
 	const Vec2 atlasOffset = light.m_shadowAtlasTileOffsets[faceIdxu].xy;
@@ -396,8 +396,9 @@ F32 computeProbeBlendWeight(Vec3 fragPos, // Doesn't need to be inside the AABB
 // https://www.shadertoy.com/view/XtcBDB
 RVec3 sampleAmbientDice(RVec3 posx, RVec3 negx, RVec3 posy, RVec3 negy, RVec3 posz, RVec3 negz, RVec3 normal)
 {
+	normal.z *= -1.0f;
 	const RVec3 axisWeights = normal * normal;
-	const RVec3 uv = ndcToUv(normal);
+	const RVec3 uv = normal * 0.5f + 0.5f;
 
 	RVec3 col = lerp(negx, posx, uv.x) * axisWeights.x;
 	col += lerp(negy, posy, uv.y) * axisWeights.y;
@@ -415,6 +416,8 @@ RVec3 sampleGlobalIllumination(const Vec3 worldPos, const Vec3 normal, const Glo
 {
 	// Find the UVW
 	Vec3 uvw = (worldPos - probe.m_aabbMin) / (probe.m_aabbMax - probe.m_aabbMin);
+	uvw = saturate(uvw);
+	uvw.y = 1.0f - uvw.y;
 
 	// The U contains the 6 directions so divide
 	uvw.x /= 6.0;

+ 4 - 2
AnKi/Shaders/LightShading.ankiprog

@@ -144,7 +144,8 @@ RVec3 main(VertOut input) : SV_TARGET0
 				const ReflectionProbe probe = g_reflectionProbes[firstbitlow2(cluster.m_reflectionProbesMask)];
 
 				// Sample
-				const Vec3 cubeUv = intersectProbe(worldPos, reflDir, probe.m_aabbMin, probe.m_aabbMax, probe.m_position);
+				Vec3 cubeUv = intersectProbe(worldPos, reflDir, probe.m_aabbMin, probe.m_aabbMax, probe.m_position);
+				cubeUv.z = -cubeUv.z;
 				probeColor = getBindlessTextureCubeRVec4(probe.m_cubeTexture).SampleLevel(g_trilinearClampSampler, cubeUv, reflLod).rgb;
 			}
 			else
@@ -165,7 +166,8 @@ RVec3 main(VertOut input) : SV_TARGET0
 					totalBlendWeight += blendWeight;
 
 					// Sample reflections
-					const Vec3 cubeUv = intersectProbe(worldPos, reflDir, probe.m_aabbMin, probe.m_aabbMax, probe.m_position);
+					Vec3 cubeUv = intersectProbe(worldPos, reflDir, probe.m_aabbMin, probe.m_aabbMax, probe.m_position);
+					cubeUv.z = -cubeUv.z;
 					const Vec3 c =
 						getBindlessTextureNonUniformIndexCubeRVec4(probe.m_cubeTexture).SampleLevel(g_trilinearClampSampler, cubeUv, reflLod).rgb;
 					probeColor += c * blendWeight;

+ 1 - 3
AnKi/Shaders/MotionVectors.ankiprog

@@ -10,8 +10,6 @@
 #if ANKI_COMPUTE_SHADER || ANKI_PIXEL_SHADER
 #	include <AnKi/Shaders/Functions.hlsl>
 
-constexpr F32 kMaxRejectionDistance = 0.1; // In meters
-
 SamplerState g_nearesetAnyClampSampler : register(s0);
 Texture2D g_currentDepthTex : register(t0);
 Texture2D g_velocityTex : register(t1);
@@ -69,7 +67,7 @@ PixelOut main(VertOut input)
 		Vec4 prevClipPos = mul(g_consts.m_prevViewProjMat, Vec4(worldPos, 1.0));
 		prevClipPos.xy /= prevClipPos.w;
 
-		const Vec2 diff = (prevClipPos.xy - clipPos.xy) * 0.5f; // aka uvToNdc(prevClipPos.xy) - uvToNdc(clipPos.xy)
+		const Vec2 diff = ndcToUv(prevClipPos.xy) - ndcToUv(clipPos.xy);
 		historyUv = uv + diff;
 	}
 

+ 4 - 3
AnKi/Shaders/QuadVert.hlsl

@@ -18,10 +18,11 @@ struct VertOut
 #if ANKI_VERTEX_SHADER
 VertOut main(U32 vertId : SV_VERTEXID)
 {
-	VertOut output;
-	output.m_uv = Vec2(vertId & 1, vertId >> 1) * 2.0;
+	const Vec2 coord = Vec2(vertId >> 1, vertId & 1);
 
-	output.m_svPosition = Vec4(output.m_uv * 2.0 - 1.0, CUSTOM_DEPTH, 1.0);
+	VertOut output;
+	output.m_svPosition = Vec4(coord * Vec2(4.0, -4.0) + Vec2(-1.0, 1.0), CUSTOM_DEPTH, 1.0);
+	output.m_uv = coord * 2.0f;
 
 	return output;
 }

+ 9 - 6
AnKi/Shaders/Sky.ankiprog

@@ -75,7 +75,8 @@ Vec3 getValFromTLut(Texture2D<Vec4> tex, SamplerState linearAnyClampSampler, Vec
 	const F32 height = length(pos);
 	const Vec3 up = pos / height;
 	const F32 sunCosZenithAngle = dot(dirToSun, up);
-	const Vec2 uv = Vec2(saturate(0.5f + 0.5f * sunCosZenithAngle), saturate((height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM)));
+	const Vec2 uv =
+		Vec2(saturate(0.5f + 0.5f * sunCosZenithAngle), 1.0 - saturate((height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM)));
 	return tex.SampleLevel(linearAnyClampSampler, uv, 0.0f).xyz;
 }
 
@@ -86,7 +87,7 @@ Vec3 getValFromTLut(Texture2D<Vec4> tex, Vec3 pos, Vec3 dirToSun)
 	const Vec3 up = pos / height;
 	const F32 sunCosZenithAngle = dot(dirToSun, up);
 
-	const Vec2 uv = Vec2(0.5f + 0.5f * sunCosZenithAngle, (height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM));
+	const Vec2 uv = Vec2(0.5f + 0.5f * sunCosZenithAngle, 1.0 - (height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM));
 
 	Vec2 texSize;
 	tex.GetDimensions(texSize.x, texSize.y);
@@ -100,7 +101,8 @@ Vec3 getValFromMultiScattLut(Texture2D<Vec4> tex, SamplerState linearAnyClampSam
 	const F32 height = length(pos);
 	const Vec3 up = pos / height;
 	const F32 sunCosZenithAngle = dot(dirToSun, up);
-	const Vec2 uv = Vec2(saturate(0.5 + 0.5 * sunCosZenithAngle), saturate((height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM)));
+	const Vec2 uv =
+		Vec2(saturate(0.5 + 0.5 * sunCosZenithAngle), 1.0 - saturate((height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM)));
 	return tex.SampleLevel(linearAnyClampSampler, uv, 0.0f).xyz;
 }
 
@@ -181,7 +183,7 @@ Vec3 getSunTransmittance(Vec3 pos, Vec3 dirToSun)
 
 	const F32 sunCosTheta = 2.0f * uv.x - 1.0f;
 	const F32 sunTheta = safeacos(sunCosTheta);
-	const F32 height = lerp(kGroundRadiusMM, kAtmosphereRadiusMM, uv.y);
+	const F32 height = lerp(kGroundRadiusMM, kAtmosphereRadiusMM, 1.0 - uv.y);
 
 	const Vec3 pos = Vec3(0.0f, height, 0.0f);
 	const Vec3 dirToSun = normalize(Vec3(0.0f, sunCosTheta, -sin(sunTheta)));
@@ -301,7 +303,7 @@ void getMulScattValues(Vec3 pos, Vec3 dirToSun, out Vec3 lumTotal, out Vec3 fms)
 
 	const F32 sunCosTheta = 2.0f * uv.x - 1.0f;
 	const F32 sunTheta = safeacos(sunCosTheta);
-	const F32 height = lerp(kGroundRadiusMM, kAtmosphereRadiusMM, uv.y);
+	const F32 height = lerp(kGroundRadiusMM, kAtmosphereRadiusMM, 1.0 - uv.y);
 
 	const Vec3 pos = Vec3(0.0f, height, 0.0f);
 	const Vec3 dirToSun = normalize(Vec3(0.0f, sunCosTheta, -sin(sunTheta)));
@@ -378,7 +380,8 @@ Vec3 raymarchScattering(Vec3 pos, Vec3 rayDir, Vec3 dirToSun, F32 tMax, F32 numS
 	Vec2 lutSize;
 	g_skyLutStorageTex.GetDimensions(lutSize.x, lutSize.y);
 
-	const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / lutSize;
+	Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / lutSize;
+	uv.y = 1.0f - uv.y;
 
 	const F32 azimuthAngle = (uv.x - 0.5f) * 2.0f * kPi;
 	// Non-linear mapping of altitude. See Section 5.3 of the paper.

+ 1 - 1
AnKi/Shaders/Sky.hlsl

@@ -42,7 +42,7 @@ Vec3 getValFromSkyLut(Texture2D<Vec4> skyLut, SamplerState linearAnyClampSampler
 
 	// Non-linear mapping of altitude angle. See Section 5.3 of the paper.
 	const F32 v = 0.5 + 0.5 * sign(altitudeAngle) * sqrt(abs(altitudeAngle) * 2.0 / kPi);
-	const Vec2 uv = Vec2(azimuthAngle / (2.0f * kPi), v);
+	const Vec2 uv = Vec2(azimuthAngle / (2.0f * kPi), 1.0 - v);
 
 	return skyLut.SampleLevel(linearAnyClampSampler, uv, 0.0f).xyz;
 }

+ 1 - 1
AnKi/Shaders/Ssr.ankiprog

@@ -128,7 +128,7 @@ RVec4 main(VertOut input) : SV_TARGET0
 		const RF32 oppositeLen = tan(coneAngle / 2.0f) * adjacentLen;
 		const Vec3 projectedOpposite =
 			cheapPerspectiveProjection(g_consts.m_projMat00_11_22_23, Vec4(hitPointViewSpace + Vec3(oppositeLen, 0.0f, 0.0f), 1.0f));
-		const F32 uvDistance = abs(ndcToUv(projectedOpposite.x) - hitPoint.x);
+		const F32 uvDistance = abs((projectedOpposite.x * 0.5f + 0.5f) - hitPoint.x);
 		const RF32 mip = max(0.0f, log2(uvDistance * lightShadingTexSize.x + kEpsilonF32));
 
 		// Reproject the hit point because you are reading the previous frame

+ 5 - 2
AnKi/Shaders/VisibilityAndCollisionFunctions.hlsl

@@ -225,8 +225,11 @@ Bool cullHzb(Vec2 aabbMinNdc, Vec2 aabbMaxNdc, F32 aabbMinDepth, Texture2D<Vec4>
 	F32 mipCount;
 	hzb.GetDimensions(0, texSize.x, texSize.y, mipCount);
 
-	const Vec2 minUv = saturate(ndcToUv(aabbMinNdc));
-	const Vec2 maxUv = saturate(ndcToUv(aabbMaxNdc));
+	const Vec2 uva = saturate(ndcToUv(aabbMinNdc));
+	const Vec2 uvb = saturate(ndcToUv(aabbMaxNdc));
+
+	const Vec2 minUv = Vec2(uva.x, uvb.y);
+	const Vec2 maxUv = Vec2(uvb.x, uva.y);
 	const Vec2 sizeXY = (maxUv - minUv) * texSize;
 	F32 mip = ceil(log2(max(sizeXY.x, sizeXY.y)));
 

+ 22 - 0
AnKi/Util/Functions.h

@@ -201,6 +201,28 @@ inline void alignRoundDown(TAlignment alignment, TValue& value)
 	value = getAlignedRoundDown(alignment, value);
 }
 
+/// Given two alignments compute a new alignment that satisfies both
+template<typename T>
+T computeCompoundAlignment(const T alignment1, const T alignment2)
+{
+	ANKI_ASSERT(alignment1 && alignment2);
+
+	// Compute greatest common divisor
+	T greatestCommonDivisor = alignment1;
+	T alignment2_ = alignment2;
+	while(alignment2_ != 0)
+	{
+		const auto temp = alignment2_;
+		alignment2_ = greatestCommonDivisor % alignment2_;
+		greatestCommonDivisor = temp;
+	}
+
+	// Calculate the least common multiple (LCM) of the two alignments
+	const auto lcmAlignment = alignment1 * (alignment2 / greatestCommonDivisor);
+
+	return lcmAlignment;
+}
+
 /// Check if a number is aligned
 template<typename Type>
 inline constexpr Bool isAligned(PtrSize alignment, Type value)

+ 8 - 4
Sandbox/Main.cpp

@@ -165,6 +165,14 @@ Error MyApp::userMainLoop(Bool& quit, Second elapsedTime)
 		mousePosOn1stClick = in.getMousePosition();
 	}
 
+	if(in.getKey(KeyCode::kF2) == 1)
+	{
+		mover->setLocalRotation(
+			Mat3x4(-0.600323, -0.173433, 0.780726, 0.000000, 0.000000, 0.976203, 0.216857, 0.000000, -0.799758, 0.130185, -0.586037, 0.000000));
+
+		// printf("%s | %s\n", mover->getWorldTransform().getRotation().toString().cstr(), mover->getWorldTransform().getOrigin().toString().cstr());
+	}
+
 	if(in.getMouseButton(MouseButton::kRight) || in.hasTouchDevice())
 	{
 		constexpr F32 ROTATE_ANGLE = toRad(2.5f);
@@ -198,10 +206,6 @@ Error MyApp::userMainLoop(Bool& quit, Second elapsedTime)
 				renderer.getDbg().setDitheredDepthTestEnabled(true);
 			}
 		}
-		if(in.getKey(KeyCode::kF2) == 1)
-		{
-			// renderer.getDbg().flipFlags(DbgFlag::SPATIAL_COMPONENT);
-		}
 
 		if(in.getKey(KeyCode::kUp))
 		{

+ 2 - 2
Tests/Gr/Gr.cpp

@@ -440,6 +440,7 @@ float4 main(float4 svPosition : SV_POSITION, float2 uv : TEXCOORDS, uint svPrimI
 
 		const U kIterationCount = 100;
 		U iterations = kIterationCount;
+		const Vec4 viewport(10.0f, 10.0f, F32(NativeWindow::getSingleton().getWidth() - 20), F32(NativeWindow::getSingleton().getHeight() - 30));
 		while(iterations--)
 		{
 			HighRezTimer timer;
@@ -451,7 +452,7 @@ float4 main(float4 svPosition : SV_POSITION, float2 uv : TEXCOORDS, uint svPrimI
 			cinit.m_flags = CommandBufferFlag::kGeneralWork;
 			CommandBufferPtr cmdb = GrManager::getSingleton().newCommandBuffer(cinit);
 
-			cmdb->setViewport(0, 0, NativeWindow::getSingleton().getWidth(), NativeWindow::getSingleton().getHeight());
+			cmdb->setViewport(U32(viewport.x()), U32(viewport.y()), U32(viewport.z()), U32(viewport.w()));
 			cmdb->bindShaderProgram(prog.get());
 
 			const TextureBarrierInfo barrier = {TextureView(presentTex.get(), TextureSubresourceDesc::all()), TextureUsageBit::kNone,
@@ -460,7 +461,6 @@ float4 main(float4 svPosition : SV_POSITION, float2 uv : TEXCOORDS, uint svPrimI
 
 			cmdb->beginRenderPass({TextureView(presentTex.get(), TextureSubresourceDesc::firstSurface())});
 
-			const Vec4 viewport(0.0f, 0.0f, F32(NativeWindow::getSingleton().getWidth()), F32(NativeWindow::getSingleton().getHeight()));
 			cmdb->setFastConstants(&viewport, sizeof(viewport));
 
 			cmdb->bindSrv(0, 0, TextureView(tex.get(), TextureSubresourceDesc::all()));