#include "Common/precompiled.h"
#include "GX2_State.h"
#include "GX2_Command.h"
#include "Cafe/HW/Latte/ISA/LatteReg.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "Cafe/OS/common/OSCommon.h"

namespace GX2
{
	using namespace Latte;

	void GX2InitAlphaTestReg(GX2AlphaTestReg* reg, uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef)
	{
		Latte::LATTE_SX_ALPHA_TEST_CONTROL tmpRegCtrl;
		tmpRegCtrl.set_ALPHA_FUNC(alphaFunc);
		tmpRegCtrl.set_ALPHA_TEST_ENABLE(alphaTestEnable != 0);
		reg->regAlphaTestControl = tmpRegCtrl;

		Latte::LATTE_SX_ALPHA_REF tmpRegRef;
		tmpRegRef.set_ALPHA_TEST_REF(alphaRef);
		reg->regAlphaTestRef = tmpRegRef;
	}

	void GX2SetAlphaTestReg(GX2AlphaTestReg* reg)
	{
		GX2ReserveCmdSpace(3 + 3);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::SX_ALPHA_TEST_CONTROL - 0xA000,
			reg->regAlphaTestControl,
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::SX_ALPHA_REF - 0xA000,
			reg->regAlphaTestRef);
	}

	void GX2SetAlphaTest(uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef)
	{
		GX2AlphaTestReg tmpReg;
		GX2InitAlphaTestReg(&tmpReg, alphaTestEnable, alphaFunc, alphaRef);
		GX2SetAlphaTestReg(&tmpReg);
	}

	void GX2InitColorControlReg(GX2ColorControlReg* reg, GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable)
	{
		Latte::LATTE_CB_COLOR_CONTROL colorControlReg2;
		colorControlReg2.set_MULTIWRITE_ENABLE(multiwriteEnable != 0);
		if (colorBufferEnable == 0)
			colorControlReg2.set_SPECIAL_OP(Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE);
		else
			colorControlReg2.set_SPECIAL_OP(Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::NORMAL);
		colorControlReg2.set_BLEND_MASK(blendMask);
		colorControlReg2.set_ROP(logicOp);
		reg->reg = colorControlReg2;
	}

	void GX2SetColorControlReg(GX2ColorControlReg* reg)
	{
		GX2ReserveCmdSpace(3);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::CB_COLOR_CONTROL - 0xA000,
			reg->reg);
	}

	void GX2SetColorControl(GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable)
	{
		GX2ColorControlReg colorControlReg;
		GX2InitColorControlReg(&colorControlReg, logicOp, blendMask, multiwriteEnable, colorBufferEnable);
		GX2SetColorControlReg(&colorControlReg);
	}


	void GX2InitPolygonControlReg(GX2PolygonControlReg* reg,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace,
		uint32 cullFront,
		uint32 cullBack,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack,
		uint32 polygonOffsetFrontEnable,
		uint32 polygonOffsetBackEnable,
		uint32 paraOffsetEnable)
	{
		Latte::LATTE_PA_SU_SC_MODE_CNTL v;
		v.set_FRONT_FACE(frontFace);
		v.set_CULL_FRONT((cullFront & 1) != 0);
		v.set_CULL_BACK((cullBack & 1) != 0);
		v.set_POLYGON_MODE(usePolygonMode);
		v.set_FRONT_POLY_MODE(polyModeFront);
		v.set_BACK_POLY_MODE(polyModeBack);
		v.set_OFFSET_PARA_ENABLED((paraOffsetEnable & 1) != 0);
		v.set_OFFSET_FRONT_ENABLED((polygonOffsetFrontEnable & 1) != 0);
		v.set_OFFSET_BACK_ENABLED((polygonOffsetBackEnable & 1) != 0);
		reg->reg = v;
	}

	void GX2SetPolygonControlReg(GX2PolygonControlReg* reg)
	{
		GX2ReserveCmdSpace(3);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::PA_SU_SC_MODE_CNTL - 0xA000,
			reg->reg);
	}

	void GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace,
		uint32 cullFront,
		uint32 cullBack,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront,
		Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack,
		uint32 polygonOffsetFrontEnable,
		uint32 polygonOffsetBackEnable,
		uint32 paraOffsetEnable)
	{
		GX2PolygonControlReg reg{};
		GX2InitPolygonControlReg(&reg, frontFace, cullFront, cullBack, usePolygonMode, polyModeFront, polyModeBack, polygonOffsetFrontEnable, polygonOffsetBackEnable, paraOffsetEnable);
		GX2SetPolygonControlReg(&reg);
	}

	void GX2SetCullOnlyControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack)
	{
		GX2PolygonControlReg reg{};
		GX2InitPolygonControlReg(&reg, frontFace, cullFront, cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE::UKN0, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::POINTS, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::POINTS, 0, 0, 0);
		GX2SetPolygonControlReg(&reg);
	}

	void GX2InitPolygonOffsetReg(GX2PolygonOffsetReg* reg, float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset)
	{
		frontScale *= 16.0;
		backScale *= 16.0;
		reg->regFrontScale = Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE().set_SCALE(frontScale);
		reg->regFrontOffset = Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET().set_OFFSET(frontOffset);
		reg->regBackScale = Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE().set_SCALE(backScale);
		reg->regBackOffset = Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET().set_OFFSET(backOffset);
		reg->regClamp = Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP().set_CLAMP(clampOffset);
	}

	void GX2SetPolygonOffsetReg(GX2PolygonOffsetReg* reg)
	{
		GX2ReserveCmdSpace(6 + 3);

		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 4),
			Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_SCALE - 0xA000,
			reg->regFrontScale,
			reg->regFrontOffset,
			reg->regBackScale,
			reg->regBackOffset,

			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::PA_SU_POLY_OFFSET_CLAMP - 0xA000,
			reg->regClamp);
	}

	void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset)
	{
		GX2PolygonOffsetReg tmpReg;
		GX2InitPolygonOffsetReg(&tmpReg, frontOffset, frontScale, backOffset, backScale, clampOffset);
		GX2SetPolygonOffsetReg(&tmpReg);
	}

	void GX2SetRasterizerClipControlEx(bool enableRasterizer, bool enableZClip, bool enableHalfZ)
	{
		GX2ReserveCmdSpace(3);

		//if (enableHalfZ)
		//{
		//	// Smash has a bug where it enables half space clipping during streamout drawcalls and shadowing and then doesn't turn it off until the next GX2SetRasterizerClipControl call
		//	// this leads to some stuff being rendered at the wrong z-plane (e.g. shields behind characters) if the game's default depth range -1 to 1 isn't supported (on OpenGL only Nvidia's glDepthRangedNV allows unclamped values)
		//	uint64 titleId = gameMeta_getTitleId();
		//	if (titleId == 0x0005000010144F00ULL ||
		//		titleId == 0x0005000010145000ULL ||
		//		titleId == 0x0005000010110E00ULL)
		//	{
		//		// force disable half space clipping
		//		if (g_renderer && g_renderer->GetType() == RendererAPI::OpenGL && LatteGPUState.glVendor != GLVENDOR_NVIDIA)
		//			enableHalfZ = false;
		//	}
		//}

		Latte::LATTE_PA_CL_CLIP_CNTL reg{};
		reg.set_ZCLIP_NEAR_DISABLE(!enableZClip).set_ZCLIP_FAR_DISABLE(!enableZClip);
		reg.set_DX_RASTERIZATION_KILL(!enableRasterizer);
		reg.set_DX_CLIP_SPACE_DEF(enableHalfZ);
		reg.set_DX_LINEAR_ATTR_CLIP_ENA(true);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::PA_CL_CLIP_CNTL - 0xA000,
			reg);
	}

	void GX2SetRasterizerClipControl(bool enableRasterizer, bool enableZClip)
	{
		GX2SetRasterizerClipControlEx(enableRasterizer, enableZClip, false);
	}

	void GX2SetRasterizerClipControlHalfZ(bool enableRasterizer, bool enableZClip, bool enableHalfZ)
	{
		GX2SetRasterizerClipControlEx(enableRasterizer, enableZClip, enableHalfZ);
	}

	void GX2InitViewportReg(GX2ViewportReg* viewportReg, float x, float y, float width, float height, float nearZ, float farZ)
	{
		// todo: set clipping registers and zMin/zMax registers
		viewportReg->xScale = Latte::LATTE_PA_CL_VPORT_XSCALE().set_SCALE(width * 0.5f);
		viewportReg->xOffset = Latte::LATTE_PA_CL_VPORT_XOFFSET().set_OFFSET(x + (width * 0.5f));
		viewportReg->yScale = Latte::LATTE_PA_CL_VPORT_YSCALE().set_SCALE(height * -0.5f);
		viewportReg->yOffset = Latte::LATTE_PA_CL_VPORT_YOFFSET().set_OFFSET(y + (height * 0.5f));
		viewportReg->zScale = Latte::LATTE_PA_CL_VPORT_ZSCALE().set_SCALE((farZ - nearZ) * 0.5f);
		viewportReg->zOffset = Latte::LATTE_PA_CL_VPORT_ZOFFSET().set_OFFSET((nearZ + farZ) * 0.5f);
	}

	void GX2SetViewportReg(GX2ViewportReg* viewportReg)
	{
		GX2ReserveCmdSpace(2 + 6);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 6),
			Latte::REGADDR::PA_CL_VPORT_XSCALE - 0xA000,
			viewportReg->xScale, viewportReg->xOffset,
			viewportReg->yScale, viewportReg->yOffset,
			viewportReg->zScale, viewportReg->zOffset);
	}

	void GX2SetViewport(float x, float y, float width, float height, float nearZ, float farZ)
	{
		GX2ViewportReg viewportReg;
		GX2InitViewportReg(&viewportReg, x, y, width, height, nearZ, farZ);
		GX2SetViewportReg(&viewportReg);
	}

	void GX2InitScissorReg(GX2ScissorReg* scissorReg, uint32 x, uint32 y, uint32 width, uint32 height)
	{
		uint32 tlx = x;
		uint32 tly = y;
		uint32 brx = x + width;
		uint32 bry = y + height;

		tlx = std::min(tlx, 8192u);
		tly = std::min(tly, 8192u);
		brx = std::min(brx, 8192u);
		bry = std::min(bry, 8192u);

		scissorReg->scissorTL = Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL().set_TL_X(tlx).set_TL_Y(tly).set_WINDOW_OFFSET_DISABLE(true);
		scissorReg->scissorBR = Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR().set_BR_X(brx).set_BR_Y(bry);
	}

	void GX2SetScissorReg(GX2ScissorReg* scissorReg)
	{
		GX2ReserveCmdSpace(4);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2),
			Latte::REGADDR::PA_SC_GENERIC_SCISSOR_TL - 0xA000,
			scissorReg->scissorTL, scissorReg->scissorBR);
	}

	void GX2GetScissorReg(GX2ScissorReg* scissorReg, uint32be* x, uint32be* y, uint32be* width, uint32be* height)
	{
		*x = scissorReg->scissorTL.value().get_TL_X();
		*y = scissorReg->scissorTL.value().get_TL_Y();
		*width = scissorReg->scissorBR.value().get_BR_X() - scissorReg->scissorTL.value().get_TL_X();
		*height = scissorReg->scissorBR.value().get_BR_Y() - scissorReg->scissorTL.value().get_TL_Y();
	}

	void GX2SetScissor(uint32 x, uint32 y, uint32 width, uint32 height)
	{
		GX2ScissorReg scissorReg;
		GX2InitScissorReg(&scissorReg, x, y, width, height);
		GX2SetScissorReg(&scissorReg);
	}

	void GX2SetDepthOnlyControl(bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction)
	{
		// disables any currently set stencil test
		GX2ReserveCmdSpace(3);

		Latte::LATTE_DB_DEPTH_CONTROL reg{};
		reg.set_Z_ENABLE(depthTestEnable);
		reg.set_Z_WRITE_ENABLE(depthWriteEnable);
		reg.set_Z_FUNC(depthFunction);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000,
			reg);
	}

	void GX2SetDepthStencilControl(
		bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction,
		bool stencilTestEnable, bool backStencilTestEnable, 
		LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC frontStencilFunction, 
		LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilFail,
		LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC backStencilFunction, 
		LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilFail
	)
	{
		GX2ReserveCmdSpace(3);

		Latte::LATTE_DB_DEPTH_CONTROL reg{};
		reg.set_Z_ENABLE(depthTestEnable).set_Z_WRITE_ENABLE(depthWriteEnable).set_Z_FUNC(depthFunction);
		reg.set_STENCIL_ENABLE(stencilTestEnable).set_BACK_STENCIL_ENABLE(backStencilTestEnable);
		reg.set_STENCIL_FUNC_F(frontStencilFunction).set_STENCIL_FUNC_B(backStencilFunction);
		reg.set_STENCIL_ZPASS_F(frontStencilZPass).set_STENCIL_ZFAIL_F(frontStencilZFail).set_STENCIL_FAIL_F(frontStencilFail);
		reg.set_STENCIL_ZPASS_B(backStencilZPass).set_STENCIL_ZFAIL_B(backStencilZFail).set_STENCIL_FAIL_B(backStencilFail);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000,
			reg);
	}

	void GX2InitDepthStencilControlReg(
		GX2DepthStencilControlReg* depthStencilControlReg,
		bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction,
		bool stencilTestEnable, bool backStencilTestEnable,
		LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC frontStencilFunction,
		LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilFail,
		LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC backStencilFunction,
		LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilFail)
	{
		Latte::LATTE_DB_DEPTH_CONTROL reg{};
		reg.set_Z_ENABLE(depthTestEnable).set_Z_WRITE_ENABLE(depthWriteEnable).set_Z_FUNC(depthFunction);
		reg.set_STENCIL_ENABLE(stencilTestEnable).set_BACK_STENCIL_ENABLE(backStencilTestEnable);
		reg.set_STENCIL_FUNC_F(frontStencilFunction).set_STENCIL_FUNC_B(backStencilFunction);
		reg.set_STENCIL_ZPASS_F(frontStencilZPass).set_STENCIL_ZFAIL_F(frontStencilZFail).set_STENCIL_FAIL_F(frontStencilFail);
		reg.set_STENCIL_ZPASS_B(backStencilZPass).set_STENCIL_ZFAIL_B(backStencilZFail).set_STENCIL_FAIL_B(backStencilFail);
		depthStencilControlReg->reg = reg;
	}

	void GX2SetDepthStencilControlReg(GX2DepthStencilControlReg* depthStencilControlReg)
	{
		GX2ReserveCmdSpace(3);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000,
			depthStencilControlReg->reg);
	}

	void GX2GetDepthStencilControlReg(
		GX2DepthStencilControlReg* depthStencilControlReg,
		uint32be* depthTestEnable, uint32be* depthWriteEnable, uint32be* depthFunction,
		uint32be* stencilTestEnable, uint32be* backStencilTestEnable,
		uint32be* frontStencilFunction,
		uint32be* frontStencilZPass, uint32be* frontStencilZFail, uint32be* frontStencilFail,
		uint32be* backStencilFunction,
		uint32be* backStencilZPass, uint32be* backStencilZFail, uint32be* backStencilFail)
	{
		// used by Hyrule Warriors

		*depthTestEnable = depthStencilControlReg->reg.value().get_Z_ENABLE();

		*depthWriteEnable = depthStencilControlReg->reg.value().get_Z_WRITE_ENABLE();
		*depthFunction = (uint32)depthStencilControlReg->reg.value().get_Z_FUNC();

		*stencilTestEnable = depthStencilControlReg->reg.value().get_STENCIL_ENABLE();
		*backStencilTestEnable = depthStencilControlReg->reg.value().get_BACK_STENCIL_ENABLE();
	
		*frontStencilFunction = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FUNC_F();
		*backStencilFunction = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FUNC_B();

		*frontStencilZPass = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZPASS_F();
		*frontStencilZFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZFAIL_F();
		*frontStencilFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FAIL_F();

		*backStencilZPass = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZPASS_B();
		*backStencilZFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZFAIL_B();
		*backStencilFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FAIL_B();
	}

	void GX2InitStencilMaskReg(GX2StencilMaskReg* stencilMaskReg, uint8 compareMaskFront, uint8 writeMaskFront, uint8 refFront, uint8 compareMaskBack, uint8 writeMaskBack, uint8 refBack)
	{
		stencilMaskReg->stencilRefMaskFrontReg = LATTE_DB_STENCILREFMASK().set_STENCILREF_F(refFront).set_STENCILMASK_F(compareMaskFront).set_STENCILWRITEMASK_F(writeMaskFront);
		stencilMaskReg->stencilRefMaskBackReg = LATTE_DB_STENCILREFMASK_BF().set_STENCILREF_B(refBack).set_STENCILMASK_B(compareMaskBack).set_STENCILWRITEMASK_B(writeMaskBack);
	}

	void GX2SetStencilMask(uint8 compareMaskFront, uint8 writeMaskFront, uint8 refFront, uint8 compareMaskBack, uint8 writeMaskBack, uint8 refBack)
	{
		GX2ReserveCmdSpace(3 + 3);

		LATTE_DB_STENCILREFMASK frontReg;
		frontReg.set_STENCILREF_F(refFront).set_STENCILMASK_F(compareMaskFront).set_STENCILWRITEMASK_F(writeMaskFront);
		LATTE_DB_STENCILREFMASK_BF backReg;
		backReg.set_STENCILREF_B(refBack).set_STENCILMASK_B(compareMaskBack).set_STENCILWRITEMASK_B(writeMaskBack);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
							REGADDR::DB_STENCILREFMASK - 0xA000,
							frontReg,
							pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
							REGADDR::DB_STENCILREFMASK_BF - 0xA000,
							backReg);
	}

	void GX2SetStencilMaskReg(GX2StencilMaskReg* stencilMaskReg)
	{
		GX2ReserveCmdSpace(3 + 3);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			REGADDR::DB_STENCILREFMASK - 0xA000,
			stencilMaskReg->stencilRefMaskFrontReg,
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			REGADDR::DB_STENCILREFMASK_BF - 0xA000,
			stencilMaskReg->stencilRefMaskBackReg);
	}

	void GX2SetPrimitiveRestartIndex(uint32 restartIndex)
	{
		GX2ReserveCmdSpace(3);
		Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_INDX reg{};
		reg.set_RESTART_INDEX(restartIndex);
		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX - 0xA000,
			reg);
	}

	void GX2InitTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7)
	{
		uint32 targetMask = 0;
		targetMask |= ((t0 & 0xF) << 0);
		targetMask |= ((t1 & 0xF) << 4);
		targetMask |= ((t2 & 0xF) << 8);
		targetMask |= ((t3 & 0xF) << 12);
		targetMask |= ((t4 & 0xF) << 16);
		targetMask |= ((t5 & 0xF) << 20);
		targetMask |= ((t6 & 0xF) << 24);
		targetMask |= ((t7 & 0xF) << 28);
		Latte::LATTE_CB_TARGET_MASK r;
		r.set_MASK(targetMask);
		reg->reg = r;
	}

	void GX2SetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg)
	{
		GX2ReserveCmdSpace(3);

		gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::CB_TARGET_MASK - 0xA000,
			reg->reg);
	}

	void GX2SetTargetChannelMasks(GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7)
	{
		GX2TargetChannelMaskReg tmpReg;
		GX2InitTargetChannelMasksReg(&tmpReg, t0, t1, t2, t3, t4, t5, t6, t7);
		GX2SetTargetChannelMasksReg(&tmpReg);
	}

	static_assert(sizeof(GX2_CHANNELMASK) == 4);

	void GX2GetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, betype<GX2_CHANNELMASK>* t0, betype<GX2_CHANNELMASK>* t1, betype<GX2_CHANNELMASK>* t2, betype<GX2_CHANNELMASK>* t3,
									betype<GX2_CHANNELMASK>* t4, betype<GX2_CHANNELMASK>* t5, betype<GX2_CHANNELMASK>* t6, betype<GX2_CHANNELMASK>* t7)
	{
		uint32 maskValue = reg->reg.value().get_MASK();
		*t0 = (maskValue >> 0) & 0xF;
		*t1 = (maskValue >> 4) & 0xF;
		*t2 = (maskValue >> 8) & 0xF;
		*t3 = (maskValue >> 12) & 0xF;
		*t4 = (maskValue >> 16) & 0xF;
		*t5 = (maskValue >> 20) & 0xF;
		*t6 = (maskValue >> 24) & 0xF;
		*t7 = (maskValue >> 28) & 0xF;
	}

	void GX2InitBlendControlReg(GX2BlendControlReg* reg, uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc)
	{
		Latte::LATTE_CB_BLENDN_CONTROL tmpReg;
		tmpReg.set_COLOR_SRCBLEND(colorSrcFactor);
		tmpReg.set_COLOR_DSTBLEND(colorDstFactor);
		tmpReg.set_COLOR_COMB_FCN(colorCombineFunc);
		tmpReg.set_ALPHA_SRCBLEND(alphaSrcFactor);
		tmpReg.set_ALPHA_DSTBLEND(alphaDstFactor);
		tmpReg.set_ALPHA_COMB_FCN(alphaCombineFunc);
		tmpReg.set_SEPARATE_ALPHA_BLEND(separateAlphaBlend != 0);

		reg->index = renderTargetIndex;
		reg->reg = tmpReg;
	}

	void GX2SetBlendControlReg(GX2BlendControlReg* reg)
	{
		GX2ReserveCmdSpace(3);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			(Latte::REGADDR::CB_BLEND0_CONTROL + (uint32)reg->index) - 0xA000,
			reg->reg
		);
	}

	void GX2SetBlendControl(uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc)
	{
		GX2BlendControlReg tmpReg;
		GX2InitBlendControlReg(&tmpReg, renderTargetIndex, colorSrcFactor, colorDstFactor, colorCombineFunc, separateAlphaBlend, alphaSrcFactor, alphaDstFactor, alphaCombineFunc);
		GX2SetBlendControlReg(&tmpReg);
	}

	void GX2InitBlendConstantColorReg(GX2BlendConstantColorReg* reg, float red, float green, float blue, float alpha)
	{
		reg->regRed = Latte::LATTE_CB_BLEND_RED().set_RED(red);
		reg->regGreen = Latte::LATTE_CB_BLEND_GREEN().set_GREEN(green);
		reg->regBlue = Latte::LATTE_CB_BLEND_BLUE().set_BLUE(blue);
		reg->regAlpha = Latte::LATTE_CB_BLEND_ALPHA().set_ALPHA(alpha);
	}

	void GX2SetBlendConstantColorReg(GX2BlendConstantColorReg* reg)
	{
		GX2ReserveCmdSpace(6);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 4),
			Latte::REGADDR::CB_BLEND_RED - 0xA000,
			reg->regRed,
			reg->regGreen,
			reg->regBlue,
			reg->regAlpha
		);
	}

	void GX2SetBlendConstantColor(float red, float green, float blue, float alpha)
	{
		GX2BlendConstantColorReg tmpReg;
		GX2InitBlendConstantColorReg(&tmpReg, red, green, blue, alpha);
		GX2SetBlendConstantColorReg(&tmpReg);
	}

	void GX2InitHiStencilInfoRegs(GX2HiStencilInfoReg* hiStencilInfo)
	{
		// seen in Color Splash
		// but the game never calls GX2SetHiStencilInfo thus this has no effect
	}

	void GX2InitPointSizeReg(GX2PointSizeReg* reg, float width, float height)
	{
		if (width < 0.0f || height < 0.0f)
		{
			cemu_assert_suspicious();
		}

		uint32 widthI = (uint32)(width * 8.0f);
		uint32 heightI = (uint32)(height * 8.0f);

		widthI = std::min<uint32>(widthI, 0xFFFF);
		heightI = std::min<uint32>(heightI, 0xFFFF);

		reg->reg = Latte::LATTE_PA_SU_POINT_SIZE().set_WIDTH(widthI).set_HEIGHT(heightI);
	}

	void GX2SetPointSizeReg(GX2PointSizeReg* reg)
	{
		GX2ReserveCmdSpace(3);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::PA_SU_POINT_SIZE - 0xA000,
			reg->reg
		);
	}

	void GX2SetPointSize(float width, float height)
	{
		GX2PointSizeReg tmpReg;
		GX2InitPointSizeReg(&tmpReg, width, height);
		GX2SetPointSizeReg(&tmpReg);
	}

	void GX2InitPointLimitsReg(GX2PointLimitsReg* reg, float minSize, float maxSize)
	{
		if (minSize < 0.0f || maxSize < 0.0f)
		{
			cemu_assert_suspicious();
		}

		uint32 minSizeI = (uint32)(minSize * 8.0f);
		uint32 maxSizeI = (uint32)(maxSize * 8.0f);

		minSizeI = std::min<uint32>(minSizeI, 0xFFFF);
		maxSizeI = std::min<uint32>(maxSizeI, 0xFFFF);

		reg->reg = Latte::LATTE_PA_SU_POINT_MINMAX().set_MIN_SIZE(minSizeI).set_MAX_SIZE(maxSizeI);
	}

	void GX2SetPointLimitsReg(GX2PointLimitsReg* reg)
	{
		GX2ReserveCmdSpace(3);
		gx2WriteGather_submit(
			pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
			Latte::REGADDR::PA_SU_POINT_MINMAX - 0xA000,
			reg->reg
		);
	}

	void GX2SetPointLimits(float minSize, float maxSize)
	{
		GX2PointLimitsReg tmpReg;
		GX2InitPointLimitsReg(&tmpReg, minSize, maxSize);
		GX2SetPointLimitsReg(&tmpReg);
	}

	enum class GX2_SPECIAL_STATE : uint32
	{
		FAST_CLEAR = 0,
		FAST_CLEAR_HIZ = 1,
	};

	void _setSpecialState0(bool isEnabled)
	{
		GX2ReserveCmdSpace(6);
		if (isEnabled)
		{
			// set PA_CL_VTE_CNTL to 0x300
			Latte::LATTE_PA_CL_VTE_CNTL regVTE{};
			regVTE.set_VTX_XY_FMT(true);
			regVTE.set_VTX_Z_FMT(true);
			gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
				Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000,
				regVTE);
			// set PA_CL_CLIP_CNTL to 0x490000
			Latte::LATTE_PA_CL_CLIP_CNTL regClip{};
			regClip.set_CLIP_DISABLE(true); // 0x10000
			regClip.set_DX_CLIP_SPACE_DEF(true); // 0x80000
			regClip.set_DX_RASTERIZATION_KILL(true); // 0x400000
			
			gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
				Latte::REGADDR::PA_CL_CLIP_CNTL - 0xA000,
				regClip);
		}
		else
		{
			// set PA_CL_VTE_CNTL to 0x43F
			Latte::LATTE_PA_CL_VTE_CNTL reg{};
			reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true);
			reg.set_VPORT_Y_OFFSET_ENA(true).set_VPORT_Y_SCALE_ENA(true);
			reg.set_VPORT_Z_OFFSET_ENA(true).set_VPORT_Z_SCALE_ENA(true);
			reg.set_VTX_W0_FMT(true);
			gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
				Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000,
				reg);
			// reset PA_CL_CLIP_CNTL
			GX2SetRasterizerClipControl(true, true);
		}
	}

	void GX2SetSpecialState(GX2_SPECIAL_STATE stateId, uint32 isEnabled)
	{
		if (stateId == GX2_SPECIAL_STATE::FAST_CLEAR)
		{
			_setSpecialState0(isEnabled != 0);
		}
		else if (stateId == GX2_SPECIAL_STATE::FAST_CLEAR_HIZ)
		{
			// todo
			// enables additional flags for special state 0
		}
		else
		{
			// legacy style
			gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SPECIAL_STATE, 2));
			gx2WriteGather_submitU32AsBE((uint32)stateId); // state id
			gx2WriteGather_submitU32AsBE(isEnabled); // enable/disable bool
		}
	}

	void GX2StateInit()
	{
		cafeExportRegister("gx2", GX2InitAlphaTestReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetAlphaTestReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetAlphaTest, LogType::GX2);

		cafeExportRegister("gx2", GX2InitColorControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetColorControl, LogType::GX2);
		cafeExportRegister("gx2", GX2SetColorControlReg, LogType::GX2);

		cafeExportRegister("gx2", GX2InitPolygonControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPolygonControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPolygonControl, LogType::GX2);
		cafeExportRegister("gx2", GX2SetCullOnlyControl, LogType::GX2);

		cafeExportRegister("gx2", GX2InitPolygonOffsetReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPolygonOffsetReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPolygonOffset, LogType::GX2);

		cafeExportRegister("gx2", GX2SetRasterizerClipControl, LogType::GX2);
		cafeExportRegister("gx2", GX2SetRasterizerClipControlHalfZ, LogType::GX2);
		cafeExportRegister("gx2", GX2SetRasterizerClipControlEx, LogType::GX2);

		cafeExportRegister("gx2", GX2InitViewportReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetViewportReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetViewport, LogType::GX2);

		cafeExportRegister("gx2", GX2InitScissorReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetScissorReg, LogType::GX2);
		cafeExportRegister("gx2", GX2GetScissorReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetScissor, LogType::GX2);

		cafeExportRegister("gx2", GX2InitDepthStencilControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetDepthStencilControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2GetDepthStencilControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetDepthOnlyControl, LogType::GX2);
		cafeExportRegister("gx2", GX2SetDepthStencilControl, LogType::GX2);

		cafeExportRegister("gx2", GX2InitStencilMaskReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetStencilMask, LogType::GX2);
		cafeExportRegister("gx2", GX2SetStencilMaskReg, LogType::GX2);

		cafeExportRegister("gx2", GX2SetPrimitiveRestartIndex, LogType::GX2);

		cafeExportRegister("gx2", GX2InitTargetChannelMasksReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetTargetChannelMasksReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetTargetChannelMasks, LogType::GX2);
		cafeExportRegister("gx2", GX2GetTargetChannelMasksReg, LogType::GX2);

		cafeExportRegister("gx2", GX2InitBlendControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetBlendControlReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetBlendControl, LogType::GX2);

		cafeExportRegister("gx2", GX2InitBlendConstantColorReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetBlendConstantColorReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetBlendConstantColor, LogType::GX2);
		cafeExportRegister("gx2", GX2InitHiStencilInfoRegs, LogType::GX2);

		cafeExportRegister("gx2", GX2InitPointSizeReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPointSizeReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPointSize, LogType::GX2);
		cafeExportRegister("gx2", GX2InitPointLimitsReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPointLimitsReg, LogType::GX2);
		cafeExportRegister("gx2", GX2SetPointLimits, LogType::GX2);

		cafeExportRegister("gx2", GX2SetSpecialState, LogType::GX2);
	}

}
