#include "stdafx.h"
#include "rpcs3/Ini.h"
#include "Utilities/Log.h"
#include "Emu/Memory/Memory.h"
#include "Emu/System.h"
#include "Emu/RSX/GSManager.h"
#include "Emu/RSX/GSRender.h"
#include "Emu/SysCalls/Modules/cellVideoOut.h"
#include "RSXThread.h"

#include "Emu/SysCalls/Callback.h"
#include "Emu/SysCalls/CB_FUNC.h"

extern "C"
{
#include "libswscale/swscale.h"
}

extern u64 get_system_time();

#define ARGS(x) (x >= count ? OutOfArgsCount(x, cmd, count, args.addr()) : args[x].value())
#define CMD_DEBUG 0

u32 methodRegisters[0xffff];

u32 GetAddress(u32 offset, u32 location)
{
	u32 res = 0;

	switch (location)
	{
	case CELL_GCM_LOCATION_LOCAL:
	{
		res = 0xC0000000 + offset;
		break;
	}
	case CELL_GCM_LOCATION_MAIN:
	{
		res = RSXIOMem.RealAddr(offset); // TODO: Error Check?
		if (res == 0)
		{
			throw EXCEPTION("RSXIO memory not mapped (offset=0x%x)", offset);
		}

		if (Emu.GetGSManager().GetRender().m_strict_ordering[offset >> 20])
		{
			_mm_mfence(); // probably doesn't have any effect on current implementation
		}

		break;
	}
	default:
	{
		throw EXCEPTION("Invalid location (offset=0x%x, location=0x%x)", offset, location);
	}
	}

	return res;
}

RSXVertexData::RSXVertexData()
	: frequency(0)
	, stride(0)
	, size(0)
	, type(0)
	, addr(0)
	, data()
{
}

void RSXVertexData::Reset()
{
	frequency = 0;
	stride = 0;
	size = 0;
	type = 0;
	addr = 0;
	data.clear();
}

void RSXVertexData::Load(u32 start, u32 count, u32 baseOffset, u32 baseIndex = 0)
{
	if (!addr) return;

	const u32 tsize = GetTypeSize();

	data.resize((start + count) * tsize * size);

	for (u32 i = start; i < start + count; ++i)
	{
		auto src = vm::get_ptr<const u8>(addr + baseOffset + stride * (i + baseIndex));
		u8* dst = &data[i * tsize * size];

		switch (tsize)
		{
		case 1:
		{
			memcpy(dst, src, size);
			break;
		}

		case 2:
		{
			auto c_src = (const be_t<u16>*)src;
			auto c_dst = (u16*)dst;
			for (u32 j = 0; j < size; ++j) *c_dst++ = *c_src++;
			break;
		}

		case 4:
		{
			auto c_src = (const be_t<u32>*)src;
			auto c_dst = (u32*)dst;
			for (u32 j = 0; j < size; ++j) *c_dst++ = *c_src++;
			break;
		}
		}
	}
}

u32 RSXVertexData::GetTypeSize() const
{
	switch (type)
	{
	case CELL_GCM_VERTEX_S1:    return 2;
	case CELL_GCM_VERTEX_F:     return 4;
	case CELL_GCM_VERTEX_SF:    return 2;
	case CELL_GCM_VERTEX_UB:    return 1;
	case CELL_GCM_VERTEX_S32K:  return 2;
	case CELL_GCM_VERTEX_CMP:   return 4;
	case CELL_GCM_VERTEX_UB256: return 1;

	default:
		LOG_ERROR(RSX, "RSXVertexData::GetTypeSize: Bad vertex data type (%d)!", type);
		return 1;
	}
}

u32 RSXThread::OutOfArgsCount(const uint x, const u32 cmd, const u32 count, const u32 args_addr)
{
	auto args = vm::ptr<u32>::make(args_addr);
	std::string debug = GetMethodName(cmd);
	debug += "(";
	for (u32 i = 0; i < count; ++i) debug += (i ? ", " : "") + fmt::format("0x%x", ARGS(i));
	debug += ")";
	LOG_NOTICE(RSX, "OutOfArgsCount(x=%d, count=%d): %s", x, count, debug.c_str());

	return 0;
}

#define case_2(offset, step) \
    case offset: \
    case offset + step:
#define case_4(offset, step) \
    case_2(offset, step) \
    case_2(offset + 2*step, step)
#define case_8(offset, step) \
    case_4(offset, step) \
    case_4(offset + 4*step, step)
#define case_16(offset, step) \
    case_8(offset, step) \
    case_8(offset + 8*step, step)
#define case_32(offset, step) \
    case_16(offset, step) \
    case_16(offset + 16*step, step)
#define case_range(n, offset, step) \
    case_##n(offset, step) \
    index = (cmd - offset) / step

void RSXThread::DoCmd(const u32 fcmd, const u32 cmd, const u32 args_addr, const u32 count)
{
	auto args = vm::ptr<u32>::make(args_addr);

#if	CMD_DEBUG
	std::string debug = GetMethodName(cmd);
	debug += "(";
	for (u32 i = 0; i < count; ++i) debug += (i ? ", " : "") + fmt::format("0x%x", ARGS(i));
	debug += ")";
	LOG_NOTICE(RSX, debug);
#endif

	u32 index = 0;

	m_used_gcm_commands.insert(cmd);

	switch (cmd)
	{
	// NV406E
	case NV406E_SET_REFERENCE:
	{
		m_ctrl->ref.exchange(ARGS(0));
		break;
	}

	case NV406E_SET_CONTEXT_DMA_SEMAPHORE:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV406E_SET_CONTEXT_DMA_SEMAPHORE: 0x%x", ARGS(0));
		}
		break;
	}

	case NV4097_SET_SEMAPHORE_OFFSET:
	{
		m_PGRAPH_semaphore_offset = ARGS(0);
		break;
	}

	case NV406E_SEMAPHORE_OFFSET:
	{
		m_PFIFO_semaphore_offset = ARGS(0);
		break;
	}

	case NV406E_SEMAPHORE_ACQUIRE:
	{
		semaphorePFIFOAcquire(m_PFIFO_semaphore_offset, ARGS(0));
		break;
	}

	case NV406E_SEMAPHORE_RELEASE:
	{
		m_PFIFO_semaphore_release_value = ARGS(0);
		break;
	}

	case NV4097_TEXTURE_READ_SEMAPHORE_RELEASE:
	{
		semaphorePGRAPHTextureReadRelease(m_PGRAPH_semaphore_offset, ARGS(0));
		break;
	}

	case NV4097_BACK_END_WRITE_SEMAPHORE_RELEASE:
	{
		u32 value = ARGS(0);
		value = (value & 0xff00ff00) | ((value & 0xff) << 16) | ((value >> 16) & 0xff);
		semaphorePGRAPHBackendRelease(m_PGRAPH_semaphore_offset, value);
		break;
	}

	// NV4097
	case 0x0003fead:
	{
		Flip();

		m_last_flip_time = get_system_time();
		m_gcm_current_buffer = ARGS(0);
		m_read_buffer = true;
		m_flip_status = 0;

		if (auto cb = m_flip_handler)
		{
			Emu.GetCallbackManager().Async([=](CPUThread& cpu)
			{
				cb(static_cast<PPUThread&>(cpu), 1);
			});
		}

		m_sem_flip.post_and_wait();

		auto sync = [&]()
		{
			double limit;
			switch (Ini.GSFrameLimit.GetValue())
			{
			case 1: limit = 50.; break;
			case 2: limit = 59.94; break;
			case 3: limit = 30.; break;
			case 4: limit = 60.; break;
			case 5: limit = m_fps_limit; break; //TODO

			case 0:
			default:
				return;
			}

			std::this_thread::sleep_for(std::chrono::milliseconds((s64)(1000.0 / limit - m_timer_sync.GetElapsedTimeInMilliSec())));
			m_timer_sync.Start();
		};

		sync();

		//Emu.Pause();
		break;
	}

	case NV4097_NO_OPERATION:
	{
		// Nothing to do here 
		break;
	}

	case NV4097_SET_CONTEXT_DMA_REPORT:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CONTEXT_DMA_REPORT: 0x%x", ARGS(0));
			dma_report = ARGS(0);
		}
		break;
	}

	case NV4097_NOTIFY:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_NOTIFY: 0x%x", ARGS(0));
		}
		break;
	}

	case NV4097_WAIT_FOR_IDLE:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_WAIT_FOR_IDLE: 0x%x", ARGS(0));
		}
		break;
	}

	case NV4097_PM_TRIGGER:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_PM_TRIGGER: 0x%x", ARGS(0));
		}
		break;
	}

	// Texture
	case_range(16, NV4097_SET_TEXTURE_FORMAT, 0x20);
	case_range(16, NV4097_SET_TEXTURE_OFFSET, 0x20);
	case_range(16, NV4097_SET_TEXTURE_FILTER, 0x20);
	case_range(16, NV4097_SET_TEXTURE_ADDRESS, 0x20);
	case_range(16, NV4097_SET_TEXTURE_IMAGE_RECT, 32);
	case_range(16, NV4097_SET_TEXTURE_BORDER_COLOR, 0x20);
	case_range(16, NV4097_SET_TEXTURE_CONTROL0, 0x20);
	case_range(16, NV4097_SET_TEXTURE_CONTROL1, 0x20);
	{
		// Done using methodRegisters in RSXTexture.cpp
		break;
	}

	case_range(16, NV4097_SET_TEX_COORD_CONTROL, 4);
	{
		const u32 a0 = ARGS(0);
		u8 texMask2D = a0 & 1;
		u8 texMaskCentroid = (a0 >> 4) & 1;
		LOG_WARNING(RSX, "TODO: NV4097_SET_TEX_COORD_CONTROL(texMask2D=%d, texMaskCentroid=%d)", texMask2D, texMaskCentroid);
		break;
	}

	case_range(16, NV4097_SET_TEXTURE_CONTROL2, 4);
	{
		LOG_WARNING(RSX, "TODO: NV4097_SET_TEXTURE_CONTROL2");
		const u32 a0 = ARGS(0);
		// TODO: Use these
		u8 unknown = (a0 >> 8) & 0xFF;
		u8 iso = (a0 >> 6) & 1;
		u8 aniso = (a0 >> 7) & 1;
		u8 slope = a0 & 0x1F;
		break;
	}

	case_range(16, NV4097_SET_TEXTURE_CONTROL3, 4);
	{
		RSXTexture& tex = m_textures[index];
		const u32 a0 = ARGS(0);
		u32 pitch = a0 & 0xFFFFF;
		u16 depth = a0 >> 20;
		tex.SetControl3(depth, pitch);
		break;
	}

	// Vertex Texture
	case_range(4, NV4097_SET_VERTEX_TEXTURE_FORMAT, 0x20);
	case_range(4, NV4097_SET_VERTEX_TEXTURE_OFFSET, 0x20);
	case_range(4, NV4097_SET_VERTEX_TEXTURE_FILTER, 0x20);
	case_range(4, NV4097_SET_VERTEX_TEXTURE_ADDRESS, 0x20);
	case_range(4, NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT, 0x20);
	case_range(4, NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR, 0x20);
	case_range(4, NV4097_SET_VERTEX_TEXTURE_CONTROL0, 0x20);
	{
		// Done using methodRegisters in RSXTexture.cpp
		break;
	}

	case_range(4, NV4097_SET_VERTEX_TEXTURE_CONTROL3, 0x20);
	{
		RSXVertexTexture& tex = m_vertex_textures[index];
		const u32 a0 = ARGS(0);
		u32 pitch = a0 & 0xFFFFF;
		u16 depth = a0 >> 20;
		tex.SetControl3(depth, pitch);
		break;
	}

		// Vertex data
	case_range(16, NV4097_SET_VERTEX_DATA4UB_M, 4);
	{
		const u32 a0 = ARGS(0);
		u8 v0 = a0;
		u8 v1 = a0 >> 8;
		u8 v2 = a0 >> 16;
		u8 v3 = a0 >> 24;

		m_vertex_data[index].Reset();
		m_vertex_data[index].size = 4;
		m_vertex_data[index].type = CELL_GCM_VERTEX_UB;
		m_vertex_data[index].data.push_back(v0);
		m_vertex_data[index].data.push_back(v1);
		m_vertex_data[index].data.push_back(v2);
		m_vertex_data[index].data.push_back(v3);

		//LOG_WARNING(RSX, "NV4097_SET_VERTEX_DATA4UB_M: index = %d, v0 = 0x%x, v1 = 0x%x, v2 = 0x%x, v3 = 0x%x", index, v0, v1, v2, v3);
		break;
	}

	case_range(16, NV4097_SET_VERTEX_DATA2F_M, 8);
	{
		const u32 a0 = ARGS(0);
		const u32 a1 = ARGS(1);

		float v0 = (float&)a0;
		float v1 = (float&)a1;

		m_vertex_data[index].Reset();
		m_vertex_data[index].type = CELL_GCM_VERTEX_F;
		m_vertex_data[index].size = 2;
		u32 pos = m_vertex_data[index].data.size();
		m_vertex_data[index].data.resize(pos + sizeof(float) * 2);
		(float&)m_vertex_data[index].data[pos + sizeof(float) * 0] = v0;
		(float&)m_vertex_data[index].data[pos + sizeof(float) * 1] = v1;

		//LOG_WARNING(RSX, "NV4097_SET_VERTEX_DATA2F_M: index = %d, v0 = %f, v1 = %f", index, v0, v1);
		break;
	}

	case_range(16, NV4097_SET_VERTEX_DATA4F_M, 16);
	{
		const u32 a0 = ARGS(0);
		const u32 a1 = ARGS(1);
		const u32 a2 = ARGS(2);
		const u32 a3 = ARGS(3);

		float v0 = (float&)a0;
		float v1 = (float&)a1;
		float v2 = (float&)a2;
		float v3 = (float&)a3;

		m_vertex_data[index].Reset();
		m_vertex_data[index].type = CELL_GCM_VERTEX_F;
		m_vertex_data[index].size = 4;
		u32 pos = m_vertex_data[index].data.size();
		m_vertex_data[index].data.resize(pos + sizeof(float) * 4);
		(float&)m_vertex_data[index].data[pos + sizeof(float) * 0] = v0;
		(float&)m_vertex_data[index].data[pos + sizeof(float) * 1] = v1;
		(float&)m_vertex_data[index].data[pos + sizeof(float) * 2] = v2;
		(float&)m_vertex_data[index].data[pos + sizeof(float) * 3] = v3;

		//LOG_WARNING(RSX, "NV4097_SET_VERTEX_DATA4F_M: index = %d, v0 = %f, v1 = %f, v2 = %f, v3 = %f", index, v0, v1, v2, v3);
		break;
	}

	case_range(16, NV4097_SET_VERTEX_DATA_ARRAY_OFFSET, 4);
	{
		const u32 addr = GetAddress(ARGS(0) & 0x7fffffff, ARGS(0) >> 31);

		m_vertex_data[index].addr = addr;
		m_vertex_data[index].data.clear();

		//LOG_WARNING(RSX, "NV4097_SET_VERTEX_DATA_ARRAY_OFFSET: num=%d, addr=0x%x", index, addr);
		break;
	}

	case_range(16, NV4097_SET_VERTEX_DATA_ARRAY_FORMAT, 4);
	{
		const u32 a0 = ARGS(0);
		u16 frequency = a0 >> 16;
		u8 stride = (a0 >> 8) & 0xff;
		u8 size = (a0 >> 4) & 0xf;
		u8 type = a0 & 0xf;

		RSXVertexData& cv = m_vertex_data[index];
		cv.frequency = frequency;
		cv.stride = stride;
		cv.size = size;
		cv.type = type;

		//LOG_WARNING(RSX, "NV4097_SET_VERTEX_DATA_ARRAY_FORMAT: index=%d, frequency=%d, stride=%d, size=%d, type=%d", index, frequency, stride, size, type);

		break;
	}

	// Vertex Attribute
	case NV4097_SET_VERTEX_ATTRIB_INPUT_MASK:
	{
		if (u32 mask = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_VERTEX_ATTRIB_INPUT_MASK: 0x%x", mask);
		}

		//VertexData[0].prog.attributeInputMask = ARGS(0);
		break;
	}

	case NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK:
	{
		if (u32 mask = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK: 0x%x", mask);
		}

		//VertexData[0].prog.attributeOutputMask = ARGS(0);
		//FragmentData.prog.attributeInputMask = ARGS(0)/* & ~0x20*/;
		break;
	}

	// Color Mask
	case NV4097_SET_COLOR_MASK:
	{
		const u32 a0 = ARGS(0);

		m_set_color_mask = true;
		m_color_mask_a = a0 & 0x1000000 ? true : false;
		m_color_mask_r = a0 & 0x0010000 ? true : false;
		m_color_mask_g = a0 & 0x0000100 ? true : false;
		m_color_mask_b = a0 & 0x0000001 ? true : false;
		notifyRasterizerStateChange();
		break;
	}

	case NV4097_SET_COLOR_MASK_MRT:
	{
		if (u32 mask = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_COLOR_MASK_MRT: 0x%x", mask);
		}
		break;
	}

	// Alpha testing
	case NV4097_SET_ALPHA_TEST_ENABLE:
	{
		m_set_alpha_test = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_ALPHA_FUNC:
	{
		m_set_alpha_func = true;
		m_alpha_func = ARGS(0);

		if (count == 2)
		{
			m_set_alpha_ref = true;
			const u32 a1 = ARGS(1);
			m_alpha_ref = (float&)a1;
		}
		break;
	}

	case NV4097_SET_ALPHA_REF:
	{
		m_set_alpha_ref = true;
		const u32 a0 = ARGS(0);
		m_alpha_ref = (float&)a0;
		break;
	}

	// Cull face
	case NV4097_SET_CULL_FACE_ENABLE:
	{
		m_set_cull_face = ARGS(0) ? true : false;
		notifyRasterizerStateChange();
		break;
	}

	case NV4097_SET_CULL_FACE:
	{
		m_cull_face = ARGS(0);
		notifyRasterizerStateChange();
		break;
	}

	// Front face 
	case NV4097_SET_FRONT_FACE:
	{
		m_front_face = ARGS(0);
		notifyRasterizerStateChange();
		break;
	}

	// Blending
	case NV4097_SET_BLEND_ENABLE:
	{
		m_set_blend = ARGS(0) ? true : false;
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_BLEND_ENABLE_MRT:
	{
		m_set_blend_mrt1 = ARGS(0) & 0x02 ? true : false;
		m_set_blend_mrt2 = ARGS(0) & 0x04 ? true : false;
		m_set_blend_mrt3 = ARGS(0) & 0x08 ? true : false;
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_BLEND_FUNC_SFACTOR:
	{
		m_set_blend_sfactor = true;
		m_blend_sfactor_rgb = ARGS(0) & 0xffff;
		m_blend_sfactor_alpha = ARGS(0) >> 16;

		if (count == 2)
		{
			m_set_blend_dfactor = true;
			m_blend_dfactor_rgb = ARGS(1) & 0xffff;
			m_blend_dfactor_alpha = ARGS(1) >> 16;
		}
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_BLEND_FUNC_DFACTOR:
	{
		m_set_blend_dfactor = true;
		m_blend_dfactor_rgb = ARGS(0) & 0xffff;
		m_blend_dfactor_alpha = ARGS(0) >> 16;
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_BLEND_COLOR:
	{
		m_set_blend_color = true;
		m_blend_color_r = ARGS(0) & 0xff;
		m_blend_color_g = (ARGS(0) >> 8) & 0xff;
		m_blend_color_b = (ARGS(0) >> 16) & 0xff;
		m_blend_color_a = (ARGS(0) >> 24) & 0xff;
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_BLEND_COLOR2:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO : NV4097_SET_BLEND_COLOR2: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_BLEND_EQUATION:
	{
		m_set_blend_equation = true;
		m_blend_equation_rgb = ARGS(0) & 0xffff;
		m_blend_equation_alpha = ARGS(0) >> 16;
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_REDUCE_DST_COLOR:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_REDUCE_DST_COLOR: 0x%x", value);
		}
		break;
	}

	// Depth bound testing
	case NV4097_SET_DEPTH_BOUNDS_TEST_ENABLE:
	{
		m_set_depth_bounds_test = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_DEPTH_BOUNDS_MIN:
	{
		m_set_depth_bounds = true;
		const u32 a0 = ARGS(0);
		m_depth_bounds_min = (float&)a0;

		if (count == 2)
		{
			const u32 a1 = ARGS(1);
			m_depth_bounds_max = (float&)a1;
		}
		break;
	}

	case NV4097_SET_DEPTH_BOUNDS_MAX:
	{
		m_set_depth_bounds = true;
		const u32 a0 = ARGS(0);
		m_depth_bounds_max = (float&)a0;
		break;
	}

	// Viewport
	case NV4097_SET_VIEWPORT_HORIZONTAL:
	{
		m_set_viewport_horizontal = true;
		m_viewport_x = ARGS(0) & 0xffff;
		m_viewport_w = ARGS(0) >> 16;

		if (count == 2)
		{
			m_set_viewport_vertical = true;
			m_viewport_y = ARGS(1) & 0xffff;
			m_viewport_h = ARGS(1) >> 16;
		}

		//LOG_NOTICE(RSX, "NV4097_SET_VIEWPORT_HORIZONTAL: x=%d, y=%d, w=%d, h=%d", m_viewport_x, m_viewport_y, m_viewport_w, m_viewport_h);
		break;
	}

	case NV4097_SET_VIEWPORT_VERTICAL:
	{
		m_set_viewport_vertical = true;
		m_viewport_y = ARGS(0) & 0xffff;
		m_viewport_h = ARGS(0) >> 16;

		//LOG_NOTICE(RSX, "NV4097_SET_VIEWPORT_VERTICAL: y=%d, h=%d", m_viewport_y, m_viewport_h);
		break;
	}

	case NV4097_SET_VIEWPORT_SCALE:
	case NV4097_SET_VIEWPORT_OFFSET:
	{
		// Done in Vertex Shader
		break;
	}

	// Clipping
	case NV4097_SET_CLIP_MIN:
	{
		const u32 a0 = ARGS(0);
		const u32 a1 = ARGS(1);

		m_set_clip = true;
		m_clip_min = (float&)a0;
		m_clip_max = (float&)a1;

		//LOG_NOTICE(RSX, "NV4097_SET_CLIP_MIN: clip_min=%.01f, clip_max=%.01f", m_clip_min, m_clip_max);
		break;
	}

	case NV4097_SET_CLIP_MAX:
	{
		const u32 a0 = ARGS(0);

		m_set_clip = true;
		m_clip_max = (float&)a0;

		//LOG_NOTICE(RSX, "NV4097_SET_CLIP_MAX: clip_max=%.01f", m_clip_max);
		break;
	}

	// Depth testing
	case NV4097_SET_DEPTH_TEST_ENABLE:
	{
		m_set_depth_test = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_DEPTH_FUNC:
	{
		m_set_depth_func = true;
		m_depth_func = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_DEPTH_MASK:
	{
		m_set_depth_mask = true;
		m_depth_mask = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	// Polygon mode/offset
	case NV4097_SET_FRONT_POLYGON_MODE:
	{
		m_set_front_polygon_mode = true;
		m_front_polygon_mode = ARGS(0);
		break;
	}

	case NV4097_SET_BACK_POLYGON_MODE:
	{
		m_set_back_polygon_mode = true;
		m_back_polygon_mode = ARGS(0);
		break;
	}

	case NV4097_SET_POLY_OFFSET_FILL_ENABLE:
	{
		m_set_poly_offset_fill = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_POLY_OFFSET_LINE_ENABLE:
	{
		m_set_poly_offset_line = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_POLY_OFFSET_POINT_ENABLE:
	{
		m_set_poly_offset_point = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR:
	{
		m_set_depth_test = true;
		m_set_poly_offset_mode = true;

		const u32 a0 = ARGS(0);
		m_poly_offset_scale_factor = (float&)a0;

		if (count == 2)
		{
			const u32 a1 = ARGS(1);
			m_poly_offset_bias = (float&)a1;
		}
		break;
	}

	case NV4097_SET_POLYGON_OFFSET_BIAS:
	{
		m_set_depth_test = true;
		m_set_poly_offset_mode = true;

		const u32 a0 = ARGS(0);
		m_poly_offset_bias = (float&)a0;
		break;
	}

	case NV4097_SET_CYLINDRICAL_WRAP:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CYLINDRICAL_WRAP: 0x%x", ARGS(0));
		}
		break;
	}

	// Clearing
	case NV4097_CLEAR_ZCULL_SURFACE:
	{
		u32 a0 = ARGS(0);

		if (a0 & 0x01) m_clear_surface_z = m_clear_z;
		if (a0 & 0x02) m_clear_surface_s = m_clear_s;

		m_clear_surface_mask |= a0 & 0x3;
		break;
	}

	case NV4097_CLEAR_SURFACE:
	{
		const u32 a0 = ARGS(0);

		if (a0 & 0x01) m_clear_surface_z = m_clear_z;
		if (a0 & 0x02) m_clear_surface_s = m_clear_s;
		if (a0 & 0x10) m_clear_surface_color_r = m_clear_color_r;
		if (a0 & 0x20) m_clear_surface_color_g = m_clear_color_g;
		if (a0 & 0x40) m_clear_surface_color_b = m_clear_color_b;
		if (a0 & 0x80) m_clear_surface_color_a = m_clear_color_a;

		m_clear_surface_mask = a0;
		Clear(NV4097_CLEAR_SURFACE);
		break;
	}

	case NV4097_SET_ZSTENCIL_CLEAR_VALUE:
	{
		const u32 value = ARGS(0);
		m_clear_s = value & 0xff;
		m_clear_z = value >> 8;
		break;
	}

	case NV4097_SET_COLOR_CLEAR_VALUE:
	{
		const u32 color = ARGS(0);
		m_clear_color_a = (color >> 24) & 0xff;
		m_clear_color_r = (color >> 16) & 0xff;
		m_clear_color_g = (color >> 8) & 0xff;
		m_clear_color_b = color & 0xff;
		break;
	}

	case NV4097_SET_CLEAR_RECT_HORIZONTAL:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CLEAR_RECT_HORIZONTAL: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_CLEAR_RECT_VERTICAL:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CLEAR_RECT_VERTICAL: 0x%x", value);
		}
		break;
	}

	// Arrays
	case NV4097_INLINE_ARRAY:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_INLINE_ARRAY: 0x%x", value);
		}
		break;
	}

	case NV4097_DRAW_ARRAYS:
	{
		for (u32 c = 0; c<count; ++c)
		{
			u32 ac = ARGS(c);
			const u32 first = ac & 0xffffff;
			const u32 _count = (ac >> 24) + 1;

			//LOG_WARNING(RSX, "NV4097_DRAW_ARRAYS: %d - %d", first, _count);

			if (first < m_draw_array_first)
			{
				m_draw_array_first = first;
			}

			m_draw_array_count += _count;
		}
		break;
	}

	case NV4097_SET_INDEX_ARRAY_ADDRESS:
	{
		m_indexed_array.m_addr = GetAddress(ARGS(0), ARGS(1) & 0xf);
		m_indexed_array.m_type = ARGS(1) >> 4;
		break;
	}

	case NV4097_DRAW_INDEX_ARRAY:
	{
		for (u32 c=0; c<count; ++c)
		{
			const u32 first = ARGS(c) & 0xffffff;
			const u32 _count = (ARGS(c) >> 24) + 1;

			if (first < m_indexed_array.m_first) m_indexed_array.m_first = first;

			int pos = (int)m_indexed_array.m_data.size();

			switch (m_indexed_array.m_type)
			{
			case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32:
				m_indexed_array.m_data.resize(pos + 4 * _count);
				break;
			case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16:
				m_indexed_array.m_data.resize(pos + 2 * _count);
				break;
			}

			for (u32 i=first; i< first + _count; ++i)
			{
				u32 index;
				switch(m_indexed_array.m_type)
				{
				case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_32:
					index = vm::read32(m_indexed_array.m_addr + i * 4);
					*(u32*)&m_indexed_array.m_data[i * 4] = index;
					break;

				case CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16:
					index = vm::read16(m_indexed_array.m_addr + i * 2);
					*(u16*)&m_indexed_array.m_data[i * 2] = index;
					break;
				}

				if (index < m_indexed_array.index_min) m_indexed_array.index_min = index;
				if (index > m_indexed_array.index_max) m_indexed_array.index_max = index;
			}

			m_indexed_array.m_count += _count;
		}
		break;
	}

	case NV4097_SET_VERTEX_DATA_BASE_OFFSET:
	{
		m_vertex_data_base_offset = ARGS(0);

		if (count >= 2)
		{
			m_vertex_data_base_index = ARGS(1);
		}

		//LOG_WARNING(RSX, "NV4097_SET_VERTEX_DATA_BASE_OFFSET: 0x%x", m_vertex_data_base_offset);
		break;
	}

	case NV4097_SET_VERTEX_DATA_BASE_INDEX:
	{
		m_vertex_data_base_index = ARGS(0);
		break;
	}

	case NV4097_SET_BEGIN_END:
	{
		const u32 a0 = ARGS(0);

		//LOG_WARNING(RSX, "NV4097_SET_BEGIN_END: 0x%x", a0);

		if (!m_indexed_array.m_count && !m_draw_array_count)
		{
			u32 min_vertex_size = ~0;
			for (auto &i : m_vertex_data)
			{
				if (!i.size)
					continue;

				u32 vertex_size = i.data.size() / (i.size * i.GetTypeSize());

				if (min_vertex_size > vertex_size)
					min_vertex_size = vertex_size;
			}

			m_draw_array_count = min_vertex_size;
			m_draw_array_first = 0;
		}

		m_read_buffer = Ini.GSReadColorBuffer.GetValue() || (!m_indexed_array.m_count && !m_draw_array_count);

		if (a0)
		{
			Begin(a0);
		}
		else
		{
			End();
		}
		break;
	}

	// Shader
	case NV4097_SET_SHADER_PROGRAM:
	{
		m_cur_fragment_prog = &m_fragment_progs[m_cur_fragment_prog_num];

		const u32 a0 = ARGS(0);
		m_cur_fragment_prog->offset = a0 & ~0x3;
		m_cur_fragment_prog->addr = GetAddress(m_cur_fragment_prog->offset, (a0 & 0x3) - 1);
		m_cur_fragment_prog->ctrl = 0x40;
		notifyProgramChange();
		break;
	}

	case NV4097_SET_SHADER_CONTROL:
	{
		m_shader_ctrl = ARGS(0);
		break;
	}

	case NV4097_SET_SHADE_MODE:
	{
		m_set_shade_mode = true;
		m_shade_mode = ARGS(0);
		break;
	}

	case NV4097_SET_SHADER_PACKER:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_SHADER_PACKER: 0x%x", ARGS(0));
		}
	}

	case NV4097_SET_SHADER_WINDOW:
	{
		const u32 a0 = ARGS(0);
		m_shader_window_height = a0 & 0xfff;
		m_shader_window_origin = (a0 >> 12) & 0xf;
		m_shader_window_pixel_centers = a0 >> 16;
		break;
	}

	// Transform 
	case NV4097_SET_TRANSFORM_PROGRAM_LOAD:
	{
		//LOG_WARNING(RSX, "NV4097_SET_TRANSFORM_PROGRAM_LOAD: prog = %d", ARGS(0));

		m_cur_vertex_prog = &m_vertex_progs[ARGS(0)];
		m_cur_vertex_prog->data.clear();

		if (count == 2)
		{
			const u32 start = ARGS(1);
			if (start)
			{
				LOG_WARNING(RSX, "NV4097_SET_TRANSFORM_PROGRAM_LOAD: start = %d", start);
			}
		}
		notifyProgramChange();
		break;
	}

	case NV4097_SET_TRANSFORM_PROGRAM_START:
	{
		const u32 start = ARGS(0);
		if (start)
		{
			LOG_WARNING(RSX, "NV4097_SET_TRANSFORM_PROGRAM_START: start = %d", start);
		}
		break;
	}

	case_range(32, NV4097_SET_TRANSFORM_PROGRAM, 4);
	{
		//LOG_WARNING(RSX, "NV4097_SET_TRANSFORM_PROGRAM[%d](%d)", index, count);

		if (!m_cur_vertex_prog)
		{
			LOG_ERROR(RSX, "NV4097_SET_TRANSFORM_PROGRAM: m_cur_vertex_prog is null");
			break;
		}

		for (u32 i = 0; i < count; ++i)
		{
			m_cur_vertex_prog->data.push_back(ARGS(i));
		}
		notifyProgramChange();
		break;
	}

	case NV4097_SET_TRANSFORM_TIMEOUT:
	{
		// TODO:
		// (cmd)[1] = CELL_GCM_ENDIAN_SWAP((count) | ((registerCount) << 16)); \

		if (!m_cur_vertex_prog)
		{
			LOG_ERROR(RSX, "NV4097_SET_TRANSFORM_TIMEOUT: m_cur_vertex_prog is null");
			break;
		}

		//m_cur_vertex_prog->Decompile();
		break;
	}

	case NV4097_SET_TRANSFORM_BRANCH_BITS:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_TRANSFORM_BRANCH_BITS: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_TRANSFORM_CONSTANT_LOAD:
	{
		if ((count - 1) % 4)
		{
			LOG_ERROR(RSX, "NV4097_SET_TRANSFORM_CONSTANT_LOAD: bad count %d", count);
			break;
		}

		for (u32 id = ARGS(0), i = 1; i<count; ++id)
		{
			const u32 x = ARGS(i); i++;
			const u32 y = ARGS(i); i++;
			const u32 z = ARGS(i); i++;
			const u32 w = ARGS(i); i++;

			RSXTransformConstant c(id, (float&)x, (float&)y, (float&)z, (float&)w);

			m_transform_constants.push_back(c);

			//LOG_NOTICE(RSX, "NV4097_SET_TRANSFORM_CONSTANT_LOAD: [%d : %d] = (%f, %f, %f, %f)", i, id, c.x, c.y, c.z, c.w);
		}
		break;
	}

	// Invalidation
	case NV4097_INVALIDATE_L2:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_INVALIDATE_L2: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_NO_PARANOID_TEXTURE_FETCHES:
	{
		// Nothing to do here
		break;
	}

	case NV4097_INVALIDATE_VERTEX_CACHE_FILE:
	{
		// Nothing to do here
		break;
	}

	case NV4097_INVALIDATE_VERTEX_FILE:
	{
		// Nothing to do here
		break;
	}

	case NV4097_INVALIDATE_ZCULL:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_INVALIDATE_ZCULL: 0x%x", value);
		}
		break;
	}

	// Logic Ops
	case NV4097_SET_LOGIC_OP_ENABLE:
	{
		m_set_logic_op = ARGS(0) ? true : false;
		notifyBlendStateChange();
		break;
	}

	case NV4097_SET_LOGIC_OP:
	{
		m_logic_op = ARGS(0);
		notifyBlendStateChange();
		break;
	}

	// Dithering
	case NV4097_SET_DITHER_ENABLE:
	{
		m_set_dither = ARGS(0) ? true : false;
		break;
	}

	// Stencil testing
	case NV4097_SET_STENCIL_TEST_ENABLE:
	{
		m_set_stencil_test = ARGS(0) ? true : false;
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_TWO_SIDED_STENCIL_TEST_ENABLE:
	{
		m_set_two_sided_stencil_test_enable = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_TWO_SIDE_LIGHT_EN:
	{
		m_set_two_side_light_enable = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_STENCIL_MASK:
	{
		m_set_stencil_mask = true;
		m_stencil_mask = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_STENCIL_FUNC:
	{
		m_set_stencil_func = true;
		m_stencil_func = ARGS(0);

		if (count >= 2)
		{
			m_set_stencil_func_ref = true;
			m_stencil_func_ref = ARGS(1);

			if (count >= 3)
			{
				m_set_stencil_func_mask = true;
				m_stencil_func_mask = ARGS(2);
			}
		}
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_STENCIL_FUNC_REF:
	{
		m_set_stencil_func_ref = true;
		m_stencil_func_ref = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_STENCIL_FUNC_MASK:
	{
		m_set_stencil_func_mask = true;
		m_stencil_func_mask = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_STENCIL_OP_FAIL:
	{
		m_set_stencil_fail = true;
		m_stencil_fail = ARGS(0);

		if (count >= 2)
		{
			m_set_stencil_zfail = true;
			m_stencil_zfail = ARGS(1);

			if (count >= 3)
			{
				m_set_stencil_zpass = true;
				m_stencil_zpass = ARGS(2);
			}
		}
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_BACK_STENCIL_MASK:
	{
		m_set_back_stencil_mask = true;
		m_back_stencil_mask = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_BACK_STENCIL_FUNC:
	{
		m_set_back_stencil_func = true;
		m_back_stencil_func = ARGS(0);

		if (count >= 2)
		{
			m_set_back_stencil_func_ref = true;
			m_back_stencil_func_ref = ARGS(1);

			if (count >= 3)
			{
				m_set_back_stencil_func_mask = true;
				m_back_stencil_func_mask = ARGS(2);
			}
		}
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_BACK_STENCIL_FUNC_REF:
	{
		m_set_back_stencil_func_ref = true;
		m_back_stencil_func_ref = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_BACK_STENCIL_FUNC_MASK:
	{
		m_set_back_stencil_func_mask = true;
		m_back_stencil_func_mask = ARGS(0);
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_BACK_STENCIL_OP_FAIL:
	{
		m_set_stencil_fail = true;
		m_stencil_fail = ARGS(0);

		if (count >= 2)
		{
			m_set_back_stencil_zfail = true;
			m_back_stencil_zfail = ARGS(1);

			if (count >= 3)
			{
				m_set_back_stencil_zpass = true;
				m_back_stencil_zpass = ARGS(2);
			}
		}
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_SCULL_CONTROL:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_SCULL_CONTROL: 0x%x", value);
		}
		break;
	}

	// Primitive restart index
	case NV4097_SET_RESTART_INDEX_ENABLE:
	{
		m_set_restart_index = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_RESTART_INDEX:
	{
		m_restart_index = ARGS(0);
		break;
	}

	// Point size
	case NV4097_SET_POINT_SIZE:
	{
		m_set_point_size = true;
		const u32 a0 = ARGS(0);
		m_point_size = (float&)a0;
		break;
	}

	// Point sprite
	case NV4097_SET_POINT_PARAMS_ENABLE:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_POINT_PARAMS_ENABLE: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_POINT_SPRITE_CONTROL:
	{
		m_set_point_sprite_control = ARGS(0) ? true : false;

		// TODO:
		//(cmd)[1] = CELL_GCM_ENDIAN_SWAP((enable) | ((rmode) << 1) | (texcoordMask));
		break;
	}

	// Lighting
	case NV4097_SET_SPECULAR_ENABLE:
	{
		m_set_specular = ARGS(0) ? true : false;
		break;
	}

	// Scissor
	case NV4097_SET_SCISSOR_HORIZONTAL:
	{
		m_set_scissor_horizontal = true;
		m_scissor_x = ARGS(0) & 0xffff;
		m_scissor_w = ARGS(0) >> 16;

		if (count == 2)
		{
			m_set_scissor_vertical = true;
			m_scissor_y = ARGS(1) & 0xffff;
			m_scissor_h = ARGS(1) >> 16;
		}
		break;
	}

	case NV4097_SET_SCISSOR_VERTICAL:
	{
		m_set_scissor_vertical = true;
		m_scissor_y = ARGS(0) & 0xffff;
		m_scissor_h = ARGS(0) >> 16;
		break;
	}

	// Depth/Color buffer usage 
	case NV4097_SET_SURFACE_FORMAT:
	{
		const u32 a0 = ARGS(0);
		m_set_surface_format = true;
		m_surface_color_format = a0 & 0x1f;
		m_surface_depth_format = (a0 >> 5) & 0x7;
		m_surface_type = (a0 >> 8) & 0xf;
		m_surface_antialias = (a0 >> 12) & 0xf;
		m_surface_width = (a0 >> 16) & 0xff;
		m_surface_height = (a0 >> 24) & 0xff;

		switch (std::min((u32)6, count))
		{
		case 6: m_surface_pitch_b  = ARGS(5);
		case 5: m_surface_offset_b = ARGS(4);
		case 4: m_surface_offset_z = ARGS(3);
		case 3: m_surface_offset_a = ARGS(2);
		case 2: m_surface_pitch_a  = ARGS(1);
		}

		auto buffers = vm::get_ptr<CellGcmDisplayInfo>(m_gcm_buffers_addr);
		m_width = buffers[m_gcm_current_buffer].width;
		m_height = buffers[m_gcm_current_buffer].height;

		CellVideoOutResolution res = ResolutionTable[ResolutionIdToNum(Ini.GSResolution.GetValue())];
		m_width_scale = (float)res.width / m_width  * 2.0f;
		m_height_scale = (float)res.height / m_height * 2.0f;
		m_width = (u32)res.width;
		m_height = (u32)res.height;
		break;
	}

	case NV4097_SET_SURFACE_COLOR_TARGET:
	{
		m_surface_color_target = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_COLOR_AOFFSET:
	{
		m_surface_offset_a = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_COLOR_BOFFSET:
	{
		m_surface_offset_b = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_COLOR_COFFSET:
	{
		m_surface_offset_c = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_COLOR_DOFFSET:
	{
		m_surface_offset_d = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_ZETA_OFFSET:
	{
		m_surface_offset_z = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_PITCH_A:
	{
		m_surface_pitch_a = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_PITCH_B:
	{
		m_surface_pitch_b = ARGS(0);
		break;
	}

	case NV4097_SET_SURFACE_PITCH_C:
	{
		if (count != 4)
		{
			LOG_ERROR(RSX, "NV4097_SET_SURFACE_PITCH_C: Bad count (%d)", count);
			break;
		}

		m_surface_pitch_c = ARGS(0);
		m_surface_pitch_d = ARGS(1);
		m_surface_offset_c = ARGS(2);
		m_surface_offset_d = ARGS(3);
		break;
	}

	case NV4097_SET_SURFACE_PITCH_D:
	{
		m_surface_pitch_d = ARGS(0);

		if (count != 1)
		{
			LOG_ERROR(RSX, "NV4097_SET_SURFACE_PITCH_D: Bad count (%d)", count);
			break;
		}
		break;
	}

	case NV4097_SET_SURFACE_PITCH_Z:
	{
		m_surface_pitch_z = ARGS(0);

		if (count != 1)
		{
			LOG_ERROR(RSX, "NV4097_SET_SURFACE_PITCH_Z: Bad count (%d)", count);
			break;
		}
		break;
	}

	case NV4097_SET_CONTEXT_DMA_COLOR_A:
	{
		m_set_context_dma_color_a = true;
		m_context_dma_color_a = ARGS(0);

		if (count != 1)
		{
			LOG_ERROR(RSX, "NV4097_SET_CONTEXT_DMA_COLOR_A: Bad count (%d)", count);
			break;
		}
		break;
	}

	case NV4097_SET_CONTEXT_DMA_COLOR_B:
	{
		m_set_context_dma_color_b = true;
		m_context_dma_color_b = ARGS(0);
		break;
	}

	case NV4097_SET_CONTEXT_DMA_COLOR_C:
	{
		m_set_context_dma_color_c = true;
		m_context_dma_color_c = ARGS(0);

		if (count > 1)
		{
			m_set_context_dma_color_d = true;
			m_context_dma_color_d = ARGS(1);
		}
		break;
	}

	case NV4097_SET_CONTEXT_DMA_COLOR_D:
	{
		if (ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CONTEXT_DMA_COLOR_D: 0x%x", ARGS(0));
		}
		break;
	}

	case NV4097_SET_CONTEXT_DMA_ZETA:
	{
		m_set_context_dma_z = true;
		m_context_dma_z = ARGS(0);
		break;
	}

	case NV4097_SET_CONTEXT_DMA_SEMAPHORE:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CONTEXT_DMA_SEMAPHORE: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_CONTEXT_DMA_NOTIFIES:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_CONTEXT_DMA_NOTIFIES: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_SURFACE_CLIP_HORIZONTAL:
	{
		const u32 a0 = ARGS(0);

		m_set_surface_clip_horizontal = true;
		m_surface_clip_x = a0;
		m_surface_clip_w = a0 >> 16;

		if (count == 2)
		{
			const u32 a1 = ARGS(1);
			m_set_surface_clip_vertical = true;
			m_surface_clip_y = a1;
			m_surface_clip_h = a1 >> 16;
		}
		break;
	}

	case NV4097_SET_SURFACE_CLIP_VERTICAL:
	{
		const u32 a0 = ARGS(0);
		m_set_surface_clip_vertical = true;
		m_surface_clip_y = a0;
		m_surface_clip_h = a0 >> 16;
		break;
	}

	// Anti-aliasing
	case NV4097_SET_ANTI_ALIASING_CONTROL:
	{
		const u32 a0 = ARGS(0);

		const u8 enable = a0 & 0xf;
		const u8 alphaToCoverage = (a0 >> 4) & 0xf;
		const u8 alphaToOne = (a0 >> 8) & 0xf;
		const u16 sampleMask = a0 >> 16;

		if (a0)
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_ANTI_ALIASING_CONTROL: 0x%x", a0);
		}
		break;
	}

	// Line/Polygon smoothing
	case NV4097_SET_LINE_SMOOTH_ENABLE:
	{
		m_set_line_smooth = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_POLY_SMOOTH_ENABLE:
	{
		m_set_poly_smooth = ARGS(0) ? true : false;
		break;
	}

	// Line width
	case NV4097_SET_LINE_WIDTH:
	{
		m_set_line_width = true;
		const u32 a0 = ARGS(0);
		m_line_width = (float)a0 / 8.0f;
		break;
	}

	// Line/Polygon stipple
	case NV4097_SET_LINE_STIPPLE:
	{
		m_set_line_stipple = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_LINE_STIPPLE_PATTERN:
	{
		m_set_line_stipple = true;
		const u32 a0 = ARGS(0);
		m_line_stipple_factor = a0 & 0xffff;
		m_line_stipple_pattern = a0 >> 16;
		break;
	}

	case NV4097_SET_POLYGON_STIPPLE:
	{
		m_set_polygon_stipple = ARGS(0) ? true : false;
		break;
	}

	case NV4097_SET_POLYGON_STIPPLE_PATTERN:
	{
		for (u32 i = 0; i < 32; i++)
		{
			m_polygon_stipple_pattern[i] = ARGS(i);
		}
		break;
	}

	// Zcull
	case NV4097_SET_ZCULL_EN:
	{
		const u32 a0 = ARGS(0);

		m_set_depth_test = a0 & 0x1 ? true : false;
		m_set_stencil_test = a0 & 0x2 ? true : false;
		notifyDepthStencilStateChange();
		break;
	}

	case NV4097_SET_ZCULL_CONTROL0:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_ZCULL_CONTROL0: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_ZCULL_CONTROL1:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_ZCULL_CONTROL1: 0x%x", value);
		}
		break;
	}

	case NV4097_SET_ZCULL_STATS_ENABLE:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_SET_ZCULL_STATS_ENABLE: 0x%x", value);
		}
		break;
	}

	case NV4097_ZCULL_SYNC:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV4097_ZCULL_SYNC: 0x%x", value);
		}
		break;
	}

	// Reports
	case NV4097_GET_REPORT:
	{
		const u32 a0 = ARGS(0);
		u8 type = a0 >> 24;
		u32 offset = a0 & 0xffffff;

		u32 value;
		switch(type)
		{
		case CELL_GCM_ZPASS_PIXEL_CNT:
		case CELL_GCM_ZCULL_STATS:
		case CELL_GCM_ZCULL_STATS1:
		case CELL_GCM_ZCULL_STATS2:
		case CELL_GCM_ZCULL_STATS3:
			value = 0;
			LOG_WARNING(RSX, "NV4097_GET_REPORT: Unimplemented type %d", type);
			break;

		default:
			value = 0;
			LOG_ERROR(RSX, "NV4097_GET_REPORT: Bad type %d", type);
			break;
		}

		// Get timestamp, and convert it from microseconds to nanoseconds
		u64 timestamp = get_system_time() * 1000;

		// NOTE: DMA broken, implement proper lpar mapping (sys_rsx)
		//dma_write64(dma_report, offset + 0x0, timestamp);
		//dma_write32(dma_report, offset + 0x8, value);
		//dma_write32(dma_report, offset + 0xc, 0);

		vm::write64(m_local_mem_addr + offset + 0x0, timestamp);
		vm::write32(m_local_mem_addr + offset + 0x8, value);
		vm::write32(m_local_mem_addr + offset + 0xc, 0);
		break;
	}

	case NV4097_CLEAR_REPORT_VALUE:
	{
		const u32 type = ARGS(0);

		switch (type)
		{
		case CELL_GCM_ZPASS_PIXEL_CNT:
			LOG_WARNING(RSX, "TODO: NV4097_CLEAR_REPORT_VALUE: ZPASS_PIXEL_CNT");
			break;
		case CELL_GCM_ZCULL_STATS:
			LOG_WARNING(RSX, "TODO: NV4097_CLEAR_REPORT_VALUE: ZCULL_STATS");
			break;
		default:
			LOG_ERROR(RSX, "NV4097_CLEAR_REPORT_VALUE: Bad type: %d", type);
			break;
		}
		break;
	}

	// Clip Plane
	case NV4097_SET_USER_CLIP_PLANE_CONTROL:
	{
		const u32 a0 = ARGS(0);
		m_set_clip_plane = true;
		m_clip_plane_0 = (a0 & 0xf) ? true : false;
		m_clip_plane_1 = ((a0 >> 4)) & 0xf ? true : false;
		m_clip_plane_2 = ((a0 >> 8)) & 0xf ? true : false;
		m_clip_plane_3 = ((a0 >> 12)) & 0xf ? true : false;
		m_clip_plane_4 = ((a0 >> 16)) & 0xf ? true : false;
		m_clip_plane_5 = (a0 >> 20) ? true : false;
		break;
	}

	// Fog
	case NV4097_SET_FOG_MODE:
	{
		m_set_fog_mode = true;
		m_fog_mode = ARGS(0);
		break;
	}

	case NV4097_SET_FOG_PARAMS:
	{
		m_set_fog_params = true;
		const u32 a0 = ARGS(0);
		const u32 a1 = ARGS(1);
		m_fog_param0 = (float&)a0;
		m_fog_param1 = (float&)a1;
		break;
	}

	// Zmin_max
	case NV4097_SET_ZMIN_MAX_CONTROL:
	{
		const u8 cullNearFarEnable = ARGS(0) & 0xf;
		const u8 zclampEnable = (ARGS(0) >> 4) & 0xf;
		const u8 cullIgnoreW = (ARGS(0) >> 8) & 0xf;

		LOG_WARNING(RSX, "TODO: NV4097_SET_ZMIN_MAX_CONTROL: cullNearFarEnable=%d, zclampEnable=%d, cullIgnoreW=%d", cullNearFarEnable, zclampEnable, cullIgnoreW);
		break;
	}

	case NV4097_SET_WINDOW_OFFSET:
	{
		const u16 x = ARGS(0);
		const u16 y = ARGS(0) >> 16;

		LOG_WARNING(RSX, "TODO: NV4097_SET_WINDOW_OFFSET: x=%d, y=%d", x, y);
		break;
	}

	case NV4097_SET_FREQUENCY_DIVIDER_OPERATION:
	{
		m_set_frequency_divider_operation = ARGS(0);

		LOG_WARNING(RSX, "TODO: NV4097_SET_FREQUENCY_DIVIDER_OPERATION: %d", m_set_frequency_divider_operation);
		break;
	}

	case NV4097_SET_RENDER_ENABLE:
	{
		const u32 offset = ARGS(0) & 0xffffff;
		const u8 mode = ARGS(0) >> 24;

		LOG_WARNING(RSX, "TODO: NV4097_SET_RENDER_ENABLE: Offset=0x%06x, Mode=0x%x", offset, mode);
		break;
	}

	case NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE:
	{
		const u32 enable = ARGS(0);

		LOG_WARNING(RSX, "TODO: NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE: %d", enable);
		break;
	}

	// NV0039
	case NV0039_SET_CONTEXT_DMA_BUFFER_IN:
	{
		const u32 srcContext = ARGS(0);
		const u32 dstContext = ARGS(1);
		m_context_dma_buffer_in_src = srcContext;
		m_context_dma_buffer_in_dst = dstContext;
		break;
	}

	case NV0039_OFFSET_IN:
	{
		const u32 inOffset = ARGS(0);
		const u32 outOffset = ARGS(1);
		const u32 inPitch = ARGS(2);
		const u32 outPitch = ARGS(3);
		const u32 lineLength = ARGS(4);
		const u32 lineCount = ARGS(5);
		const u8 outFormat = (ARGS(6) >> 8);
		const u8 inFormat = (ARGS(6) >> 0);
		const u32 notify = ARGS(7);

		// The existing GCM commands use only the value 0x1 for inFormat and outFormat
		if (inFormat != 0x01 || outFormat != 0x01)
		{
			LOG_ERROR(RSX, "NV0039_OFFSET_IN: Unsupported format: inFormat=%d, outFormat=%d", inFormat, outFormat);
		}

		if (lineCount == 1 && !inPitch && !outPitch && !notify)
		{
			memcpy(vm::get_ptr<void>(GetAddress(outOffset, 0)), vm::get_ptr<void>(GetAddress(inOffset, 0)), lineLength);
		}
		else
		{
			LOG_ERROR(RSX, "NV0039_OFFSET_IN: bad offset(in=0x%x, out=0x%x), pitch(in=0x%x, out=0x%x), line(len=0x%x, cnt=0x%x), fmt(in=0x%x, out=0x%x), notify=0x%x",
				inOffset, outOffset, inPitch, outPitch, lineLength, lineCount, inFormat, outFormat, notify);
		}
		break;
	}

	case NV0039_OFFSET_OUT: // [E : RSXThread]: TODO: unknown/illegal method [0x00002310](0x0)
	{
		const u32 offset = ARGS(0);

		if (!offset)
		{
		}
		else
		{
			LOG_ERROR(RSX, "TODO: NV0039_OFFSET_OUT: offset=0x%x", offset);
		}
		break;
	}

	case NV0039_PITCH_IN:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV0039_PITCH_IN: 0x%x", value);
		}
		break;
	}

	case NV0039_BUFFER_NOTIFY:
	{
		if (u32 value = ARGS(0))
		{
			LOG_WARNING(RSX, "TODO: NV0039_BUFFER_NOTIFY: 0x%x", value);
		}
		break;
	}

	// NV3062
	case NV3062_SET_CONTEXT_DMA_IMAGE_DESTIN:
	{
		if (count == 1)
		{
			m_context_dma_img_dst = ARGS(0);
		}
		else
		{
			LOG_ERROR(RSX, "NV3062_SET_CONTEXT_DMA_IMAGE__DESTIN: unknown arg count (%d)", count);
		}
		break;
	}

	case NV3062_SET_OFFSET_DESTIN:
	{
		if (count == 1)
		{
			m_dst_offset = ARGS(0);
		}
		else
		{
			LOG_ERROR(RSX, "NV3062_SET_OFFSET_DESTIN: unknown arg count (%d)", count);
		}
		break;
	}

	case NV3062_SET_COLOR_FORMAT:
	{
		if (count == 2 || count == 4)
		{
			m_color_format = ARGS(0);
			m_color_format_src_pitch = ARGS(1);
			m_color_format_dst_pitch = ARGS(1) >> 16;

			if (count == 4)
			{
				if (ARGS(2))
				{
					LOG_ERROR(RSX, "NV3062_SET_COLOR_FORMAT: unknown arg2 value (0x%x)", ARGS(2));
				}

				m_dst_offset = ARGS(3);
			}
		}
		else
		{
			LOG_ERROR(RSX, "NV3062_SET_COLOR_FORMAT: unknown arg count (%d)", count);
		}
		break;
	}

	// NV309E
	case NV309E_SET_CONTEXT_DMA_IMAGE:
	{
		if (count == 1)
		{
			m_context_dma_img_src = ARGS(0);
		}
		else
		{
			LOG_ERROR(RSX, "NV309E_SET_CONTEXT_DMA_IMAGE: unknown arg count (%d)", count);
		}
		break;
	}

	case NV309E_SET_FORMAT:
	{
		if (count == 2)
		{
			m_swizzle_format = ARGS(0);
			m_swizzle_width = ARGS(0) >> 16;
			m_swizzle_height = ARGS(0) >> 24;
			m_swizzle_offset = ARGS(1);
		}
		else
		{
			LOG_ERROR(RSX, "NV309E_SET_FORMAT: unknown arg count (%d)", count);
		}
		break;
	}

	// NV308A
	case NV308A_POINT:
	{
		const u32 a0 = ARGS(0);
		m_point_x = a0 & 0xffff;
		m_point_y = a0 >> 16;
		break;
	}

	case NV308A_COLOR:
	{
		RSXTransformConstant c;
		c.id = m_dst_offset | ((u32)m_point_x << 2);

		if (count >= 1)
		{
			u32 a = ARGS(0);
			a = a << 16 | a >> 16;
			c.x = (float&)a;
		}

		if (count >= 2)
		{
			u32 a = ARGS(1);
			a = a << 16 | a >> 16;
			c.y = (float&)a;
		}

		if (count >= 3)
		{
			u32 a = ARGS(2);
			a = a << 16 | a >> 16;
			c.z = (float&)a;
		}

		if (count >= 4)
		{
			u32 a = ARGS(3);
			a = a << 16 | a >> 16;
			c.w = (float&)a;
		}

		if (count >= 5)
		{
			LOG_ERROR(RSX, "NV308A_COLOR: unknown arg count (%d)", count);
		}

		m_fragment_constants.push_back(c);

		//LOG_WARNING(RSX, "NV308A_COLOR: [%d]: %f, %f, %f, %f", c.id, c.x, c.y, c.z, c.w);
		break;
	}

	// NV3089
	case NV3089_SET_CONTEXT_DMA_IMAGE:
	{
		if (count == 1)
		{
			m_context_dma_img_src = ARGS(0);
		}
		else
		{
			LOG_ERROR(RSX, "NV3089_SET_CONTEXT_DMA_IMAGE: unknown arg count (%d)", count);
		}
		break;
	}

	case NV3089_SET_CONTEXT_SURFACE:
	{
		if (count == 1)
		{
			m_context_surface = ARGS(0);

			if (m_context_surface != CELL_GCM_CONTEXT_SURFACE2D && m_context_surface != CELL_GCM_CONTEXT_SWIZZLE2D)
			{
				LOG_ERROR(RSX, "NV3089_SET_CONTEXT_SURFACE: unknown surface (0x%x)", ARGS(0));
			}
		}
		else
		{
			LOG_ERROR(RSX, "NV3089_SET_CONTEXT_SURFACE: unknown arg count (%d)", count);
		}
		break;
	}

	case NV3089_IMAGE_IN_SIZE:
	{
		const u16 width = ARGS(0);
		const u16 height = ARGS(0) >> 16;
		const u16 pitch = ARGS(1);

		const u8 origin = ARGS(1) >> 16;
		if (origin != 2 /* CELL_GCM_TRANSFER_ORIGIN_CORNER */)
		{
			LOG_ERROR(RSX, "NV3089_IMAGE_IN_SIZE: unknown origin (%d)", origin);
		}

		const u8 inter = ARGS(1) >> 24;
		if (inter != 0 /* CELL_GCM_TRANSFER_INTERPOLATOR_ZOH */ && inter != 1 /* CELL_GCM_TRANSFER_INTERPOLATOR_FOH */)
		{
			LOG_ERROR(RSX, "NV3089_IMAGE_IN_SIZE: unknown inter (%d)", inter);
		}

		const u32 offset = ARGS(2);

		const u16 u = ARGS(3); // inX (currently ignored)
		const u16 v = ARGS(3) >> 16; // inY (currently ignored)

		u8* pixels_src = vm::get_ptr<u8>(GetAddress(offset, m_context_dma_img_src - 0xfeed0000));
		u8* pixels_dst = vm::get_ptr<u8>(GetAddress(m_dst_offset, m_context_dma_img_dst - 0xfeed0000));

		if (m_context_surface == CELL_GCM_CONTEXT_SWIZZLE2D)
		{
			LOG_ERROR(RSX, "NV3089_IMAGE_IN_SIZE: Swizzle2D not implemented");
		}
		else if (m_context_surface != CELL_GCM_CONTEXT_SURFACE2D)
		{
			LOG_ERROR(RSX, "NV3089_IMAGE_IN_SIZE: unknown m_context_surface (0x%x)", m_context_surface);
		}

		if (m_color_format != 4 /* CELL_GCM_TRANSFER_SURFACE_FORMAT_R5G6B5 */ && m_color_format != 10 /* CELL_GCM_TRANSFER_SURFACE_FORMAT_A8R8G8B8 */)
		{
			LOG_ERROR(RSX, "NV3089_IMAGE_IN_SIZE: unknown m_color_format (%d)", m_color_format);
		}

		const u32 in_bpp = m_color_format == 4 ? 2 : 4; // bytes per pixel
		const u32 out_bpp = m_color_conv_fmt == 7 ? 2 : 4;

		const s32 out_w = (s32)(u64(width) * (1 << 20) / m_color_conv_dsdx);
		const s32 out_h = (s32)(u64(height) * (1 << 20) / m_color_conv_dtdy);

		LOG_WARNING(RSX, "NV3089_IMAGE_IN_SIZE: w=%d, h=%d, pitch=%d, offset=0x%x, inX=%f, inY=%f, scaleX=%f, scaleY=%f",
			width, height, pitch, offset, double(u) / 16, double(v) / 16, double(1 << 20) / (m_color_conv_dsdx), double(1 << 20) / (m_color_conv_dtdy));

		std::unique_ptr<u8[]> temp;
		
		if (in_bpp != out_bpp && width != out_w && height != out_h)
		{
			// resize/convert if necessary

			temp.reset(new u8[out_bpp * out_w * out_h]);

			AVPixelFormat in_format = m_color_format == 4 ? AV_PIX_FMT_RGB565BE : AV_PIX_FMT_ARGB; // ???
			AVPixelFormat out_format = m_color_conv_fmt == 7 ? AV_PIX_FMT_RGB565BE : AV_PIX_FMT_ARGB; // ???

			std::unique_ptr<SwsContext, void(*)(SwsContext*)> sws(sws_getContext(width, height, in_format, out_w, out_h, out_format, inter ? SWS_FAST_BILINEAR : SWS_POINT, NULL, NULL, NULL), sws_freeContext);

			int in_line = in_bpp * width;
			u8* out_ptr = temp.get();
			int out_line = out_bpp * out_w;

			sws_scale(sws.get(), &pixels_src, &in_line, 0, height, &out_ptr, &out_line);

			pixels_src = temp.get(); // use resized image as a source
		}

		if (m_color_conv_out_w != m_color_conv_clip_w || m_color_conv_out_w != out_w ||
			m_color_conv_out_h != m_color_conv_clip_h || m_color_conv_out_h != out_h ||
			m_color_conv_out_x || m_color_conv_out_y || m_color_conv_clip_x || m_color_conv_clip_y)
		{
			// clip if necessary

			for (s32 y = m_color_conv_clip_y, dst_y = m_color_conv_out_y; y < out_h; y++, dst_y++)
			{
				if (dst_y >= 0 && dst_y < m_color_conv_out_h)
				{
					// destination line
					u8* dst_line = pixels_dst + dst_y * out_bpp * m_color_conv_out_w + std::min<s32>(std::max<s32>(m_color_conv_out_x, 0), m_color_conv_out_w);
					size_t dst_max = std::min<s32>(std::max<s32>((s32)m_color_conv_out_w - m_color_conv_out_x, 0), m_color_conv_out_w) * out_bpp;

					if (y >= 0 && y < std::min<s32>(m_color_conv_clip_h, out_h))
					{
						// source line
						u8* src_line = pixels_src + y * out_bpp * out_w + std::min<s32>(std::max<s32>(m_color_conv_clip_x, 0), m_color_conv_clip_w);
						size_t src_max = std::min<s32>(std::max<s32>((s32)m_color_conv_clip_w - m_color_conv_clip_x, 0), m_color_conv_clip_w) * out_bpp;

						std::pair<u8*, size_t>
							z0 = { src_line + 0, std::min<size_t>(dst_max, std::max<s64>(0, m_color_conv_clip_x)) },
							d0 = { src_line + z0.second, std::min<size_t>(dst_max - z0.second, src_max) },
							z1 = { src_line + d0.second, dst_max - z0.second - d0.second };

						memset(z0.first, 0, z0.second);
						memcpy(d0.first, src_line, d0.second);
						memset(z1.first, 0, z1.second);
					}
					else
					{
						memset(dst_line, 0, dst_max);
					}
				}
			}
		}
		else
		{
			memcpy(pixels_dst, pixels_src, out_w * out_h * out_bpp);
		}

		break;
	}

	case NV3089_SET_COLOR_CONVERSION:
	{
		m_color_conv = ARGS(0);
		if (m_color_conv != 1 /* CELL_GCM_TRANSFER_CONVERSION_TRUNCATE */)
		{
			LOG_ERROR(RSX, "NV3089_SET_COLOR_CONVERSION: unknown color conv (%d)", m_color_conv);
		}

		m_color_conv_fmt = ARGS(1);
		if (m_color_conv_fmt != 3 /* CELL_GCM_TRANSFER_SCALE_FORMAT_A8R8G8B8 */ && m_color_conv_fmt != 7 /* CELL_GCM_TRANSFER_SCALE_FORMAT_R5G6B5 */)
		{
			LOG_ERROR(RSX, "NV3089_SET_COLOR_CONVERSION: unknown format (%d)", m_color_conv_fmt);
		}

		m_color_conv_op = ARGS(2);
		if (m_color_conv_op != 3 /* CELL_GCM_TRANSFER_OPERATION_SRCCOPY */)
		{
			LOG_ERROR(RSX, "NV3089_SET_COLOR_CONVERSION: unknown color conv op (%d)", m_color_conv_op);
		}

		m_color_conv_clip_x = ARGS(3);
		m_color_conv_clip_y = ARGS(3) >> 16;
		m_color_conv_clip_w = ARGS(4);
		m_color_conv_clip_h = ARGS(4) >> 16;
		m_color_conv_out_x = ARGS(5);
		m_color_conv_out_y = ARGS(5) >> 16;
		m_color_conv_out_w = ARGS(6);
		m_color_conv_out_h = ARGS(6) >> 16;
		m_color_conv_dsdx = ARGS(7);
		m_color_conv_dtdy = ARGS(8);
		break;
	}

	case GCM_SET_USER_COMMAND:
	{
		const u32 cause = ARGS(0);

		if (auto cb = m_user_handler)
		{
			Emu.GetCallbackManager().Async([=](CPUThread& cpu)
			{
				cb(static_cast<PPUThread&>(cpu), cause);
			});
		}
		else
		{
			throw EXCEPTION("User handler not set");
		}
		
		break;
	}

	// Note: What is this? NV4097 offsets?
	case 0x000002c8:
	case 0x000002d0:
	case 0x000002d8:
	case 0x000002e0:
	case 0x000002e8:
	case 0x000002f0:
	case 0x000002f8:
		break;

	// The existing GCM commands don't use any of the following NV4097 / NV0039 / NV3062 / NV309E / NV308A / NV3089 methods
	case NV4097_SET_WINDOW_CLIP_TYPE:
	case NV4097_SET_WINDOW_CLIP_HORIZONTAL:
	case NV4097_SET_WINDOW_CLIP_VERTICAL:
	{
		LOG_WARNING(RSX, "Unused NV4097 method 0x%x detected!", cmd);
		break;
	}

	case NV0039_SET_CONTEXT_DMA_BUFFER_OUT:
	case NV0039_PITCH_OUT:
	case NV0039_LINE_LENGTH_IN:
	case NV0039_LINE_COUNT:
	case NV0039_FORMAT:
	case NV0039_SET_OBJECT:
	case NV0039_SET_CONTEXT_DMA_NOTIFIES:
	{
		LOG_WARNING(RSX, "Unused NV0039 method 0x%x detected!", cmd);
		break;
	}

	case NV3062_SET_OBJECT:
	case NV3062_SET_CONTEXT_DMA_NOTIFIES:
	case NV3062_SET_CONTEXT_DMA_IMAGE_SOURCE:
	case NV3062_SET_PITCH:
	case NV3062_SET_OFFSET_SOURCE:
	{
		LOG_WARNING(RSX, "Unused NV3062 method 0x%x detected!", cmd);
		break;
	}

	case NV308A_SET_OBJECT:
	case NV308A_SET_CONTEXT_DMA_NOTIFIES:
	case NV308A_SET_CONTEXT_COLOR_KEY:
	case NV308A_SET_CONTEXT_CLIP_RECTANGLE:
	case NV308A_SET_CONTEXT_PATTERN:
	case NV308A_SET_CONTEXT_ROP:
	case NV308A_SET_CONTEXT_BETA1:
	case NV308A_SET_CONTEXT_BETA4:
	case NV308A_SET_CONTEXT_SURFACE:
	case NV308A_SET_COLOR_CONVERSION:
	case NV308A_SET_OPERATION:
	case NV308A_SET_COLOR_FORMAT:
	case NV308A_SIZE_OUT:
	case NV308A_SIZE_IN:
	{
		LOG_WARNING(RSX, "Unused NV308A method 0x%x detected!", cmd);
		break;
	}

	case NV309E_SET_OBJECT:
	case NV309E_SET_CONTEXT_DMA_NOTIFIES:
	case NV309E_SET_OFFSET:
	{
		LOG_WARNING(RSX, "Unused NV309E method 0x%x detected!", cmd);
		break;
	}

	case NV3089_SET_OBJECT:
	case NV3089_SET_CONTEXT_DMA_NOTIFIES:
	case NV3089_SET_CONTEXT_PATTERN:
	case NV3089_SET_CONTEXT_ROP:
	case NV3089_SET_CONTEXT_BETA1:
	case NV3089_SET_CONTEXT_BETA4:
	case NV3089_SET_COLOR_FORMAT:
	case NV3089_SET_OPERATION:
	case NV3089_CLIP_POINT:
	case NV3089_CLIP_SIZE:
	case NV3089_IMAGE_OUT_POINT:
	case NV3089_IMAGE_OUT_SIZE:
	case NV3089_DS_DX:
	case NV3089_DT_DY:
	case NV3089_IMAGE_IN_FORMAT:
	case NV3089_IMAGE_IN_OFFSET:
	case NV3089_IMAGE_IN:
	{
		LOG_WARNING(RSX, "Unused NV3089 methods 0x%x detected!", cmd);
		break;
	}

	default:
	{
		std::string log = GetMethodName(cmd);
		log += "(";
		for (u32 i = 0; i < count; ++i)
		{
			log += (i ? ", " : "") + fmt::format("0x%x", ARGS(i));
		}
		log += ")";
		LOG_ERROR(RSX, "TODO: %s", log.c_str());
		break;
	}
	}
}

void RSXThread::Begin(u32 draw_mode)
{
	m_begin_end = 1;
	m_draw_mode = draw_mode;
	m_draw_array_count = 0;
	m_draw_array_first = ~0;
}

void RSXThread::End()
{
	Draw();

	for (auto &vdata : m_vertex_data)
	{
		vdata.data.clear();
	}

	m_indexed_array.Reset();
	m_fragment_constants.clear();
	m_transform_constants.clear();
	m_cur_fragment_prog_num = 0;

	m_clear_surface_mask = 0;
	m_begin_end = 0;

	OnReset();
}

void RSXThread::Task()
{
	u8 inc;
	LOG_NOTICE(RSX, "RSX thread started");

	OnInitThread();

	m_last_flip_time = get_system_time() - 1000000;

	autojoin_thread_t vblank(WRAP_EXPR("VBlank Thread"), [this]()
	{
		const u64 start_time = get_system_time();

		m_vblank_count = 0;

		while (joinable())
		{
			CHECK_EMU_STATUS;

			if (get_system_time() - start_time > m_vblank_count * 1000000 / 60)
			{
				m_vblank_count++;

				if (auto cb = m_vblank_handler)
				{
					Emu.GetCallbackManager().Async([=](CPUThread& cpu)
					{
						cb(static_cast<PPUThread&>(cpu), 1);
					});
				}
			}
			else
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack
			}
		}
	});

	while (joinable() && !Emu.IsStopped())
	{
		std::lock_guard<std::mutex> lock(m_cs_main);

		inc = 1;

		const be_t<u32> put = m_ctrl->put;
		const be_t<u32> get = m_ctrl->get;

		if (put == get || !Emu.IsRunning())
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack
			continue;
		}

		const u32 cmd = ReadIO32(get);
		const u32 count = (cmd >> 18) & 0x7ff;

		if (Ini.RSXLogging.GetValue())
		{
			LOG_NOTICE(Log::RSX, "%s (cmd=0x%x)", GetMethodName(cmd & 0xffff).c_str(), cmd);
		}
	
		if (cmd & CELL_GCM_METHOD_FLAG_JUMP)
		{
			u32 offs = cmd & 0x1fffffff;
			//LOG_WARNING(RSX, "rsx jump(0x%x) #addr=0x%x, cmd=0x%x, get=0x%x, put=0x%x", offs, m_ioAddress + get, cmd, get, put);
			m_ctrl->get.exchange(offs);
			continue;
		}
		if (cmd & CELL_GCM_METHOD_FLAG_CALL)
		{
			m_call_stack.push(get + 4);
			u32 offs = cmd & ~3;
			//LOG_WARNING(RSX, "rsx call(0x%x) #0x%x - 0x%x", offs, cmd, get);
			m_ctrl->get.exchange(offs);
			continue;
		}
		if (cmd == CELL_GCM_METHOD_FLAG_RETURN)
		{
			u32 get = m_call_stack.top();
			m_call_stack.pop();
			//LOG_WARNING(RSX, "rsx return(0x%x)", get);
			m_ctrl->get.exchange(get);
			continue;
		}
		if (cmd & CELL_GCM_METHOD_FLAG_NON_INCREMENT)
		{
			//LOG_WARNING(RSX, "rsx non increment cmd! 0x%x", cmd);
			inc = 0;
		}

		if (cmd == 0) //nop
		{
			m_ctrl->get += 4;
			continue;
		}

		auto args = vm::ptr<u32>::make((u32)RSXIOMem.RealAddr(get + 4));

		for (u32 i = 0; i < count; i++)
		{
			methodRegisters[(cmd & 0xffff) + (i * 4 * inc)] = ARGS(i);
		}

		DoCmd(cmd, cmd & 0x3ffff, args.addr(), count);

		m_ctrl->get += (count + 1) * 4;
	}

	OnExitThread();
}

void RSXThread::Init(const u32 ioAddress, const u32 ioSize, const u32 ctrlAddress, const u32 localAddress)
{
	m_ctrl = vm::get_ptr<CellGcmControl>(ctrlAddress);
	m_ioAddress = ioAddress;
	m_ioSize = ioSize;
	m_ctrlAddress = ctrlAddress;
	m_local_mem_addr = localAddress;

	m_cur_vertex_prog = nullptr;
	m_cur_fragment_prog = nullptr;
	m_cur_fragment_prog_num = 0;

	m_used_gcm_commands.clear();

	OnInit();

	start(WRAP_EXPR("RSXThread"), WRAP_EXPR(Task()));
}

u32 RSXThread::ReadIO32(u32 addr)
{
	u32 value;

	if (!RSXIOMem.Read32(addr, &value))
	{
		throw EXCEPTION("RSXIO memory not mapped (addr=0x%x)", addr);
	}

	return value;
}

void RSXThread::WriteIO32(u32 addr, u32 value)
{
	if (!RSXIOMem.Write32(addr, value))
	{
		throw EXCEPTION("RSXIO memory not mapped (addr=0x%x)", addr);
	}
}
