Browse Source

Bug fixes in Euler angles. Some work on the cornell box test

Panagiotis Christopoulos Charitos 5 years ago
parent
commit
14f6df4ece

+ 5 - 0
src/anki/gr/ShaderProgram.h

@@ -139,6 +139,11 @@ public:
 			return false;
 		}
 
+		if(!graphicsMask && !compute && !rtMask)
+		{
+			return false;
+		}
+
 		return true;
 	}
 };

+ 41 - 8
src/anki/gr/vulkan/AccelerationStructureImpl.cpp

@@ -258,10 +258,47 @@ void AccelerationStructureImpl::computeBarrierInfo(AccelerationStructureUsageBit
 		srcAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
 	}
 
+	if(!!(before & AccelerationStructureUsageBit::ATTACH))
+	{
+		srcStages |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR;
+		srcAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
+	}
+
+	if(!!(before & AccelerationStructureUsageBit::GEOMETRY_READ))
+	{
+		srcStages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT
+					 | VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
+		srcAccesses |= VK_ACCESS_MEMORY_READ_BIT; // READ_BIT is the only viable solution by elimination
+	}
+
+	if(!!(before & AccelerationStructureUsageBit::FRAGMENT_READ))
+	{
+		srcStages |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		srcAccesses |= VK_ACCESS_MEMORY_READ_BIT;
+	}
+
+	if(!!(before & AccelerationStructureUsageBit::COMPUTE_READ))
+	{
+		srcStages |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+		srcAccesses |= VK_ACCESS_MEMORY_READ_BIT;
+	}
+
+	if(!!(before & AccelerationStructureUsageBit::RAY_GEN_READ))
+	{
+		srcStages |= VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR;
+		srcAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
+	}
+
 	// After
 	dstStages = 0;
 	dstAccesses = 0;
 
+	if(!!(after & AccelerationStructureUsageBit::BUILD))
+	{
+		dstStages |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR;
+		dstAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
+	}
+
 	if(!!(after & AccelerationStructureUsageBit::ATTACH))
 	{
 		dstStages |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR;
@@ -272,19 +309,19 @@ void AccelerationStructureImpl::computeBarrierInfo(AccelerationStructureUsageBit
 	{
 		dstStages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT
 					 | VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
-		dstAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
+		dstAccesses |= VK_ACCESS_MEMORY_READ_BIT; // READ_BIT is the only viable solution by elimination
 	}
 
 	if(!!(after & AccelerationStructureUsageBit::FRAGMENT_READ))
 	{
 		dstStages |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
-		dstAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
+		dstAccesses |= VK_ACCESS_MEMORY_READ_BIT;
 	}
 
 	if(!!(after & AccelerationStructureUsageBit::COMPUTE_READ))
 	{
 		dstStages |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
-		dstAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
+		dstAccesses |= VK_ACCESS_MEMORY_READ_BIT;
 	}
 
 	if(!!(after & AccelerationStructureUsageBit::RAY_GEN_READ))
@@ -293,11 +330,7 @@ void AccelerationStructureImpl::computeBarrierInfo(AccelerationStructureUsageBit
 		dstAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
 	}
 
-	if(after == AccelerationStructureUsageBit::BUILD)
-	{
-		dstStages |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR;
-		dstAccesses |= VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
-	}
+	ANKI_ASSERT(srcStages && dstStages);
 }
 
 } // end namespace anki

+ 1 - 0
src/anki/gr/vulkan/CommandBufferImpl.inl.h

@@ -392,6 +392,7 @@ inline void CommandBufferImpl::traceRaysInternal(BufferPtr& sbtBuffer, PtrSize s
 	const U32 shaderGroupBaseAlignment =
 		getGrManagerImpl().getPhysicalDeviceRayTracingProperties().shaderGroupBaseAlignment;
 	const PtrSize sbtBufferSize = sbtRecordCount * shaderGroupBaseAlignment;
+	(void)sbtBufferSize;
 	ANKI_ASSERT(sbtBufferSize + sbtBufferOffset <= sbtBuffer->getSize());
 	ANKI_ASSERT(isAligned(shaderGroupBaseAlignment, sbtBufferOffset));
 

+ 27 - 26
src/anki/math/Axisang.h

@@ -38,16 +38,16 @@ public:
 
 	explicit TAxisang(const TQuat<T>& q)
 	{
-		m_ang = 2.0 * acos<T>(q.w());
-		T length = sqrt<T>(1.0 - q.w() * q.w());
+		m_ang = T(2) * acos<T>(q.w());
+		T length = sqrt<T>(T(1) - q.w() * q.w());
 		if(!isZero<T>(length))
 		{
-			length = 1.0 / length;
+			length = T(1) / length;
 			m_axis = TVec<T, 3>(q.x() * length, q.y() * length, q.z() * length);
 		}
 		else
 		{
-			m_axis = TVec<T, 3>(0.0);
+			m_axis = TVec<T, 3>(T(0));
 		}
 	}
 
@@ -56,50 +56,51 @@ public:
 		if(isZero<T>(m3(0, 1) - m3(1, 0)) && isZero<T>(m3(0, 2) - m3(2, 0)) && isZero<T>(m3(1, 2) - m3(2, 1)))
 		{
 
-			if((absolute<T>(m3(0, 1) + m3(1, 0)) < 0.1) && (absolute<T>(m3(0, 2) + m3(2, 0)) < 0.1)
-			   && (absolute<T>(m3(1, 2) + m3(2, 1)) < 0.1) && (absolute<T>(m3(0, 0) + m3(1, 1) + m3(2, 2)) - 3) < 0.1)
+			if((absolute<T>(m3(0, 1) + m3(1, 0)) < T(0.1)) && (absolute<T>(m3(0, 2) + m3(2, 0)) < T(0.1))
+			   && (absolute<T>(m3(1, 2) + m3(2, 1)) < T(0.1))
+			   && (absolute<T>(m3(0, 0) + m3(1, 1) + m3(2, 2)) - 3) < T(0.1))
 			{
-				m_axis = TVec<T, 3>(1.0, 0.0, 0.0);
-				m_ang = 0.0;
+				m_axis = TVec<T, 3>(T(1), T(0), T(0));
+				m_ang = T(0);
 				return;
 			}
 
 			m_ang = PI;
-			m_axis.x() = (m3(0, 0) + 1.0) / 2.0;
-			if(m_axis.x() > 0.0)
+			m_axis.x() = (m3(0, 0) + T(1)) / T(2);
+			if(m_axis.x() > T(0))
 			{
 				m_axis.x() = sqrt(m_axis.x());
 			}
 			else
 			{
-				m_axis.x() = 0.0;
+				m_axis.x() = T(0);
 			}
-			m_axis.y() = (m3(1, 1) + 1.0) / 2.0;
-			if(m_axis.y() > 0.0)
+			m_axis.y() = (m3(1, 1) + T(1)) / T(2);
+			if(m_axis.y() > T(0))
 			{
 				m_axis.y() = sqrt(m_axis.y());
 			}
 			else
 			{
-				m_axis.y() = 0.0;
+				m_axis.y() = T(0);
 			}
 
-			m_axis.z() = (m3(2, 2) + 1.0) / 2.0;
-			if(m_axis.z() > 0.0)
+			m_axis.z() = (m3(2, 2) + T(1)) / T(2);
+			if(m_axis.z() > T(0))
 			{
 				m_axis.z() = sqrt(m_axis.z());
 			}
 			else
 			{
-				m_axis.z() = 0.0;
+				m_axis.z() = T(0);
 			}
 
-			Bool xZero = isZero<T>(m_axis.x());
-			Bool yZero = isZero<T>(m_axis.y());
-			Bool zZero = isZero<T>(m_axis.z());
-			Bool xyPositive = (m3(0, 1) > 0);
-			Bool xzPositive = (m3(0, 2) > 0);
-			Bool yzPositive = (m3(1, 2) > 0);
+			const Bool xZero = isZero<T>(m_axis.x());
+			const Bool yZero = isZero<T>(m_axis.y());
+			const Bool zZero = isZero<T>(m_axis.z());
+			const Bool xyPositive = (m3(0, 1) > T(0));
+			const Bool xzPositive = (m3(0, 2) > T(0));
+			const Bool yzPositive = (m3(1, 2) > T(0));
 			if(xZero && !yZero && !zZero)
 			{
 				if(!yzPositive)
@@ -128,12 +129,12 @@ public:
 		T s = sqrt((m3(2, 1) - m3(1, 2)) * (m3(2, 1) - m3(1, 2)) + (m3(0, 2) - m3(2, 0)) * (m3(0, 2) - m3(2, 0))
 				   + (m3(1, 0) - m3(0, 1)) * (m3(1, 0) - m3(0, 1)));
 
-		if(absolute(s) < 0.001)
+		if(absolute(s) < T(0.001))
 		{
-			s = 1.0;
+			s = T(1);
 		}
 
-		m_ang = acos<T>((m3(0, 0) + m3(1, 1) + m3(2, 2) - 1.0) / 2.0);
+		m_ang = acos<T>((m3(0, 0) + m3(1, 1) + m3(2, 2) - T(1)) / T(2));
 		m_axis.x() = (m3(2, 1) - m3(1, 2)) / s;
 		m_axis.y() = (m3(0, 2) - m3(2, 0)) / s;
 		m_axis.z() = (m3(1, 0) - m3(0, 1)) / s;

+ 34 - 40
src/anki/math/Euler.h

@@ -13,7 +13,8 @@ namespace anki
 /// @addtogroup math
 /// @{
 
-/// Euler angles. Used for rotations. It cannot describe a rotation accurately though
+/// Euler angles. Used for rotations. It cannot describe a rotation accurately though.
+/// The 'x' denotes a rotation around x axis, 'y' around y axis and 'z' around z axis.
 template<typename T>
 class TEuler
 {
@@ -41,58 +42,51 @@ public:
 	explicit TEuler(const TQuat<T>& q)
 	{
 		const T test = q.x() * q.y() + q.z() * q.w();
-		if(test > 0.499)
+		if(test > T(0.499))
 		{
-			y() = 2.0 * atan2<T>(q.x(), q.w());
-			z() = PI / 2.0;
-			x() = 0.0;
-			return;
+			y() = T(2) * atan2<T>(q.x(), q.w());
+			z() = PI / T(2);
+			x() = T(0);
 		}
-		if(test < -0.499)
+		else if(test < T(-0.499))
 		{
-			y() = -2.0 * atan2<T>(q.x(), q.w());
-			z() = -PI / 2.0;
-			x() = 0.0;
-			return;
+			y() = -T(2) * atan2<T>(q.x(), q.w());
+			z() = -PI / T(2);
+			x() = T(0);
+		}
+		else
+		{
+			const T sqx = q.x() * q.x();
+			const T sqy = q.y() * q.y();
+			const T sqz = q.z() * q.z();
+			y() = atan2<T>(T(2) * q.y() * q.w() - T(2) * q.x() * q.z(), T(1) - T(2) * sqy - T(2) * sqz);
+			z() = asin<T>(T(2) * test);
+			x() = atan2<T>(T(2) * q.x() * q.w() - T(2) * q.y() * q.z(), T(1) - T(2) * sqx - T(2) * sqz);
 		}
-
-		T sqx = q.x() * q.x();
-		T sqy = q.y() * q.y();
-		T sqz = q.z() * q.z();
-		y() = atan2<T>(2.0 * q.y() * q.w() - 2.0 * q.x() * q.z(), 1.0 - 2.0 * sqy - 2.0 * sqz);
-		z() = asin<T>(2.0 * test);
-		x() = atan2<T>(2.0 * q.x() * q.w() - 2.0 * q.y() * q.z(), 1.0 - 2.0 * sqx - 2.0 * sqz);
 	}
 
 	explicit TEuler(const TMat<T, 3, 3>& m3)
 	{
-		T cx, sx;
-		T cy, sy;
-		T cz, sz;
-
-		sy = m3(0, 2);
-		cy = sqrt<T>(1.0 - sy * sy);
-		// normal case
-		if(!isZero<T>(cy))
+		if(m3(1, 0) > T(0.998))
+		{
+			// Singularity at north pole
+			y() = atan2(m3(0, 2), m3(2, 2));
+			z() = PI / T(2);
+			x() = T(0);
+		}
+		else if(m3(1, 0) < T(-0.998))
 		{
-			T factor = 1.0 / cy;
-			sx = -m3(1, 2) * factor;
-			cx = m3(2, 2) * factor;
-			sz = -m3(0, 1) * factor;
-			cz = m3(0, 0) * factor;
+			// Singularity at south pole
+			y() = atan2(m3(0, 2), m3(2, 2));
+			z() = -PI / T(2);
+			x() = T(0);
 		}
-		// x and z axes aligned
 		else
 		{
-			sz = 0.0;
-			cz = 1.0;
-			sx = m3(2, 1);
-			cx = m3(1, 1);
+			y() = atan2(-m3(2, 0), m3(0, 0));
+			z() = asin(m3(1, 0));
+			x() = atan2(-m3(1, 2), m3(1, 1));
 		}
-
-		z() = atan2<T>(sz, cz);
-		y() = atan2<T>(sy, cy);
-		x() = atan2<T>(sx, cx);
 	}
 	/// @}
 

+ 27 - 3
src/anki/math/Mat.h

@@ -1007,11 +1007,11 @@ public:
 	{
 		TMat& m = *this;
 		// Not normalized axis
-		ANKI_ASSERT(isZero<T>(1.0 - axisang.getAxis().getLength()));
+		ANKI_ASSERT(isZero<T>(T(1) - axisang.getAxis().getLength()));
 
 		T c, s;
 		sinCos(axisang.getAngle(), s, c);
-		T t = 1.0 - c;
+		T t = T(1) - c;
 
 		const TVec<T, 3>& axis = axisang.getAxis();
 		m(0, 0) = c + axis.x() * axis.x() * t;
@@ -1473,7 +1473,7 @@ public:
 	ANKI_ENABLE_METHOD(I == 4 && J == 4)
 	TMat getInverseTransformation() const
 	{
-		TMat<T, 3, 3> invertedRot = getRotationPart().getTransposed();
+		const TMat<T, 3, 3> invertedRot = getRotationPart().getTransposed();
 		TVec<T, 3> invertedTsl = getTranslationPart().xyz();
 		invertedTsl = -(invertedRot * invertedTsl);
 		return TMat(invertedTsl.xyz0(), invertedRot);
@@ -1489,6 +1489,30 @@ public:
 						  m(2, 0) * v.x() + m(2, 1) * v.y() + m(2, 2) * v.z() + m(2, 3));
 	}
 
+	/// Create a new transform matrix position at eye and looking at refPoint.
+	template<U VEC_DIMS, ANKI_ENABLE(J == 3 && I == 4 && VEC_DIMS >= 3)>
+	static TMat lookAt(const TVec<T, VEC_DIMS>& eye, const TVec<T, VEC_DIMS>& refPoint, const TVec<T, VEC_DIMS>& up)
+	{
+		const TVec<T, 3> vdir = (refPoint.xyz() - eye.xyz()).getNormalized();
+		const TVec<T, 3> vup = (up.xyz() - vdir * up.xyz().dot(vdir)).getNormalized();
+		const TVec<T, 3> vside = vdir.cross(vup);
+		TMat out;
+		out.setColumns(vside, vup, -vdir, eye.xyz());
+		return out;
+	}
+
+	/// Create a new transform matrix position at eye and looking at refPoint.
+	template<U VEC_DIMS, ANKI_ENABLE(J == 4 && I == 4 && VEC_DIMS >= 3)>
+	static TMat lookAt(const TVec<T, VEC_DIMS>& eye, const TVec<T, VEC_DIMS>& refPoint, const TVec<T, VEC_DIMS>& up)
+	{
+		const TVec<T, 4> vdir = (refPoint.xyz0() - eye.xyz0()).getNormalized();
+		const TVec<T, 4> vup = (up.xyz0() - vdir * up.xyz0().dot(vdir)).getNormalized();
+		const TVec<T, 4> vside = vdir.cross(vup);
+		TMat out;
+		out.setColumns(vside, vup, -vdir, eye.xyz1());
+		return out;
+	}
+
 	TMat lerp(const TMat& b, T t) const
 	{
 		return ((*this) * (1.0 - t)) + (b * t);

+ 12 - 2
src/anki/math/Transform.h

@@ -215,8 +215,18 @@ public:
 		return out;
 	}
 
-	ANKI_ENABLE_METHOD(std::is_floating_point<T>::value)
-	void toString(StringAuto& str) const
+	template<U VEC_DIMS>
+	TTransform& lookAt(const TVec<T, VEC_DIMS>& refPoint, const TVec<T, VEC_DIMS>& up)
+	{
+		const TVec<T, 4> j = up.xyz0();
+		const TVec<T, 4> vdir = (refPoint.xyz0() - m_origin).getNormalized();
+		const TVec<T, 4> vup = (j - vdir * j.dot(vdir)).getNormalized();
+		const TVec<T, 4> vside = vdir.cross(vup);
+		m_rotation.setColumns(vside.xyz(), vup.xyz(), (-vdir).xyz());
+		return *this;
+	}
+
+	ANKI_ENABLE_METHOD(std::is_floating_point<T>::value) void toString(StringAuto& str) const
 	{
 		StringAuto b(str.getAllocator());
 		m_origin.toString(b);

+ 131 - 11
tests/gr/Gr.cpp

@@ -12,6 +12,7 @@
 #include <anki/resource/TransferGpuAllocator.h>
 #include <anki/shader_compiler/Glslang.h>
 #include <anki/shader_compiler/ShaderProgramParser.h>
+#include <anki/collision/Aabb.h>
 #include <ctime>
 
 namespace anki
@@ -379,8 +380,8 @@ static ShaderProgramPtr createProgram(CString vertSrc, CString fragSrc, GrManage
 	ShaderPtr vert = createShader(vertSrc, ShaderType::VERTEX, gr);
 	ShaderPtr frag = createShader(fragSrc, ShaderType::FRAGMENT, gr);
 	ShaderProgramInitInfo inf;
-	inf.m_graphicsShaders[ShaderType::VERTEX];
-	inf.m_graphicsShaders[ShaderType::FRAGMENT];
+	inf.m_graphicsShaders[ShaderType::VERTEX] = vert;
+	inf.m_graphicsShaders[ShaderType::FRAGMENT] = frag;
 	return gr.newShaderProgram(inf);
 }
 
@@ -2633,18 +2634,18 @@ static void createCubeBuffers(GrManager& gr, Vec3 min, Vec3 max, BufferPtr& inde
 {
 	BufferInitInfo inf;
 	inf.m_access = BufferMapAccessBit::WRITE;
-	inf.m_usage = BufferUsageBit::INDEX | BufferUsageBit::STORAGE_TRACE_RAYS_READ;
+	inf.m_usage = BufferUsageBit::INDEX | BufferUsageBit::VERTEX | BufferUsageBit::STORAGE_TRACE_RAYS_READ;
 	inf.m_size = sizeof(Vec3) * 8;
 	vertBuffer = gr.newBuffer(inf);
 	WeakArray<Vec3> positions = vertBuffer->map<Vec3>(0, 8, BufferMapAccessBit::WRITE);
 
-	//   7----6
-	//  /|   /|
-	// 3-|--2 |
-	// | |  | |
-	// | 4 -|-5
-	// |/   |/
-	// 0----1
+	//   7------6
+	//  /|     /|
+	// 3------2 |
+	// | |    | |
+	// | 4 ---|-5
+	// |/     |/
+	// 0------1
 	positions[0] = Vec3(min.x(), min.y(), max.z());
 	positions[1] = Vec3(max.x(), min.y(), max.z());
 	positions[2] = Vec3(max.x(), max.y(), max.z());
@@ -2709,6 +2710,7 @@ static void createCubeBuffers(GrManager& gr, Vec3 min, Vec3 max, BufferPtr& inde
 	indices[t++] = 5;
 	indices[t++] = 6;
 
+	ANKI_ASSERT(t == indices.getSize());
 	indexBuffer->unmap();
 }
 
@@ -2716,7 +2718,7 @@ ANKI_TEST(Gr, RayGen)
 {
 	COMMON_BEGIN();
 
-	const Bool useRayTracing = gr->getDeviceCapabilities().m_rayTracingEnabled;
+	const Bool useRayTracing = false; // gr->getDeviceCapabilities().m_rayTracingEnabled;
 	if(!useRayTracing)
 	{
 		ANKI_TEST_LOGW("Ray tracing not supported");
@@ -2724,6 +2726,124 @@ ANKI_TEST(Gr, RayGen)
 
 	HeapAllocator<U8> alloc = {allocAligned, nullptr};
 
+	// Create the raster programs
+	ShaderProgramPtr rasterProg;
+	if(!useRayTracing)
+	{
+		const CString vertSrc = R"(
+layout(push_constant, row_major) uniform b_pc
+{
+	Mat4 u_mvp;
+	Vec4 u_color;
+};
+
+layout(location = 0) in Vec3 in_pos;
+layout(location = 0) out Vec4 out_color;
+
+void main()
+{
+	gl_Position = u_mvp * Vec4(in_pos, 1.0);
+	out_color = u_color;
+}
+)";
+
+		const CString fragSrc = R"(
+layout(location = 0) in Vec4 in_color;
+layout(location = 0) out Vec4 out_color;
+
+void main()
+{
+	out_color = in_color;
+}
+)";
+
+		rasterProg = createProgram(vertSrc, fragSrc, *gr);
+	}
+
+	// Create geometry
+	BufferPtr smallBoxVertBuffer, smallBoxIndexBuffer;
+	BufferPtr bigBoxVertBuffer, bigBoxIndexBuffer;
+	const Aabb smallBox(Vec3(130.0f, 0.0f, 65.0f), Vec3(295.0f, 160.0f, 230.0f));
+	const Aabb bigBox(Vec3(265.0f, 0.0f, 295.0f), Vec3(430.0f, 330.0f, 460.0f));
+	{
+		createCubeBuffers(*gr, -(smallBox.getMax().xyz() - smallBox.getMin().xyz()) / 2.0f,
+						  (smallBox.getMax().xyz() - smallBox.getMin().xyz()) / 2.0f, smallBoxIndexBuffer,
+						  smallBoxVertBuffer);
+
+		createCubeBuffers(*gr, -(bigBox.getMax().xyz() - bigBox.getMin().xyz()) / 2.0f,
+						  (bigBox.getMax().xyz() - bigBox.getMin().xyz()) / 2.0f, bigBoxIndexBuffer, bigBoxVertBuffer);
+	}
+
+	// Draw
+	constexpr U32 ITERATIONS = 200;
+	for(U i = 0; i < ITERATIONS; ++i)
+	{
+		HighRezTimer timer;
+		timer.start();
+
+		const Mat4 viewMat =
+			Mat4::lookAt(Vec3(278.0f, 278.0f, -800.0f), Vec3(278.0f, 278.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f))
+				.getInverse();
+		const Mat4 projMat =
+			Mat4::calculatePerspectiveProjectionMatrix(toRad(40.0f) * WIDTH / HEIGHT, toRad(40.0f), 0.01f, 2000.0f);
+
+		CommandBufferInitInfo cinit;
+		cinit.m_flags = CommandBufferFlag::GRAPHICS_WORK | CommandBufferFlag::SMALL_BATCH;
+		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
+
+		cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+
+		cmdb->bindShaderProgram(rasterProg);
+		TexturePtr presentTex = gr->acquireNextPresentableTexture();
+		FramebufferPtr fb = createColorFb(*gr, presentTex);
+
+		cmdb->setTextureBarrier(presentTex, TextureUsageBit::NONE, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+								TextureSubresourceInfo{});
+
+		cmdb->beginRenderPass(fb, {TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}, {});
+
+		struct PC
+		{
+			Mat4 m_mvp;
+			Vec4 m_color;
+		} pc;
+
+		pc.m_mvp = projMat * viewMat
+				   * Mat4(Vec4((smallBox.getMin() + smallBox.getMax()).xyz() / 2.0f, 1.0f),
+						  Mat3(Axisang(toRad(-18.0f), Vec3(0.0f, 1.0f, 0.0f))));
+		pc.m_color = Vec4(0.75f);
+		cmdb->setPushConstants(&pc, sizeof(pc));
+
+		cmdb->setVertexAttribute(0, 0, Format::R32G32B32_SFLOAT, 0);
+		cmdb->bindVertexBuffer(0, smallBoxVertBuffer, 0, sizeof(Vec3));
+		cmdb->bindIndexBuffer(smallBoxIndexBuffer, 0, IndexType::U16);
+
+		cmdb->drawElements(PrimitiveTopology::TRIANGLE_STRIP, 36);
+
+		cmdb->bindVertexBuffer(0, bigBoxVertBuffer, 0, sizeof(Vec3));
+		pc.m_mvp = projMat * viewMat
+				   * Mat4(Vec4((bigBox.getMin() + bigBox.getMax()).xyz() / 2.0f, 1.0f),
+						  Mat3(Axisang(toRad(15.0f), Vec3(0.0f, 1.0f, 0.0f))));
+		cmdb->setPushConstants(&pc, sizeof(pc));
+		cmdb->drawElements(PrimitiveTopology::TRIANGLE_STRIP, 36);
+
+		cmdb->endRenderPass();
+
+		cmdb->setTextureBarrier(presentTex, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, TextureUsageBit::PRESENT,
+								TextureSubresourceInfo{});
+
+		cmdb->flush();
+
+		gr->swapBuffers();
+
+		timer.stop();
+		const F32 TICK = 1.0f / 30.0f;
+		if(timer.getElapsedTime() < TICK)
+		{
+			HighRezTimer::sleep(TICK - timer.getElapsedTime());
+		}
+	}
+
 	COMMON_END();
 }