#include "stdafx.h"
#include "rsx_methods.h"
#include "RSXThread.h"
#include "rsx_utils.h"
#include "rsx_decode.h"
#include "Common/time.hpp"
#include "Core/RSXReservationLock.hpp"
#include "Emu/Cell/PPUCallback.h"
#include "Emu/Cell/lv2/sys_rsx.h"
#include "Emu/RSX/Common/BufferUtils.h"

namespace rsx
{
	rsx_state method_registers;

	std::array<rsx_method_t, 0x10000 / 4> methods{};
	std::array<u32, 0x10000 / 4> state_signals{};

	void invalid_method(thread* rsx, u32 reg, u32 arg)
	{
		//Don't throw, gather information and ignore broken/garbage commands
		//TODO: Investigate why these commands are executed at all. (Heap corruption? Alignment padding?)
		const u32 cmd = rsx->get_fifo_cmd();
		rsx_log.error("Invalid RSX method 0x%x (arg=0x%x, start=0x%x, count=0x%x, non-inc=%s)", reg << 2, arg,
		cmd & 0xfffc, (cmd >> 18) & 0x7ff, !!(cmd & RSX_METHOD_NON_INCREMENT_CMD));

		if (g_cfg.core.rsx_fifo_accuracy != rsx_fifo_mode::as_ps3)
		{
			rsx->recover_fifo();
		}
	}

	static void trace_method(thread* /*rsx*/, u32 reg, u32 arg)
	{
		// For unknown yet valid methods
		rsx_log.trace("RSX method 0x%x (arg=0x%x)", reg << 2, arg);
	}

	template <bool FlushDMA, bool FlushPipe>
	void write_gcm_label(thread* rsx, u32 address, u32 data)
	{
		const bool is_flip_sema = (address == (rsx->label_addr + 0x10) || address == (rsx->device_addr + 0x30));
		if (!is_flip_sema)
		{
			// First, queue the GPU work. If it flushes the queue for us, the following routines will be faster.
			const bool handled = rsx->get_backend_config().supports_host_gpu_labels && rsx->release_GCM_label(address, data);

			if (vm::_ref<RsxSemaphore>(address).val == data)
			{
				// It's a no-op to write the same value (although there is a delay in real-hw so it's more accurate to allow GPU label in this case)
				return;
			}

			if constexpr (FlushDMA)
			{
				// If the backend handled the request, this call will basically be a NOP
				g_fxo->get<rsx::dma_manager>().sync();
			}

			if constexpr (FlushPipe)
			{
				// Manually flush the pipeline.
				// It is possible to stream report writes using the host GPU, but that generates too much submit traffic.
				rsx->sync();
			}

			if (handled)
			{
				// Backend will handle it, nothing to write.
				return;
			}
		}

		vm::_ref<RsxSemaphore>(address).val = data;
	}

	template<typename Type> struct vertex_data_type_from_element_type;
	template<> struct vertex_data_type_from_element_type<float> { static const vertex_base_type type = vertex_base_type::f; };
	template<> struct vertex_data_type_from_element_type<f16> { static const vertex_base_type type = vertex_base_type::sf; };
	template<> struct vertex_data_type_from_element_type<u8> { static const vertex_base_type type = vertex_base_type::ub; };
	template<> struct vertex_data_type_from_element_type<u16> { static const vertex_base_type type = vertex_base_type::s32k; };
	template<> struct vertex_data_type_from_element_type<s16> { static const vertex_base_type type = vertex_base_type::s1; };

	namespace nv406e
	{
		void set_reference(thread* rsx, u32 /*reg*/, u32 arg)
		{
			rsx->sync();

			// Write ref+get (get will be written again with the same value at command end)
			auto& dma = vm::_ref<RsxDmaControl>(rsx->dma_address);
			dma.get.release(rsx->fifo_ctrl->get_pos());
			dma.ref.store(arg);
		}

		void semaphore_acquire(thread* rsx, u32 /*reg*/, u32 arg)
		{
			rsx->sync_point_request.release(true);
			const u32 addr = get_address(method_registers.semaphore_offset_406e(), method_registers.semaphore_context_dma_406e());

			const auto& sema = vm::_ref<RsxSemaphore>(addr).val;

			if (sema == arg)
			{
				// Flip semaphore doesnt need wake-up delay
				if (addr != rsx->label_addr + 0x10)
				{
					rsx->flush_fifo();
					rsx->fifo_wake_delay(2);
				}

				return;
			}
			else
			{
				rsx->flush_fifo();
			}

			u64 start = rsx::uclock();
			u64 last_check_val = start;

			while (sema != arg)
			{
				if (rsx->test_stopped())
				{
					rsx->state += cpu_flag::again;
					return;
				}

				if (const auto tdr = static_cast<u64>(g_cfg.video.driver_recovery_timeout))
				{
					const u64 current = rsx::uclock();

					if (current - last_check_val > 20'000)
					{
						// Suspicious amnount of time has passed
						// External pause such as debuggers' pause or operating system sleep may have taken place
						// Ignore it
						start += current - last_check_val;
					}

					last_check_val = current;

					if ((current - start) > tdr)
					{
						// If longer than driver timeout force exit
						rsx_log.error("nv406e::semaphore_acquire has timed out. semaphore_address=0x%X", addr);
						break;
					}
				}

				rsx->cpu_wait({});
			}

			rsx->fifo_wake_delay();
			rsx->performance_counters.idle_time += (rsx::uclock() - start);
		}

		void semaphore_release(thread* rsx, u32 /*reg*/, u32 arg)
		{
			const u32 offset = method_registers.semaphore_offset_406e();

			if (offset % 4)
			{
				rsx_log.warning("NV406E semaphore release is using unaligned semaphore, ignoring. (offset=0x%x)", offset);
				return;
			}

			const u32 ctxt = method_registers.semaphore_context_dma_406e();

			// By avoiding doing this on flip's semaphore release
			// We allow last gcm's registers reset to occur in case of a crash
			if (const bool is_flip_sema = (offset == 0x10 && ctxt == CELL_GCM_CONTEXT_DMA_SEMAPHORE_R);
				!is_flip_sema)
			{
				rsx->sync_point_request.release(true);
			}

			const u32 addr = get_address(offset, ctxt);

			// TODO: Check if possible to write on reservations
			if (rsx->label_addr >> 28 != addr >> 28)
			{
				rsx_log.error("NV406E semaphore unexpected address. Please report to the developers. (offset=0x%x, addr=0x%x)", offset, addr);
				rsx->recover_fifo();
				return;
			}

			if (addr == rsx->device_addr + 0x30 && !arg)
			{
				// HW flip synchronization related, 1 is not written without display queue command (TODO: make it behave as real hw)
				arg = 1;
			}

			write_gcm_label<false, true>(rsx, addr, arg);
		}
	}

	namespace nv4097
	{
		void clear(thread* rsx, u32 /*reg*/, u32 arg)
		{
			rsx->clear_surface(arg);

			if (rsx->capture_current_frame)
			{
				rsx->capture_frame("clear");
			}
		}

		void clear_zcull(thread* rsx, u32 /*reg*/, u32 /*arg*/)
		{
			if (rsx->capture_current_frame)
			{
				rsx->capture_frame("clear zcull memory");
			}
		}

		void set_face_property(thread* rsx, u32 reg, u32 arg)
		{
			if (reg == method_registers.register_previous_value)
			{
				return;
			}

			bool valid;
			switch (reg)
			{
			case NV4097_SET_CULL_FACE:
				valid = !!to_cull_face(arg); break;
			case NV4097_SET_FRONT_FACE:
				valid = !!to_front_face(arg); break;
			default:
				valid = false; break;
			}

			if (valid) [[ likely ]]
			{
				rsx->m_graphics_state |= rsx::pipeline_config_dirty;
			}
			else
			{
				method_registers.registers[reg] = method_registers.register_previous_value;
			}
		}

		void set_notify(thread* rsx, u32 /*reg*/, u32 /*arg*/)
		{
			const u32 location = method_registers.context_dma_notify();
			const u32 index = (location & 0x7) ^ 0x7;

			if ((location & ~7) != (CELL_GCM_CONTEXT_DMA_NOTIFY_MAIN_0 & ~7))
			{
				rsx_log.trace("NV4097_NOTIFY: invalid context = 0x%x", method_registers.context_dma_notify());
				return;
			}

			const u32 addr = rsx->iomap_table.get_addr(0xf100000 + (index * 0x40));

			ensure(addr != umax);

			vm::_ref<atomic_t<RsxNotify>>(addr).store(
			{
				rsx->timestamp(),
				0
			});
		}

		void texture_read_semaphore_release(thread* rsx, u32 /*reg*/, u32 arg)
		{
			// Pipeline barrier seems to be equivalent to a SHADER_READ stage barrier.
			// Ideally the GPU only needs to have cached all textures declared up to this point before writing the label.

			// lle-gcm likes to inject system reserved semaphores, presumably for system/vsh usage
			// Avoid calling render to avoid any havoc(flickering) they may cause from invalid flush/write
			const u32 offset = method_registers.semaphore_offset_4097();

			if (offset % 16)
			{
				rsx_log.error("NV4097 semaphore using unaligned offset, recovering. (offset=0x%x)", offset);
				rsx->recover_fifo();
				return;
			}

			const u32 addr = get_address(offset, method_registers.semaphore_context_dma_4097());

			if (rsx->label_addr >> 28 != addr >> 28)
			{
				rsx_log.error("NV4097 semaphore unexpected address. Please report to the developers. (offset=0x%x, addr=0x%x)", offset, addr);
			}

			if (g_cfg.video.strict_rendering_mode) [[ unlikely ]]
			{
				write_gcm_label<true, true>(rsx, addr, arg);
			}
			else
			{
				write_gcm_label<true, false>(rsx, addr, arg);
			}
		}

		void back_end_write_semaphore_release(thread* rsx, u32 /*reg*/, u32 arg)
		{
			// Full pipeline barrier. GPU must flush pipeline before writing the label

			const u32 offset = method_registers.semaphore_offset_4097();

			if (offset % 16)
			{
				rsx_log.error("NV4097 semaphore using unaligned offset, recovering. (offset=0x%x)", offset);
				rsx->recover_fifo();
				return;
			}

			const u32 addr = get_address(offset, method_registers.semaphore_context_dma_4097());

			if (rsx->label_addr >> 28 != addr >> 28)
			{
				rsx_log.error("NV4097 semaphore unexpected address. Please report to the developers. (offset=0x%x, addr=0x%x)", offset, addr);
			}

			const u32 val = (arg & 0xff00ff00) | ((arg & 0xff) << 16) | ((arg >> 16) & 0xff);
			write_gcm_label<true, true>(rsx, addr, val);
		}

		/**
		 * id = base method register
		 * index = register index in method
		 * count = element count per attribute
		 * register_count = number of registers consumed per attribute. E.g 3-element methods have padding
		 */
		template<u32 id, u32 index, int count, int register_count, typename type>
		void set_vertex_data_impl(thread* rsx, u32 arg)
		{
			static constexpr usz increment_per_array_index = (register_count * sizeof(type)) / sizeof(u32);

			static constexpr usz attribute_index = index / increment_per_array_index;
			static constexpr usz vertex_subreg = index % increment_per_array_index;

			constexpr auto vtype = vertex_data_type_from_element_type<type>::type;
			static_assert(vtype != rsx::vertex_base_type::cmp);
			static_assert(vtype != rsx::vertex_base_type::ub256);

			// Convert LE data to BE layout
			if constexpr (sizeof(type) == 4)
			{
				arg = std::bit_cast<u32, be_t<u32>>(arg);
			}
			else if constexpr (sizeof(type) == 2)
			{
				// 2 16-bit values packed in 1 32-bit word
				const auto be_data = std::bit_cast<u32, be_t<u32>>(arg);

				// After u32 swap, the components are in the wrong position
				arg = (be_data << 16) | (be_data >> 16);
			}

			if (rsx->in_begin_end)
			{
				// Update to immediate mode register/array
				// NOTE: Push buffers still behave like register writes.
				// You do not need to specify each attribute for each vertex, the register is referenced instead.
				// This is classic OpenGL 1.x behavior as I remember.
				rsx->append_to_push_buffer(attribute_index, count, vertex_subreg, vtype, arg);
			}

			auto& info = rsx::method_registers.register_vertex_info[attribute_index];

			info.type = vtype;
			info.size = count;
			info.frequency = 0;
			info.stride = 0;
			rsx::method_registers.register_vertex_info[attribute_index].data[vertex_subreg] = arg;
		}

		template<u32 index>
		struct set_vertex_data4ub_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA4UB_M, index, 4, 4, u8>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data1f_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA1F_M, index, 1, 1, f32>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data2f_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA2F_M, index, 2, 2, f32>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data3f_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				//Register alignment is only 1, 2, or 4 (Rachet & Clank 2)
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA3F_M, index, 3, 4, f32>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data4f_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA4F_M, index, 4, 4, f32>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data2s_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA2S_M, index, 2, 2, u16>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data4s_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA4S_M, index, 4, 4, u16>(rsx, arg);
			}
		};

		template<u32 index>
		struct set_vertex_data_scaled4s_m
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				set_vertex_data_impl<NV4097_SET_VERTEX_DATA_SCALED4S_M, index, 4, 4, s16>(rsx, arg);
			}
		};

		void set_array_element16(thread* rsx, u32, u32 arg)
		{
			if (rsx->in_begin_end)
			{
				rsx->append_array_element(arg & 0xFFFF);
				rsx->append_array_element(arg >> 16);
			}
		}

		void set_array_element32(thread* rsx, u32, u32 arg)
		{
			if (rsx->in_begin_end)
				rsx->append_array_element(arg);
		}

		void draw_arrays(thread* /*rsx*/, u32 /*reg*/, u32 arg)
		{
			rsx::method_registers.current_draw_clause.command = rsx::draw_command::array;
			rsx::registers_decoder<NV4097_DRAW_ARRAYS>::decoded_type v(arg);

			rsx::method_registers.current_draw_clause.append(v.start(), v.count());
		}

		void draw_index_array(thread* /*rsx*/, u32 /*reg*/, u32 arg)
		{
			rsx::method_registers.current_draw_clause.command = rsx::draw_command::indexed;
			rsx::registers_decoder<NV4097_DRAW_INDEX_ARRAY>::decoded_type v(arg);

			rsx::method_registers.current_draw_clause.append(v.start(), v.count());
		}

		void draw_inline_array(thread* /*rsx*/, u32 /*reg*/, u32 arg)
		{
			arg = std::bit_cast<u32, be_t<u32>>(arg);
			rsx::method_registers.current_draw_clause.command = rsx::draw_command::inlined_array;
			rsx::method_registers.current_draw_clause.inline_vertex_array.push_back(arg);
		}

		struct set_transform_constant
		{
			static void impl(thread* rsx, u32 _reg, u32 /*arg*/)
			{
				const u32 index = _reg - NV4097_SET_TRANSFORM_CONSTANT;
				const u32 reg = index / 4;
				const u8 subreg = index % 4;

				// FIFO args count including this one
				const u32 fifo_args_cnt = rsx->fifo_ctrl->get_remaining_args_count() + 1;

				// The range of methods this function resposible to
				const u32 method_range = 32 - index;

				// Get limit imposed by FIFO PUT (if put is behind get it will result in a number ignored by min)
				const u32 fifo_read_limit = static_cast<u32>(((rsx->ctrl->put & ~3ull) - (rsx->fifo_ctrl->get_pos())) / 4);

				const u32 count = std::min<u32>({fifo_args_cnt, fifo_read_limit, method_range});

				const u32 load = rsx::method_registers.transform_constant_load();

				u32 rcount = count;
				if (const u32 max = (load + reg) * 4 + count + subreg, limit = 468 * 4; max > limit)
				{
					// Ignore addresses outside the usable [0, 467] range
					rsx_log.warning("Invalid transform register index (load=%u, index=%u, count=%u)", load, index, count);

					if ((max - count) < limit)
						rcount -= max - limit;
					else
						rcount = 0;
				}

				const auto values = &rsx::method_registers.transform_constants[load + reg][subreg];

				const auto fifo_span = rsx->fifo_ctrl->get_current_arg_ptr();

				if (fifo_span.size() < rcount)
				{
					rcount = ::size32(fifo_span);
				}

				if (rsx->m_graphics_state & rsx::pipeline_state::transform_constants_dirty)
				{
					// Minor optimization: don't compare values if we already know we need invalidation
					copy_data_swap_u32(values, fifo_span.data(), rcount);
				}
				else
				{
					if (copy_data_swap_u32_cmp(values, fifo_span.data(), rcount))
					{
						// Transform constants invalidation is expensive (~8k bytes per update)
						rsx->m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
					}
				}

				rsx->fifo_ctrl->skip_methods(rcount - 1);
			}
		};

		struct set_transform_program
		{
			static void impl(thread* rsx, u32 reg, u32 /*arg*/)
			{
				const u32 index = reg - NV4097_SET_TRANSFORM_PROGRAM;

				// FIFO args count including this one
				const u32 fifo_args_cnt = rsx->fifo_ctrl->get_remaining_args_count() + 1;

				// The range of methods this function resposible to
				const u32 method_range = 32 - index;

				// Get limit imposed by FIFO PUT (if put is behind get it will result in a number ignored by min)
				const u32 fifo_read_limit = static_cast<u32>(((rsx->ctrl->put & ~3ull) - (rsx->fifo_ctrl->get_pos())) / 4);

				const u32 count = std::min<u32>({fifo_args_cnt, fifo_read_limit, method_range});

				const u32 load_pos = rsx::method_registers.transform_program_load();

				u32 rcount = count;

				if (const u32 max = load_pos * 4 + rcount + (index % 4);
					max > max_vertex_program_instructions * 4)
				{
					rsx_log.warning("Program buffer overflow! Attempted to write %u VP instructions.", max / 4);
					rcount -= max - (max_vertex_program_instructions * 4);
				}

				const auto fifo_span = rsx->fifo_ctrl->get_current_arg_ptr();

				if (fifo_span.size() < rcount)
				{
					rcount = ::size32(fifo_span);
				}

				copy_data_swap_u32(&rsx::method_registers.transform_program[load_pos * 4 + index % 4], fifo_span.data(), rcount);

				rsx->m_graphics_state |= rsx::pipeline_state::vertex_program_ucode_dirty;
				rsx::method_registers.transform_program_load_set(load_pos + ((rcount + index % 4) / 4));
				rsx->fifo_ctrl->skip_methods(rcount - 1);
			}
		};

		void set_transform_program_start(thread* rsx, u32 reg, u32)
		{
			if (method_registers.registers[reg] != method_registers.register_previous_value)
			{
				rsx->m_graphics_state |= rsx::pipeline_state::vertex_program_ucode_dirty;
			}
		}

		void set_vertex_attribute_output_mask(thread* rsx, u32 reg, u32)
		{
			if (method_registers.registers[reg] != method_registers.register_previous_value)
			{
				rsx->m_graphics_state |= rsx::pipeline_state::vertex_program_state_dirty;
			}
		}

		void set_begin_end(thread* rsxthr, u32 /*reg*/, u32 arg)
		{
			// Ignore upper bits
			if (const u8 prim = static_cast<u8>(arg))
			{
				const auto primitive_type = to_primitive_type(prim);
				if (!primitive_type)
				{
					rsxthr->in_begin_end = true;

					rsx_log.warning("Invalid NV4097_SET_BEGIN_END value: 0x%x", arg);
					return;
				}

				rsx::method_registers.current_draw_clause.reset(primitive_type);
				rsxthr->begin();
				return;
			}

			// Check if we have immediate mode vertex data in a driver-local buffer
			if (rsx::method_registers.current_draw_clause.command == rsx::draw_command::none)
			{
				const u32 push_buffer_vertices_count = rsxthr->get_push_buffer_vertex_count();
				const u32 push_buffer_index_count = rsxthr->get_push_buffer_index_count();

				// Need to set this flag since it overrides some register contents
				rsx::method_registers.current_draw_clause.is_immediate_draw = true;

				if (push_buffer_index_count)
				{
					rsx::method_registers.current_draw_clause.command = rsx::draw_command::indexed;
					rsx::method_registers.current_draw_clause.append(0, push_buffer_index_count);
				}
				else if (push_buffer_vertices_count)
				{
					rsx::method_registers.current_draw_clause.command = rsx::draw_command::array;
					rsx::method_registers.current_draw_clause.append(0, push_buffer_vertices_count);
				}
			}
			else
			{
				rsx::method_registers.current_draw_clause.is_immediate_draw = false;
			}

			if (!rsx::method_registers.current_draw_clause.empty())
			{
				rsx::method_registers.current_draw_clause.compile();

				if (g_cfg.video.disable_video_output)
				{
					rsxthr->execute_nop_draw();
					rsxthr->rsx::thread::end();
					return;
				}

				rsxthr->end();
			}
			else
			{
				rsxthr->in_begin_end = false;
			}

			if (rsxthr->pause_on_draw && rsxthr->pause_on_draw.exchange(false))
			{
				rsxthr->state -= cpu_flag::dbg_step;
				rsxthr->state += cpu_flag::dbg_pause;
				rsxthr->check_state();
			}
		}

		vm::addr_t get_report_data_impl(u32 offset)
		{
			u32 location = 0;
			blit_engine::context_dma report_dma = method_registers.context_dma_report();

			switch (report_dma)
			{
			case blit_engine::context_dma::to_memory_get_report: location = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_LOCAL; break;
			case blit_engine::context_dma::report_location_main: location = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_MAIN; break;
			case blit_engine::context_dma::memory_host_buffer: location = CELL_GCM_CONTEXT_DMA_MEMORY_HOST_BUFFER; break;
			default:
				return vm::addr_t(0);
			}

			return vm::cast(get_address(offset, location));
		}

		void get_report(thread* rsx, u32 /*reg*/, u32 arg)
		{
			u8 type = arg >> 24;
			u32 offset = arg & 0xffffff;

			auto address_ptr = get_report_data_impl(offset);
			if (!address_ptr)
			{
				rsx_log.error("Bad argument passed to NV4097_GET_REPORT, arg=0x%X", arg);
				return;
			}

			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:
				rsx->get_zcull_stats(type, address_ptr);
				break;
			default:
				rsx_log.error("NV4097_GET_REPORT: Bad type %d", type);

				vm::_ref<atomic_t<CellGcmReportData>>(address_ptr).atomic_op([&](CellGcmReportData& data)
				{
					data.timer = rsx->timestamp();
					data.padding = 0;
				});
				break;
			}
		}

		void clear_report_value(thread* rsx, u32 /*reg*/, u32 arg)
		{
			switch (arg)
			{
			case CELL_GCM_ZPASS_PIXEL_CNT:
			case CELL_GCM_ZCULL_STATS:
				break;
			default:
				rsx_log.error("NV4097_CLEAR_REPORT_VALUE: Bad type: %d", arg);
				break;
			}

			rsx->clear_zcull_stats(arg);
		}

		void set_render_mode(thread* rsx, u32, u32 arg)
		{
			const u32 mode = arg >> 24;
			switch (mode)
			{
			case 1:
				rsx->disable_conditional_rendering();
				return;
			case 2:
				break;
			default:
				rsx_log.error("Unknown render mode %d", mode);
				return;
			}

			const u32 offset = arg & 0xffffff;
			auto address_ptr = get_report_data_impl(offset);

			if (!address_ptr)
			{
				rsx_log.error("Bad argument passed to NV4097_SET_RENDER_ENABLE, arg=0x%X", arg);
				return;
			}

			// Defer conditional render evaluation
			rsx->enable_conditional_rendering(address_ptr);
		}

		void set_zcull_render_enable(thread* rsx, u32, u32)
		{
			rsx->notify_zcull_info_changed();
		}

		void set_zcull_stats_enable(thread* rsx, u32, u32)
		{
			rsx->notify_zcull_info_changed();
		}

		void set_zcull_pixel_count_enable(thread* rsx, u32, u32)
		{
			rsx->notify_zcull_info_changed();
		}

		void sync(thread* rsx, u32, u32)
		{
			rsx->sync();
		}

		void set_shader_program_dirty(thread* rsx, u32, u32)
		{
			rsx->m_graphics_state |= rsx::pipeline_state::fragment_program_ucode_dirty;
		}

		void set_surface_dirty_bit(thread* rsx, u32 reg, u32 arg)
		{
			if (arg == method_registers.register_previous_value)
			{
				return;
			}

			switch (reg)
			{
			case NV4097_SET_SURFACE_COLOR_TARGET:
				rsx->m_graphics_state |= rsx::pipeline_state::pipeline_config_dirty;
				break;
			case NV4097_SET_SURFACE_CLIP_VERTICAL:
			case NV4097_SET_SURFACE_CLIP_HORIZONTAL:
				rsx->m_graphics_state |= rsx::pipeline_state::vertex_state_dirty;
				break;
			default:
				break;
			}

			rsx->m_graphics_state.set(rtt_config_dirty);
			rsx->m_graphics_state.clear(rtt_config_contested);
		}

		void set_surface_format(thread* rsx, u32 reg, u32 arg)
		{
			// The high bits of this register are just log2(dimension), ignore them
			if ((arg & 0xFFFF) == (method_registers.register_previous_value & 0xFFFF))
			{
				return;
			}

			// The important parameters have changed (format, type, antialias)
			rsx->m_graphics_state |= rsx::pipeline_state::pipeline_config_dirty;

			// Check if we need to also update fragment state
			const auto current = method_registers.decode<NV4097_SET_SURFACE_FORMAT>(arg);
			const auto previous = method_registers.decode<NV4097_SET_SURFACE_FORMAT>(method_registers.register_previous_value);

			if (*current.antialias() != *previous.antialias() ||                         // Antialias control has changed, update ROP parameters
				current.is_integer_color_format() != previous.is_integer_color_format()) // The type of color format also requires ROP control update
			{
				rsx->m_graphics_state |= rsx::pipeline_state::fragment_state_dirty;
			}

			set_surface_dirty_bit(rsx, reg, arg);
		}

		void set_surface_options_dirty_bit(thread* rsx, u32 reg, u32 arg)
		{
			if (arg != method_registers.register_previous_value)
			{
				rsx->on_framebuffer_options_changed(reg);
				rsx->m_graphics_state |= rsx::pipeline_config_dirty;
			}
		}

		void set_stencil_op(thread* rsx, u32 reg, u32 arg)
		{
			if (arg == method_registers.register_previous_value)
			{
				return;
			}

			const auto typed = to_stencil_op(arg);
			if (typed) [[ likely ]]
			{
				set_surface_options_dirty_bit(rsx, reg, arg);
			}
			else
			{
				method_registers.decode(reg, method_registers.register_previous_value);
			}
		}

		void set_vertex_base_offset(thread* rsx, u32 reg, u32 arg)
		{
			if (rsx->in_begin_end &&
				!rsx::method_registers.current_draw_clause.empty() &&
				reg != method_registers.register_previous_value)
			{
				// Revert change to queue later
				method_registers.decode(reg, method_registers.register_previous_value);

				// Insert base mofifier barrier
				method_registers.current_draw_clause.insert_command_barrier(vertex_base_modifier_barrier, arg);
			}
		}

		void set_index_base_offset(thread* rsx, u32 reg, u32 arg)
		{
			if (rsx->in_begin_end &&
				!rsx::method_registers.current_draw_clause.empty() &&
				reg != method_registers.register_previous_value)
			{
				// Revert change to queue later
				method_registers.decode(reg, method_registers.register_previous_value);

				// Insert base mofifier barrier
				method_registers.current_draw_clause.insert_command_barrier(index_base_modifier_barrier, arg);
			}
		}

		template<u32 index>
		struct set_vertex_array_offset
		{
			static void impl(thread* rsx, u32 reg, u32 arg)
			{
				if (rsx->in_begin_end &&
					!rsx::method_registers.current_draw_clause.empty() &&
					reg != method_registers.register_previous_value)
				{
					// Revert change to queue later
					method_registers.decode(reg, method_registers.register_previous_value);

					// Insert offset mofifier barrier
					method_registers.current_draw_clause.insert_command_barrier(vertex_array_offset_modifier_barrier, arg, index);
				}
			}
		};

		void check_index_array_dma(thread* rsx, u32 reg, u32 arg)
		{
			// Check if either location or index type are invalid
			if (arg & ~(CELL_GCM_LOCATION_MAIN | (CELL_GCM_DRAW_INDEX_ARRAY_TYPE_16 << 4)))
			{
				// Ignore invalid value, recover
				method_registers.registers[reg] = method_registers.register_previous_value;
				rsx->recover_fifo();

				rsx_log.error("Invalid NV4097_SET_INDEX_ARRAY_DMA value: 0x%x", arg);
			}
		}

		void set_blend_equation(thread* rsx, u32 reg, u32 arg)
		{
			if (reg == method_registers.register_previous_value)
			{
				return;
			}

			if (to_blend_equation(arg & 0xFFFF) &&
				to_blend_equation((arg >> 16) & 0xFFFF)) [[ likely ]]
			{
				rsx->m_graphics_state |= rsx::pipeline_config_dirty;
			}
			else
			{
				method_registers.decode(reg, method_registers.register_previous_value);
			}
		}

		void set_blend_factor(thread* rsx, u32 reg, u32 arg)
		{
			if (reg == method_registers.register_previous_value)
			{
				return;
			}

			if (to_blend_factor(arg & 0xFFFF) &&
				to_blend_factor((arg >> 16) & 0xFFFF)) [[ likely ]]
			{
				rsx->m_graphics_state |= rsx::pipeline_config_dirty;
			}
			else
			{
				method_registers.decode(reg, method_registers.register_previous_value);
			}
		}

		template<u32 index>
		struct set_texture_dirty_bit
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 /*arg*/)
			{
				rsx->m_textures_dirty[index] = true;

				if (rsx->current_fp_metadata.referenced_textures_mask & (1 << index))
				{
					rsx->m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty;
				}
			}
		};

		template<u32 index>
		struct set_vertex_texture_dirty_bit
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 /*arg*/)
			{
				rsx->m_vertex_textures_dirty[index] = true;

				if (rsx->current_vp_metadata.referenced_textures_mask & (1 << index))
				{
					rsx->m_graphics_state |= rsx::pipeline_state::vertex_program_state_dirty;
				}
			}
		};
	}

	namespace nv308a
	{
		struct color
		{
			static void impl(thread* rsx, u32 reg, u32)
			{
				const u32 out_x_max = method_registers.nv308a_size_out_x();
				const u32 index = reg - NV308A_COLOR;

				if (index >= out_x_max)
				{
					// Skip
					return;
				}

				// Get position of the current command arg
				[[maybe_unused]] const u32 src_offset = rsx->fifo_ctrl->get_pos();

				// FIFO args count including this one
				const u32 fifo_args_cnt = rsx->fifo_ctrl->get_remaining_args_count() + 1;

				// The range of methods this function resposible to
				const u32 method_range = std::min<u32>(0x700 - index, out_x_max - index);

				// Get limit imposed by FIFO PUT (if put is behind get it will result in a number ignored by min)
				const u32 fifo_read_limit = static_cast<u32>(((rsx->ctrl->put & ~3ull) - (rsx->fifo_ctrl->get_pos())) / 4);

				u32 count = std::min<u32>({fifo_args_cnt, fifo_read_limit, method_range});

				const u32 dst_dma = method_registers.blit_engine_output_location_nv3062();
				const u32 dst_offset = method_registers.blit_engine_output_offset_nv3062();
				const u32 out_pitch = method_registers.blit_engine_output_pitch_nv3062();

				const u32 x = method_registers.nv308a_x() + index;
				const u32 y = method_registers.nv308a_y();

				const auto fifo_span = rsx->fifo_ctrl->get_current_arg_ptr();

				if (fifo_span.size() < count)
				{
					count = ::size32(fifo_span);
				}

				// Skip "handled methods"
				rsx->fifo_ctrl->skip_methods(count - 1);

				switch (*method_registers.blit_engine_nv3062_color_format())
				{
				case blit_engine::transfer_destination_format::a8r8g8b8:
				case blit_engine::transfer_destination_format::y32:
				{
					// Bit cast - optimize to mem copy

					const u32 data_length = count * 4;

					const auto dst_address = get_address(dst_offset + (x * 4) + (out_pitch * y), dst_dma, data_length);

					if (!dst_address)
					{
						rsx->recover_fifo();
						return;
					}

					const auto dst = vm::_ptr<u8>(dst_address);
					const auto src = reinterpret_cast<const u8*>(fifo_span.data());

					rsx::reservation_lock<true> rsx_lock(dst_address, data_length);

					if (rsx->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) [[unlikely]]
					{
						// Move last 32 bits
						reinterpret_cast<u32*>(dst)[0] = reinterpret_cast<const u32*>(src)[count - 1];
						rsx->invalidate_fragment_program(dst_dma, dst_offset, 4);
					}
					else
					{
						if (dst_dma & CELL_GCM_LOCATION_MAIN)
						{
							// May overlap
							std::memmove(dst, src, data_length);
						}
						else
						{
							// Never overlaps
							std::memcpy(dst, src, data_length);
						}

						rsx->invalidate_fragment_program(dst_dma, dst_offset, count * 4);
					}

					break;
				}
				case blit_engine::transfer_destination_format::r5g6b5:
				{
					const auto data_length = count * 2;

					const auto dst_address = get_address(dst_offset + (x * 2) + (y * out_pitch), dst_dma, data_length);
					const auto dst = vm::_ptr<u16>(dst_address);
					const auto src = utils::bless<const be_t<u32>>(fifo_span.data());

					if (!dst_address)
					{
						rsx->recover_fifo();
						return;
					}

					rsx::reservation_lock<true> rsx_lock(dst_address, data_length);

					auto convert = [](u32 input) -> u16
					{
						// Input is considered to be ARGB8
						u32 r = (input >> 16) & 0xFF;
						u32 g = (input >> 8) & 0xFF;
						u32 b = input & 0xFF;

						r = (r * 32) / 255;
						g = (g * 64) / 255;
						b = (b * 32) / 255;
						return static_cast<u16>((r << 11) | (g << 5) | b);
					};

					if (rsx->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) [[unlikely]]
					{
						// Move last 16 bits
						dst[0] = convert(src[count - 1]);
						rsx->invalidate_fragment_program(dst_dma, dst_offset, 2);
						break;
					}

					for (u32 i = 0; i < count; i++)
					{
						dst[i] = convert(src[i]);
					}

					rsx->invalidate_fragment_program(dst_dma, dst_offset, count * 2);
					break;
				}
				default:
				{
					fmt::throw_exception("Unreachable");
				}
				}
			}
		};
	}

	namespace nv3089
	{
		void image_in(thread* rsx, u32 /*reg*/, u32 /*arg*/)
		{
			const rsx::blit_engine::transfer_operation operation = method_registers.blit_engine_operation();

			const u16 out_x = method_registers.blit_engine_output_x();
			const u16 out_y = method_registers.blit_engine_output_y();
			const u16 out_w = method_registers.blit_engine_output_width();
			const u16 out_h = method_registers.blit_engine_output_height();

			const u16 in_w = method_registers.blit_engine_input_width();
			const u16 in_h = method_registers.blit_engine_input_height();

			const blit_engine::transfer_origin in_origin = method_registers.blit_engine_input_origin();
			const blit_engine::transfer_interpolator in_inter = method_registers.blit_engine_input_inter();
			auto src_color_format = method_registers.blit_engine_src_color_format();

			const f32 scale_x = method_registers.blit_engine_ds_dx();
			const f32 scale_y = method_registers.blit_engine_dt_dy();

			// Clipping
			// Validate that clipping rect will fit onto both src and dst regions
			const u16 clip_w = std::min(method_registers.blit_engine_clip_width(), out_w);
			const u16 clip_h = std::min(method_registers.blit_engine_clip_height(), out_h);

			// Check both clip dimensions and dst dimensions
			if (clip_w == 0 || clip_h == 0)
			{
				rsx_log.warning("NV3089_IMAGE_IN: Operation NOPed out due to empty regions");
				return;
			}

			if (in_w == 0 || in_h == 0)
			{
				// Input cant be an empty region
				fmt::throw_exception("NV3089_IMAGE_IN_SIZE: Invalid blit dimensions passed (in_w=%d, in_h=%d)", in_w, in_h);
			}

			u16 clip_x = method_registers.blit_engine_clip_x();
			u16 clip_y = method_registers.blit_engine_clip_y();

			//Fit onto dst
			if (clip_x && (out_x + clip_x + clip_w) > out_w) clip_x = 0;
			if (clip_y && (out_y + clip_y + clip_h) > out_h) clip_y = 0;

			u16 in_pitch = method_registers.blit_engine_input_pitch();

			switch (in_origin)
			{
			case blit_engine::transfer_origin::corner:
			case blit_engine::transfer_origin::center:
				break;
			default:
				rsx_log.warning("NV3089_IMAGE_IN_SIZE: unknown origin (%d)", static_cast<u8>(in_origin));
			}

			if (operation != rsx::blit_engine::transfer_operation::srccopy)
			{
				rsx_log.error("NV3089_IMAGE_IN_SIZE: unknown operation (0x%x)", method_registers.registers[NV3089_SET_OPERATION]);
				rsx->recover_fifo();
				return;
			}

			if (!src_color_format)
			{
				rsx_log.error("NV3089_IMAGE_IN_SIZE: unknown src color format (0x%x)", method_registers.registers[NV3089_SET_COLOR_FORMAT]);
				rsx->recover_fifo();
				return;
			}

			const u32 src_offset = method_registers.blit_engine_input_offset();
			const u32 src_dma = method_registers.blit_engine_input_location();

			u32 dst_offset;
			u32 dst_dma = 0;
			rsx::blit_engine::transfer_destination_format dst_color_format;
			u32 out_pitch = 0;
			[[maybe_unused]] u32 out_alignment = 64;
			bool is_block_transfer = false;

			switch (method_registers.blit_engine_context_surface())
			{
			case blit_engine::context_surface::surface2d:
			{
				dst_dma = method_registers.blit_engine_output_location_nv3062();
				dst_offset = method_registers.blit_engine_output_offset_nv3062();
				out_pitch = method_registers.blit_engine_output_pitch_nv3062();
				out_alignment = method_registers.blit_engine_output_alignment_nv3062();
				is_block_transfer = fcmp(scale_x, 1.f) && fcmp(scale_y, 1.f);

				if (auto dst_fmt = method_registers.blit_engine_nv3062_color_format(); !dst_fmt)
				{
					rsx_log.error("NV3089_IMAGE_IN_SIZE: unknown NV3062 dst color format (0x%x)", method_registers.registers[NV3062_SET_COLOR_FORMAT]);
					rsx->recover_fifo();
					return;
				}
				else
				{
					dst_color_format = dst_fmt;
				}

				break;
			}
			case blit_engine::context_surface::swizzle2d:
			{
				dst_dma = method_registers.blit_engine_nv309E_location();
				dst_offset = method_registers.blit_engine_nv309E_offset();

				if (auto dst_fmt = method_registers.blit_engine_output_format_nv309E(); !dst_fmt)
				{
					rsx_log.error("NV3089_IMAGE_IN_SIZE: unknown NV309E dst color format (0x%x)", method_registers.registers[NV309E_SET_FORMAT]);
					rsx->recover_fifo();
					return;
				}
				else
				{
					dst_color_format = dst_fmt;
				}

				break;
			}
			default:
				rsx_log.error("NV3089_IMAGE_IN_SIZE: unknown m_context_surface (0x%x)", static_cast<u8>(method_registers.blit_engine_context_surface()));
				return;
			}

			const u32 in_bpp = (src_color_format == rsx::blit_engine::transfer_source_format::r5g6b5) ? 2 : 4; // bytes per pixel
			const u32 out_bpp = (dst_color_format == rsx::blit_engine::transfer_destination_format::r5g6b5) ? 2 : 4;

			if (out_pitch == 0)
			{
				out_pitch = out_bpp * out_w;
			}

			if (in_pitch == 0)
			{
				in_pitch = in_bpp * in_w;
			}

			if (in_bpp != out_bpp)
			{
				is_block_transfer = false;
			}

			u16 in_x, in_y;
			if (in_origin == blit_engine::transfer_origin::center)
			{
				// Convert to normal u,v addressing. Under this scheme offset of 1 is actually half-way inside pixel 0
				const float x = std::max(method_registers.blit_engine_in_x(), 0.5f);
				const float y = std::max(method_registers.blit_engine_in_y(), 0.5f);
				in_x = static_cast<u16>(std::floor(x - 0.5f));
				in_y = static_cast<u16>(std::floor(y - 0.5f));
			}
			else
			{
				in_x = static_cast<u16>(std::floor(method_registers.blit_engine_in_x()));
				in_y = static_cast<u16>(std::floor(method_registers.blit_engine_in_y()));
			}

			// Check for subpixel addressing
			if (scale_x < 1.f)
			{
				float dst_x = in_x * scale_x;
				in_x = static_cast<u16>(std::floor(dst_x) / scale_x);
			}

			if (scale_y < 1.f)
			{
				float dst_y = in_y * scale_y;
				in_y = static_cast<u16>(std::floor(dst_y) / scale_y);
			}

			const u32 in_offset = in_x * in_bpp + in_pitch * in_y;
			const u32 out_offset = out_x * out_bpp + out_pitch * out_y;

			const u32 src_line_length = (in_w * in_bpp);

			u32 src_address = 0;
			const u32 dst_address = get_address(dst_offset, dst_dma, 1); // TODO: Add size

			if (is_block_transfer && (clip_h == 1 || (in_pitch == out_pitch && src_line_length == in_pitch)))
			{
				const u32 nb_lines = std::min(clip_h, in_h);
				const u32 data_length = nb_lines * src_line_length;

				if (src_address = get_address(src_offset, src_dma, data_length);
					!src_address || !dst_address)
				{
					rsx->recover_fifo();
					return;
				}

				rsx->invalidate_fragment_program(dst_dma, dst_offset, data_length);

				if (const auto result = rsx->read_barrier(src_address, data_length, false);
					result == rsx::result_zcull_intr)
				{
					if (rsx->copy_zcull_stats(src_address, data_length, dst_address) == data_length)
					{
						// All writes deferred
						return;
					}
				}
			}
			else
			{
				const u16 read_h = std::min(static_cast<u16>(clip_h / scale_y), in_h);
				const u32 data_length = in_pitch * (read_h - 1) + src_line_length;

				if (src_address = get_address(src_offset, src_dma, data_length);
					!src_address || !dst_address)
				{
					rsx->recover_fifo();
					return;
				}

				rsx->invalidate_fragment_program(dst_dma, dst_offset, data_length);
				rsx->read_barrier(src_address, data_length, true);
			}

			if (src_address == dst_address &&
				in_w == clip_w && in_h == clip_h &&
				in_pitch == out_pitch &&
				rsx::fcmp(scale_x, 1.f) && rsx::fcmp(scale_y, 1.f))
			{
				// NULL operation
				rsx_log.warning("NV3089_IMAGE_IN: Operation writes memory onto itself with no modification (move-to-self). Will ignore.");
				return;
			}

			u8* pixels_src = vm::_ptr<u8>(src_address + in_offset);
			u8* pixels_dst = vm::_ptr<u8>(dst_address + out_offset);

			if (dst_color_format != rsx::blit_engine::transfer_destination_format::r5g6b5 &&
				dst_color_format != rsx::blit_engine::transfer_destination_format::a8r8g8b8)
			{
				fmt::throw_exception("NV3089_IMAGE_IN_SIZE: unknown dst_color_format (%d)", static_cast<u8>(dst_color_format));
			}

			if (src_color_format != rsx::blit_engine::transfer_source_format::r5g6b5 &&
				src_color_format != rsx::blit_engine::transfer_source_format::a8r8g8b8)
			{
				// Alpha has no meaning in both formats
				if (src_color_format == rsx::blit_engine::transfer_source_format::x8r8g8b8)
				{
					src_color_format = rsx::blit_engine::transfer_source_format::a8r8g8b8;
				}
				else
				{
					// TODO: Support more formats
					fmt::throw_exception("NV3089_IMAGE_IN_SIZE: unknown src_color_format (%d)", static_cast<u8>(*src_color_format));
				}
			}

			u32 convert_w = static_cast<u32>(std::abs(scale_x) * in_w);
			u32 convert_h = static_cast<u32>(std::abs(scale_y) * in_h);

			if (convert_w == 0 || convert_h == 0)
			{
				rsx_log.error("NV3089_IMAGE_IN: Invalid dimensions or scaling factor. Request ignored (ds_dx=%f, dt_dy=%f)",
					method_registers.blit_engine_ds_dx(), method_registers.blit_engine_dt_dy());
				return;
			}

			// Lock here. RSX cannot execute any locking operations from this point, including ZCULL read barriers
			auto res = ::rsx::reservation_lock<true>(dst_address, out_pitch * out_h, src_address, in_pitch * in_h);

			if (!g_cfg.video.force_cpu_blit_processing && (dst_dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER || src_dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER))
			{
				blit_src_info src_info = {};
				blit_dst_info dst_info = {};

				src_info.format = src_color_format;
				src_info.origin = in_origin;
				src_info.width = in_w;
				src_info.height = in_h;
				src_info.pitch = in_pitch;
				src_info.offset_x = in_x;
				src_info.offset_y = in_y;
				src_info.rsx_address = src_address;
				src_info.pixels = pixels_src;

				dst_info.format = dst_color_format;
				dst_info.width = convert_w;
				dst_info.height = convert_h;
				dst_info.clip_x = clip_x;
				dst_info.clip_y = clip_y;
				dst_info.clip_width = clip_w;
				dst_info.clip_height = clip_h;
				dst_info.offset_x = out_x;
				dst_info.offset_y = out_y;
				dst_info.pitch = out_pitch;
				dst_info.scale_x = scale_x;
				dst_info.scale_y = scale_y;
				dst_info.rsx_address = dst_address;
				dst_info.pixels = pixels_dst;
				dst_info.swizzled = (method_registers.blit_engine_context_surface() == blit_engine::context_surface::swizzle2d);

				if (rsx->scaled_image_from_memory(src_info, dst_info, in_inter == blit_engine::transfer_interpolator::foh))
					return;
			}

			std::vector<u8> temp1, temp2, temp3, sw_temp;

			if (scale_y < 0 || scale_x < 0)
			{
				const u32 packed_pitch = in_w * in_bpp;
				temp1.resize(packed_pitch * in_h);

				const s32 stride_y = (scale_y < 0 ? -1 : 1) * s32{in_pitch};

				for (u32 y = 0; y < in_h; ++y)
				{
					u8 *dst = temp1.data() + (packed_pitch * y);
					u8 *src = pixels_src + (static_cast<s32>(y) * stride_y);

					if (scale_x < 0)
					{
						if (in_bpp == 2)
						{
							rsx::memcpy_r<u16>(dst, src, in_w);
						}
						else
						{
							rsx::memcpy_r<u32>(dst, src, in_w);
						}
					}
					else
					{
						std::memcpy(dst, src, packed_pitch);
					}
				}

				pixels_src = temp1.data();
				in_pitch = packed_pitch;
			}

			const AVPixelFormat in_format = (src_color_format == rsx::blit_engine::transfer_source_format::r5g6b5) ? AV_PIX_FMT_RGB565BE : AV_PIX_FMT_ARGB;
			const AVPixelFormat out_format = (dst_color_format == rsx::blit_engine::transfer_destination_format::r5g6b5) ? AV_PIX_FMT_RGB565BE : AV_PIX_FMT_ARGB;

			const bool need_clip =
				clip_w != in_w ||
				clip_h != in_h ||
				clip_x > 0 || clip_y > 0 ||
				convert_w != out_w || convert_h != out_h;

			const bool need_convert = out_format != in_format || !rsx::fcmp(fabsf(scale_x), 1.f) || !rsx::fcmp(fabsf(scale_y), 1.f);
			const u32 slice_h = static_cast<u32>(std::ceil(static_cast<f32>(clip_h + clip_y) / scale_y));

			if (method_registers.blit_engine_context_surface() != blit_engine::context_surface::swizzle2d)
			{
				if (!need_convert)
				{
					const bool is_overlapping = scale_x > 0 && scale_y > 0 && dst_dma == src_dma && [&]() -> bool
					{
						const u32 src_max = src_offset + in_pitch * (in_h - 1) + (in_bpp * in_w);
						const u32 dst_max = dst_offset + out_pitch * (out_h - 1) + (out_bpp * out_w);
						return (src_offset >= dst_offset && src_offset < dst_max) ||
						 (dst_offset >= src_offset && dst_offset < src_max);
					}();

					if (is_overlapping)
					{
						if (need_clip)
						{
							temp2.resize(out_pitch * clip_h);

							clip_image_may_overlap(pixels_dst, pixels_src, clip_x, clip_y, clip_w, clip_h, out_bpp, in_pitch, out_pitch, temp2.data());
						}
						else if (out_pitch != in_pitch || out_pitch != out_bpp * out_w)
						{
							const u32 buffer_pitch = out_bpp * out_w;
							temp2.resize(buffer_pitch * out_h);
							std::add_pointer_t<u8> buf = temp2.data(), pixels = pixels_src;

							// Read the whole buffer from source
							for (u32 y = 0; y < out_h; ++y)
							{
								std::memcpy(buf, pixels, buffer_pitch);
								pixels += in_pitch;
								buf += buffer_pitch;
							}

							buf = temp2.data(), pixels = pixels_dst;

							// Write to destination
							for (u32 y = 0; y < out_h; ++y)
							{
								std::memcpy(pixels, buf, buffer_pitch);
								pixels += out_pitch;
								buf += buffer_pitch;
							}
						}
						else
						{
							std::memmove(pixels_dst, pixels_src, out_pitch * out_h);
						}
					}
					else
					{
						if (need_clip)
						{
							clip_image(pixels_dst, pixels_src, clip_x, clip_y, clip_w, clip_h, out_bpp, in_pitch, out_pitch);
						}
						else if (out_pitch != in_pitch || out_pitch != out_bpp * out_w)
						{
							u8 *dst = pixels_dst, *src = pixels_src;

							for (u32 y = 0; y < out_h; ++y)
							{
								std::memcpy(dst, src, out_w * out_bpp);
								dst += out_pitch;
								src += in_pitch;
							}
						}
						else
						{
							std::memcpy(pixels_dst, pixels_src, out_pitch * out_h);
						}
					}
				}
				else
				{
					if (need_clip)
					{
						temp2.resize(out_pitch * std::max<u32>(convert_h, clip_h));

						convert_scale_image(temp2.data(), out_format, convert_w, convert_h, out_pitch,
							pixels_src, in_format, in_w, in_h, in_pitch, slice_h, in_inter == blit_engine::transfer_interpolator::foh);

						clip_image(pixels_dst, temp2.data(), clip_x, clip_y, clip_w, clip_h, out_bpp, out_pitch, out_pitch);
					}
					else
					{
						convert_scale_image(pixels_dst, out_format, out_w, out_h, out_pitch,
							pixels_src, in_format, in_w, in_h, in_pitch, slice_h, in_inter == blit_engine::transfer_interpolator::foh);
					}
				}
			}
			else
			{
				if (need_convert || need_clip)
				{
					if (need_clip)
					{
						temp3.resize(out_pitch * clip_h);

						if (need_convert)
						{
							temp2.resize(out_pitch * std::max<u32>(convert_h, clip_h));

							convert_scale_image(temp2.data(), out_format, convert_w, convert_h, out_pitch,
								pixels_src, in_format, in_w, in_h, in_pitch, slice_h, in_inter == blit_engine::transfer_interpolator::foh);

							clip_image(temp3.data(), temp2.data(), clip_x, clip_y, clip_w, clip_h, out_bpp, out_pitch, out_pitch);
						}
						else
						{
							clip_image(temp3.data(), pixels_src, clip_x, clip_y, clip_w, clip_h, out_bpp, in_pitch, out_pitch);
						}
					}
					else
					{
						temp3.resize(out_pitch * out_h);

						convert_scale_image(temp3.data(), out_format, out_w, out_h, out_pitch,
							pixels_src, in_format, in_w, in_h, in_pitch, slice_h, in_inter == blit_engine::transfer_interpolator::foh);
					}

					pixels_src = temp3.data();
					in_pitch = out_pitch;
				}

				// It looks like rsx may ignore the requested swizzle size and just always
				// round up to nearest power of 2
				/*u8 sw_width_log2 = method_registers.nv309e_sw_width_log2();
				u8 sw_height_log2 = method_registers.nv309e_sw_height_log2();

				// 0 indicates height of 1 pixel
				sw_height_log2 = sw_height_log2 == 0 ? 1 : sw_height_log2;

				// swizzle based on destination size
				u16 sw_width = 1 << sw_width_log2;
				u16 sw_height = 1 << sw_height_log2;
				*/

				u32 sw_width = next_pow2(out_w);
				u32 sw_height = next_pow2(out_h);

				u8* linear_pixels = pixels_src;
				u8* swizzled_pixels = pixels_dst;

				// Check and pad texture out if we are given non power of 2 output
				if (sw_width != out_w || sw_height != out_h)
				{
					sw_temp.resize(out_bpp * sw_width * sw_height);

					switch (out_bpp)
					{
					case 1:
						pad_texture<u8>(linear_pixels, sw_temp.data(), out_w, out_h, sw_width, sw_height);
						break;
					case 2:
						pad_texture<u16>(linear_pixels, sw_temp.data(), out_w, out_h, sw_width, sw_height);
						break;
					case 4:
						pad_texture<u32>(linear_pixels, sw_temp.data(), out_w, out_h, sw_width, sw_height);
						break;
					}

					linear_pixels = sw_temp.data();
				}

				switch (out_bpp)
				{
				case 1:
					convert_linear_swizzle<u8, false>(linear_pixels, swizzled_pixels, sw_width, sw_height, in_pitch);
					break;
				case 2:
					convert_linear_swizzle<u16, false>(linear_pixels, swizzled_pixels, sw_width, sw_height, in_pitch);
					break;
				case 4:
					convert_linear_swizzle<u32, false>(linear_pixels, swizzled_pixels, sw_width, sw_height, in_pitch);
					break;
				}
			}
		}
	}

	namespace nv0039
	{
		void buffer_notify(thread* rsx, u32, u32 arg)
		{
			s32 in_pitch = method_registers.nv0039_input_pitch();
			s32 out_pitch = method_registers.nv0039_output_pitch();
			const u32 line_length = method_registers.nv0039_line_length();
			const u32 line_count = method_registers.nv0039_line_count();
			const u8 out_format = method_registers.nv0039_output_format();
			const u8 in_format = method_registers.nv0039_input_format();
			const u32 notify = arg;

			if (!line_count || !line_length)
			{
				rsx_log.warning("NV0039_BUFFER_NOTIFY NOPed out: pitch(in=0x%x, out=0x%x), line(len=0x%x, cnt=0x%x), fmt(in=0x%x, out=0x%x), notify=0x%x",
					in_pitch, out_pitch, line_length, line_count, in_format, out_format, notify);
				return;
			}

			rsx_log.trace("NV0039_BUFFER_NOTIFY: pitch(in=0x%x, out=0x%x), line(len=0x%x, cnt=0x%x), fmt(in=0x%x, out=0x%x), notify=0x%x",
				in_pitch, out_pitch, line_length, line_count, in_format, out_format, notify);

			u32 src_offset = method_registers.nv0039_input_offset();
			u32 src_dma = method_registers.nv0039_input_location();

			u32 dst_offset = method_registers.nv0039_output_offset();
			u32 dst_dma = method_registers.nv0039_output_location();

			const bool is_block_transfer = (in_pitch == out_pitch && out_pitch + 0u == line_length);
			const auto read_address = get_address(src_offset, src_dma);
			const auto write_address = get_address(dst_offset, dst_dma);
			const auto read_length = in_pitch * (line_count - 1) + line_length;
			const auto write_length = out_pitch * (line_count - 1) + line_length;

			rsx->invalidate_fragment_program(dst_dma, dst_offset, write_length);

			if (const auto result = rsx->read_barrier(read_address, read_length, !is_block_transfer);
				result == rsx::result_zcull_intr)
			{
				// This transfer overlaps will zcull data pool
				if (rsx->copy_zcull_stats(read_address, read_length, write_address) == write_length)
				{
					// All writes deferred
					return;
				}
			}

			auto res = ::rsx::reservation_lock<true>(write_address, write_length, read_address, read_length);

			u8 *dst = vm::_ptr<u8>(write_address);
			const u8 *src = vm::_ptr<u8>(read_address);

			const bool is_overlapping = dst_dma == src_dma && [&]() -> bool
			{
				const u32 src_max = src_offset + read_length;
				const u32 dst_max = dst_offset + (out_pitch * (line_count - 1) + line_length);
				return (src_offset >= dst_offset && src_offset < dst_max) ||
				 (dst_offset >= src_offset && dst_offset < src_max);
			}();

			if (in_format > 1 || out_format > 1) [[ unlikely ]]
			{
				// The formats are just input channel strides. You can use this to do cool tricks like gathering channels
				// Very rare, only seen in use by Destiny
				// TODO: Hw accel
				for (u32 row = 0; row < line_count; ++row)
				{
					auto dst_ptr = dst;
					auto src_ptr = src;
					while (src_ptr < src + line_length)
					{
						*dst_ptr = *src_ptr;

						src_ptr += in_format;
						dst_ptr += out_format;
					}

					dst += out_pitch;
					src += in_pitch;
				}
			}
			else if (is_overlapping) [[ unlikely ]]
			{
				if (is_block_transfer)
				{
					std::memmove(dst, src, read_length);
				}
				else
				{
					std::vector<u8> temp(line_length * line_count);
					u8* buf = temp.data();

					for (u32 y = 0; y < line_count; ++y)
					{
						std::memcpy(buf, src, line_length);
						buf += line_length;
						src += in_pitch;
					}

					buf = temp.data();

					for (u32 y = 0; y < line_count; ++y)
					{
						std::memcpy(dst, buf, line_length);
						buf += line_length;
						dst += out_pitch;
					}
				}
			}
			else
			{
				if (is_block_transfer)
				{
					std::memcpy(dst, src, read_length);
				}
				else
				{
					for (u32 i = 0; i < line_count; ++i)
					{
						std::memcpy(dst, src, line_length);
						dst += out_pitch;
						src += in_pitch;
					}
				}
			}

			//res->release(0);
		}
	}

	void flip_command(thread* rsx, u32, u32 arg)
	{
		ensure(rsx->isHLE);

		if (rsx->vblank_at_flip != umax)
		{
			rsx->flip_notification_count++;
		}

		if (auto ptr = rsx->queue_handler)
		{
			rsx->intr_thread->cmd_list
			({
				{ ppu_cmd::set_args, 1 }, u64{1},
				{ ppu_cmd::lle_call, ptr },
				{ ppu_cmd::sleep, 0 }
			});

			rsx->intr_thread->cmd_notify++;
			rsx->intr_thread->cmd_notify.notify_one();
		}

		rsx->reset();
		rsx->on_frame_end(arg);
		rsx->request_emu_flip(arg);
		vm::_ref<atomic_t<u128>>(rsx->label_addr + 0x10).store(u128{});
	}

	void user_command(thread* rsx, u32, u32 arg)
	{
		if (!rsx->isHLE)
		{
			sys_rsx_context_attribute(0x55555555, 0xFEF, 0, arg, 0, 0);
			return;
		}

		if (auto ptr = rsx->user_handler)
		{
			rsx->intr_thread->cmd_list
			({
				{ ppu_cmd::set_args, 1 }, u64{arg},
				{ ppu_cmd::lle_call, ptr },
				{ ppu_cmd::sleep, 0 }
			});

			rsx->intr_thread->cmd_notify++;
			rsx->intr_thread->cmd_notify.notify_one();
		}
	}

	namespace gcm
	{
		template<u32 index>
		struct driver_flip
		{
			static void impl(thread*, u32 /*reg*/, u32 arg)
			{
				sys_rsx_context_attribute(0x55555555, 0x102, index, arg, 0, 0);
			}
		};

		template<u32 index>
		struct queue_flip
		{
			static void impl(thread* rsx, u32 /*reg*/, u32 arg)
			{
				if (rsx->vblank_at_flip != umax)
				{
					rsx->flip_notification_count++;
				}

				sys_rsx_context_attribute(0x55555555, 0x103, index, arg, 0, 0);
			}
		};
	}

	namespace fifo
	{
		void draw_barrier(thread* rsx, u32, u32)
		{
			if (rsx->in_begin_end)
			{
				if (!method_registers.current_draw_clause.is_disjoint_primitive)
				{
					// Enable primitive barrier request
					method_registers.current_draw_clause.primitive_barrier_enable = true;
				}
			}
		}
	}

	void rsx_state::init()
	{
		// Reset all regsiters
		registers.fill(0);
		state_signals.fill(0);
		transform_program.fill(0);
		transform_constants = {};
		current_draw_clause = {};
		register_vertex_info = {};

		// Special values set at initialization, these are not set by a context reset
		registers[NV4097_SET_SHADER_PROGRAM] = (0 << 2) | (CELL_GCM_LOCATION_LOCAL + 1);

		for (u32 i = 0; i < 16; i++)
		{
			registers[NV4097_SET_TEXTURE_FORMAT + (i * 8)] = (1 << 16 /* mipmap */) | ((CELL_GCM_TEXTURE_R5G6B5 | CELL_GCM_TEXTURE_SZ | CELL_GCM_TEXTURE_NR) << 8) | (2 << 4 /* 2D */) | (CELL_GCM_LOCATION_LOCAL + 1);
		}

		for (u32 i = 0; i < 4; i++)
		{
			registers[NV4097_SET_VERTEX_TEXTURE_FORMAT + (i * 8)] = (1 << 16 /* mipmap */) | ((CELL_GCM_TEXTURE_X32_FLOAT | CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_NR) << 8) | (2 << 4 /* 2D */) | (CELL_GCM_LOCATION_LOCAL + 1);
		}

		registers[NV406E_SET_CONTEXT_DMA_SEMAPHORE] = CELL_GCM_CONTEXT_DMA_SEMAPHORE_R;
		registers[NV4097_SET_CONTEXT_DMA_SEMAPHORE] = CELL_GCM_CONTEXT_DMA_SEMAPHORE_RW;

		{
			// Commands injected by cellGcmInit
			registers[NV406E_SEMAPHORE_OFFSET] = 0x30;
			registers[NV406E_SEMAPHORE_ACQUIRE] = 0x1;
			registers[NV406E_SET_CONTEXT_DMA_SEMAPHORE] = 0x66616661;
			registers[0x0] = 0x31337000;
			registers[NV4097_SET_CONTEXT_DMA_NOTIFIES] = 0x66604200;
			registers[NV4097_SET_CONTEXT_DMA_A] = 0xfeed0000;
			registers[NV4097_SET_CONTEXT_DMA_B] = 0xfeed0001;
			registers[NV4097_SET_CONTEXT_DMA_COLOR_B] = 0xfeed0000;
			registers[NV4097_SET_CONTEXT_DMA_STATE] = 0x0;
			registers[NV4097_SET_CONTEXT_DMA_COLOR_A] = 0xfeed0000;
			registers[NV4097_SET_CONTEXT_DMA_ZETA] = 0xfeed0000;
			registers[NV4097_SET_CONTEXT_DMA_VERTEX_A] = 0xfeed0000;
			registers[NV4097_SET_CONTEXT_DMA_VERTEX_B] = 0xfeed0001;
			registers[NV4097_SET_CONTEXT_DMA_SEMAPHORE] = 0x66606660;
			registers[NV4097_SET_CONTEXT_DMA_REPORT] = 0x66626660;
			registers[NV4097_SET_CONTEXT_DMA_CLIP_ID] = 0x0;
			registers[NV4097_SET_CONTEXT_DMA_CULL_DATA] = 0x0;
			registers[NV4097_SET_CONTEXT_DMA_COLOR_C] = 0xfeed0000;
			registers[NV4097_SET_CONTEXT_DMA_COLOR_D] = 0xfeed0000;
			registers[NV406E_SET_CONTEXT_DMA_SEMAPHORE] = 0x66616661;
			registers[NV4097_SET_SURFACE_CLIP_HORIZONTAL] = 0x0;
			registers[NV4097_SET_SURFACE_CLIP_VERTICAL] = 0x0;
			registers[NV4097_SET_SURFACE_FORMAT] = 0x121;
			registers[NV4097_SET_SURFACE_PITCH_A] = 0x40;
			registers[NV4097_SET_SURFACE_COLOR_AOFFSET] = 0x0;
			registers[NV4097_SET_SURFACE_ZETA_OFFSET] = 0x0;
			registers[NV4097_SET_SURFACE_COLOR_BOFFSET] = 0x0;
			registers[NV4097_SET_SURFACE_PITCH_B] = 0x40;
			registers[NV4097_SET_SURFACE_COLOR_TARGET] = 0x1;
			registers[0x224 / 4] = 0x80;
			registers[0x228 / 4] = 0x100;
			registers[NV4097_SET_SURFACE_PITCH_Z] = 0x40;
			registers[0x230 / 4] = 0x0;
			registers[NV4097_SET_SURFACE_PITCH_C] = 0x40;
			registers[NV4097_SET_SURFACE_PITCH_D] = 0x40;
			registers[NV4097_SET_SURFACE_COLOR_COFFSET] = 0x0;
			registers[NV4097_SET_SURFACE_COLOR_DOFFSET] = 0x0;
			registers[0x1d80 / 4] = 0x3;
			registers[NV4097_SET_WINDOW_OFFSET] = 0x0;
			registers[0x2bc / 4] = 0x0;
			registers[0x2c0 / 4] = 0xfff0000;
			registers[0x2c4 / 4] = 0xfff0000;
			registers[0x2c8 / 4] = 0xfff0000;
			registers[0x2cc / 4] = 0xfff0000;
			registers[0x2d0 / 4] = 0xfff0000;
			registers[0x2d4 / 4] = 0xfff0000;
			registers[0x2d8 / 4] = 0xfff0000;
			registers[0x2dc / 4] = 0xfff0000;
			registers[0x2e0 / 4] = 0xfff0000;
			registers[0x2e4 / 4] = 0xfff0000;
			registers[0x2e8 / 4] = 0xfff0000;
			registers[0x2ec / 4] = 0xfff0000;
			registers[0x2f0 / 4] = 0xfff0000;
			registers[0x2f4 / 4] = 0xfff0000;
			registers[0x2f8 / 4] = 0xfff0000;
			registers[0x2fc / 4] = 0xfff0000;
			registers[0x1d98 / 4] = 0xfff0000;
			registers[0x1d9c / 4] = 0xfff0000;
			registers[0x1da4 / 4] = 0x0;
			registers[NV4097_SET_CONTROL0] = 0x100000;
			registers[0x1454 / 4] = 0x0;
			registers[NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK] = 0x3fffff;
			registers[NV4097_SET_FREQUENCY_DIVIDER_OPERATION] = 0x0;
			registers[NV4097_SET_ATTRIB_COLOR] = 0x6144321;
			registers[NV4097_SET_ATTRIB_TEX_COORD] = 0xedcba987;
			registers[NV4097_SET_ATTRIB_TEX_COORD_EX] = 0x6f;
			registers[NV4097_SET_ATTRIB_UCLIP0] = 0x171615;
			registers[NV4097_SET_ATTRIB_UCLIP1] = 0x1b1a19;
			registers[NV4097_SET_TEX_COORD_CONTROL] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 1] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 2] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 3] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 4] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 5] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 6] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 7] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 8] = 0x0;
			registers[NV4097_SET_TEX_COORD_CONTROL + 9] = 0x0;
			registers[0xa0c / 4] = 0x0;
			registers[0xa60 / 4] = 0x0;
			registers[NV4097_SET_POLY_OFFSET_LINE_ENABLE] = 0x0;
			registers[NV4097_SET_POLY_OFFSET_FILL_ENABLE] = 0x0;
			registers[NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR] = 0x0;
			registers[NV4097_SET_POLYGON_OFFSET_BIAS] = 0x0;
			registers[0x1428 / 4] = 0x1;
			registers[NV4097_SET_SHADER_WINDOW] = 0x1000;
			registers[0x1e94 / 4] = 0x11;
			registers[0x1450 / 4] = 0x80003;
			registers[0x1d64 / 4] = 0x2000000;
			registers[0x145c / 4] = 0x1;
			registers[NV4097_SET_REDUCE_DST_COLOR] = 0x1;
			registers[NV4097_SET_TEXTURE_CONTROL2] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 1] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 2] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 3] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 4] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 5] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 6] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 7] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 8] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 9] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 10] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 11] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 12] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 13] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 14] = 0x2dc8;
			registers[NV4097_SET_TEXTURE_CONTROL2 + 15] = 0x2dc8;
			registers[NV4097_SET_FOG_MODE] = 0x800;
			registers[NV4097_SET_FOG_PARAMS] = 0x0;
			registers[NV4097_SET_FOG_PARAMS + 1] = 0x0;
			registers[NV4097_SET_FOG_PARAMS + 2] = 0x0;
			registers[0x240 / 4] = 0xffff;
			registers[0x244 / 4] = 0x0;
			registers[0x248 / 4] = 0x0;
			registers[0x24c / 4] = 0x0;
			registers[NV4097_SET_ANISO_SPREAD] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 1] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 2] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 3] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 4] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 5] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 6] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 7] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 8] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 9] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 10] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 11] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 12] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 13] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 14] = 0x10101;
			registers[NV4097_SET_ANISO_SPREAD + 15] = 0x10101;
			registers[0x400 / 4] = 0x7421;
			registers[0x404 / 4] = 0x7421;
			registers[0x408 / 4] = 0x7421;
			registers[0x40c / 4] = 0x7421;
			registers[0x410 / 4] = 0x7421;
			registers[0x414 / 4] = 0x7421;
			registers[0x418 / 4] = 0x7421;
			registers[0x41c / 4] = 0x7421;
			registers[0x420 / 4] = 0x7421;
			registers[0x424 / 4] = 0x7421;
			registers[0x428 / 4] = 0x7421;
			registers[0x42c / 4] = 0x7421;
			registers[0x430 / 4] = 0x7421;
			registers[0x434 / 4] = 0x7421;
			registers[0x438 / 4] = 0x7421;
			registers[0x43c / 4] = 0x7421;
			registers[0x440 / 4] = 0x9aabaa98;
			registers[0x444 / 4] = 0x66666789;
			registers[0x448 / 4] = 0x98766666;
			registers[0x44c / 4] = 0x89aabaa9;
			registers[0x450 / 4] = 0x99999999;
			registers[0x454 / 4] = 0x88888889;
			registers[0x458 / 4] = 0x98888888;
			registers[0x45c / 4] = 0x99999999;
			registers[0x460 / 4] = 0x56676654;
			registers[0x464 / 4] = 0x33333345;
			registers[0x468 / 4] = 0x54333333;
			registers[0x46c / 4] = 0x45667665;
			registers[0x470 / 4] = 0xaabbba99;
			registers[0x474 / 4] = 0x66667899;
			registers[0x478 / 4] = 0x99876666;
			registers[0x47c / 4] = 0x99abbbaa;
			registers[NV4097_SET_VERTEX_DATA_BASE_OFFSET] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_BASE_INDEX] = 0x0;
			registers[0xe000 / 4] = 0xcafebabe;
			registers[NV4097_SET_ALPHA_FUNC] = 0x207;
			registers[NV4097_SET_ALPHA_REF] = 0x0;
			registers[NV4097_SET_ALPHA_TEST_ENABLE] = 0x0;
			registers[NV4097_SET_BACK_STENCIL_FUNC] = 0x207;
			registers[NV4097_SET_BACK_STENCIL_FUNC_REF] = 0x0;
			registers[NV4097_SET_BACK_STENCIL_FUNC_MASK] = 0xff;
			registers[NV4097_SET_BACK_STENCIL_MASK] = 0xff;
			registers[NV4097_SET_BACK_STENCIL_OP_FAIL] = 0x1e00;
			registers[NV4097_SET_BACK_STENCIL_OP_ZFAIL] = 0x1e00;
			registers[NV4097_SET_BACK_STENCIL_OP_ZPASS] = 0x1e00;
			registers[NV4097_SET_BLEND_COLOR] = 0x0;
			registers[NV4097_SET_BLEND_COLOR2] = 0x0;
			registers[NV4097_SET_BLEND_ENABLE] = 0x0;
			registers[NV4097_SET_BLEND_ENABLE_MRT] = 0x0;
			registers[NV4097_SET_BLEND_EQUATION] = 0x80068006;
			registers[NV4097_SET_BLEND_FUNC_SFACTOR] = 0x10001;
			registers[NV4097_SET_BLEND_FUNC_DFACTOR] = 0x0;
			registers[NV4097_SET_ZSTENCIL_CLEAR_VALUE] = 0xffffff00;
			registers[NV4097_CLEAR_SURFACE] = 0x0;
			registers[NV4097_NO_OPERATION] = 0x0;
			registers[NV4097_SET_COLOR_MASK] = 0x1010101;
			registers[NV4097_SET_CULL_FACE_ENABLE] = 0x0;
			registers[NV4097_SET_CULL_FACE] = 0x405;
			registers[NV4097_SET_DEPTH_BOUNDS_MIN] = 0x0;
			registers[NV4097_SET_DEPTH_BOUNDS_MAX] = 0x3f800000;
			registers[NV4097_SET_DEPTH_BOUNDS_TEST_ENABLE] = 0x0;
			registers[NV4097_SET_DEPTH_FUNC] = 0x201;
			registers[NV4097_SET_DEPTH_MASK] = 0x1;
			registers[NV4097_SET_DEPTH_TEST_ENABLE] = 0x0;
			registers[NV4097_SET_DITHER_ENABLE] = 0x1;
			registers[NV4097_SET_SHADER_PACKER] = 0x0;
			registers[NV4097_SET_FREQUENCY_DIVIDER_OPERATION] = 0x0;
			registers[NV4097_SET_FRONT_FACE] = 0x901;
			registers[NV4097_SET_LINE_WIDTH] = 0x8;
			registers[NV4097_SET_LOGIC_OP_ENABLE] = 0x0;
			registers[NV4097_SET_LOGIC_OP] = 0x1503;
			registers[NV4097_SET_POINT_SIZE] = 0x3f800000;
			registers[NV4097_SET_POLY_OFFSET_FILL_ENABLE] = 0x0;
			registers[NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR] = 0x0;
			registers[NV4097_SET_POLYGON_OFFSET_BIAS] = 0x0;
			registers[NV4097_SET_RESTART_INDEX_ENABLE] = 0x0;
			registers[NV4097_SET_RESTART_INDEX] = 0xffffffff;
			registers[NV4097_SET_SCISSOR_HORIZONTAL] = 0x10000000;
			registers[NV4097_SET_SCISSOR_VERTICAL] = 0x10000000;
			registers[NV4097_SET_SHADE_MODE] = 0x1d01;
			registers[NV4097_SET_STENCIL_FUNC] = 0x207;
			registers[NV4097_SET_STENCIL_FUNC_REF] = 0x0;
			registers[NV4097_SET_STENCIL_FUNC_MASK] = 0xff;
			registers[NV4097_SET_STENCIL_MASK] = 0xff;
			registers[NV4097_SET_STENCIL_OP_FAIL] = 0x1e00;
			registers[NV4097_SET_STENCIL_OP_ZFAIL] = 0x1e00;
			registers[NV4097_SET_STENCIL_OP_ZPASS] = 0x1e00;
			registers[NV4097_SET_STENCIL_TEST_ENABLE] = 0x0;
			registers[NV4097_SET_TEXTURE_ADDRESS] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 8] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 8] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 8] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 8] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 16] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 16] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 16] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 16] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 24] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 24] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 24] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 24] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 32] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 32] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 32] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 32] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 40] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 40] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 40] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 40] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 48] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 48] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 48] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 48] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 56] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 56] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 56] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 56] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 64] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 64] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 64] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 64] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 72] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 72] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 72] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 72] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 80] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 80] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 80] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 80] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 88] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 88] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 88] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 88] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 96] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 96] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 96] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 96] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 104] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 104] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 104] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 104] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 112] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 112] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 112] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 112] = 0x2052000;
			registers[NV4097_SET_TEXTURE_ADDRESS + 120] = 0x30101;
			registers[NV4097_SET_TEXTURE_BORDER_COLOR + 120] = 0x0;
			registers[NV4097_SET_TEXTURE_CONTROL0 + 120] = 0x60000;
			registers[NV4097_SET_TEXTURE_FILTER + 120] = 0x2052000;
			registers[NV4097_SET_TWO_SIDED_STENCIL_TEST_ENABLE] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 1] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 1] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 2] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 2] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 3] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 3] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 4] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 4] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 5] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 5] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 6] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 6] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 7] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 7] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 8] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 8] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 9] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 9] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 10] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 10] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 11] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 11] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 12] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 12] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 13] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 13] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 14] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 14] = 0x0;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 15] = 0x2;
			registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 15] = 0x0;
			registers[NV4097_SET_VIEWPORT_HORIZONTAL] = 0x10000000;
			registers[NV4097_SET_VIEWPORT_VERTICAL] = 0x10000000;
			registers[NV4097_SET_CLIP_MIN] = 0x0;
			registers[NV4097_SET_CLIP_MAX] = 0x3f800000;
			registers[NV4097_SET_VIEWPORT_OFFSET + 0] = 0x45000000;
			registers[NV4097_SET_VIEWPORT_OFFSET + 1] = 0x45000000;
			registers[NV4097_SET_VIEWPORT_OFFSET + 2] = 0x3f000000;
			registers[NV4097_SET_VIEWPORT_OFFSET + 3] = 0x0;
			registers[NV4097_SET_VIEWPORT_SCALE + 0] = 0x45000000;
			registers[NV4097_SET_VIEWPORT_SCALE + 1] = 0x45000000;
			registers[NV4097_SET_VIEWPORT_SCALE + 2] = 0x3f000000;
			registers[NV4097_SET_VIEWPORT_SCALE + 3] = 0x0;
			// NOTE: Realhw emits this sequence twice, likely to work around a hardware bug. Similar behavior can be seen in other buggy register blocks
			//registers[NV4097_SET_VIEWPORT_OFFSET + 0] = 0x45000000;
			//registers[NV4097_SET_VIEWPORT_OFFSET + 1] = 0x45000000;
			//registers[NV4097_SET_VIEWPORT_OFFSET + 2] = 0x3f000000;
			//registers[NV4097_SET_VIEWPORT_OFFSET + 3] = 0x0;
			//registers[NV4097_SET_VIEWPORT_SCALE + 0] = 0x45000000;
			//registers[NV4097_SET_VIEWPORT_SCALE + 1] = 0x45000000;
			//registers[NV4097_SET_VIEWPORT_SCALE + 2] = 0x3f000000;
			//registers[NV4097_SET_VIEWPORT_SCALE + 3] = 0x0;
			registers[NV4097_SET_ANTI_ALIASING_CONTROL] = 0xffff0000;
			registers[NV4097_SET_BACK_POLYGON_MODE] = 0x1b02;
			registers[NV4097_SET_COLOR_CLEAR_VALUE] = 0x0;
			registers[NV4097_SET_COLOR_MASK_MRT] = 0x0;
			registers[NV4097_SET_FRONT_POLYGON_MODE] = 0x1b02;
			registers[NV4097_SET_LINE_SMOOTH_ENABLE] = 0x0;
			registers[NV4097_SET_LINE_STIPPLE] = 0x0;
			registers[NV4097_SET_POINT_PARAMS_ENABLE] = 0x0;
			registers[NV4097_SET_POINT_SPRITE_CONTROL] = 0x0;
			registers[NV4097_SET_POLY_SMOOTH_ENABLE] = 0x0;
			registers[NV4097_SET_POLYGON_STIPPLE] = 0x0;
			registers[NV4097_SET_RENDER_ENABLE] = 0x1000000;
			registers[NV4097_SET_USER_CLIP_PLANE_CONTROL] = 0x0;
			registers[NV4097_SET_VERTEX_ATTRIB_INPUT_MASK] = 0xffff;
			registers[NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS] = 0x101;
			registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0] = 0x60000;
			registers[NV4097_SET_VERTEX_TEXTURE_FILTER] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS + 8] = 0x101;
			registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + 8] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0 + 8] = 0x60000;
			registers[NV4097_SET_VERTEX_TEXTURE_FILTER + 8] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS + 16] = 0x101;
			registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + 16] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0 + 16] = 0x60000;
			registers[NV4097_SET_VERTEX_TEXTURE_FILTER + 16] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS + 24] = 0x101;
			registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + 24] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0 + 24] = 0x60000;
			registers[NV4097_SET_VERTEX_TEXTURE_FILTER + 24] = 0x0;
			registers[NV4097_SET_CYLINDRICAL_WRAP] = 0x0;
			registers[NV4097_SET_ZMIN_MAX_CONTROL] = 0x1;
			registers[NV4097_SET_TWO_SIDE_LIGHT_EN] = 0x0;
			registers[NV4097_SET_TRANSFORM_BRANCH_BITS] = 0x0;
			registers[NV4097_SET_NO_PARANOID_TEXTURE_FETCHES] = 0x0;
			registers[0x2000 / 4] = 0x31337303;
			registers[0x2180 / 4] = 0x66604200;
			registers[0x2184 / 4] = 0xfeed0001;
			registers[0x2188 / 4] = 0xfeed0000;
			registers[NV3062_SET_OBJECT] = 0x313371c3;
			registers[NV3062_SET_CONTEXT_DMA_NOTIFIES] = 0x66604200;
			registers[NV3062_SET_CONTEXT_DMA_IMAGE_SOURCE] = 0xfeed0000;
			registers[NV3062_SET_CONTEXT_DMA_IMAGE_DESTIN] = 0xfeed0000;
			registers[0xa000 / 4] = 0x31337808;
			registers[0xa180 / 4] = 0x66604200;
			registers[0xa184 / 4] = 0x0;
			registers[0xa188 / 4] = 0x0;
			registers[0xa18c / 4] = 0x0;
			registers[0xa190 / 4] = 0x0;
			registers[0xa194 / 4] = 0x0;
			registers[0xa198 / 4] = 0x0;
			registers[0xa19c / 4] = 0x313371c3;
			registers[0xa2fc / 4] = 0x3;
			registers[0xa300 / 4] = 0x4;
			registers[0x8000 / 4] = 0x31337a73;
			registers[0x8180 / 4] = 0x66604200;
			registers[0x8184 / 4] = 0xfeed0000;
			registers[0xc000 / 4] = 0x3137af00;
			registers[0xc180 / 4] = 0x66604200;
			registers[NV4097_SET_ZCULL_EN] = 0x3;
			registers[NV4097_SET_ZCULL_STATS_ENABLE] = 0x0;
			registers[NV4097_SET_ZCULL_CONTROL0] = 0x10;
			registers[NV4097_SET_ZCULL_CONTROL1] = 0x1000100;
			registers[NV4097_SET_SCULL_CONTROL] = 0xff000002;
			registers[NV4097_SET_TEXTURE_OFFSET] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 8] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 8] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 8] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 1] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 8] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 16] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 16] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 16] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 2] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 16] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 24] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 24] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 24] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 3] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 24] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 32] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 32] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 32] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 4] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 32] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 40] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 40] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 40] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 5] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 40] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 48] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 48] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 48] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 6] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 48] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 56] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 56] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 56] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 7] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 56] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 64] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 64] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 64] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 8] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 64] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 72] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 72] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 72] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 9] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 72] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 80] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 80] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 80] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 10] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 80] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 88] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 88] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 88] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 11] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 88] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 96] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 96] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 96] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 12] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 96] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 104] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 104] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 104] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 13] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 104] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 112] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 112] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 112] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 14] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 112] = 0xaae4;
			registers[NV4097_SET_TEXTURE_OFFSET + 120] = 0x0;
			registers[NV4097_SET_TEXTURE_FORMAT + 120] = 0x18429;
			registers[NV4097_SET_TEXTURE_IMAGE_RECT + 120] = 0x80008;
			registers[NV4097_SET_TEXTURE_CONTROL3 + 15] = 0x100008;
			registers[NV4097_SET_TEXTURE_CONTROL1 + 120] = 0xaae4;
			registers[NV4097_SET_VERTEX_TEXTURE_OFFSET] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_FORMAT] = 0x1bc21;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL3] = 0x8;
			registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT] = 0x80008;
			registers[NV4097_SET_VERTEX_TEXTURE_OFFSET + 8] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_FORMAT + 8] = 0x1bc21;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL3 + 8] = 0x8;
			registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT + 8] = 0x80008;
			registers[NV4097_SET_VERTEX_TEXTURE_OFFSET + 16] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_FORMAT + 16] = 0x1bc21;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL3 + 16] = 0x8;
			registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT + 16] = 0x80008;
			registers[NV4097_SET_VERTEX_TEXTURE_OFFSET + 24] = 0x0;
			registers[NV4097_SET_VERTEX_TEXTURE_FORMAT + 24] = 0x1bc21;
			registers[NV4097_SET_VERTEX_TEXTURE_CONTROL3 + 24] = 0x8;
			registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT + 24] = 0x80008;
			registers[0x230c / 4] = 0x0;
			registers[0x2310 / 4] = 0x0;
			registers[0x2314 / 4] = 0x0;
			registers[0x2318 / 4] = 0x0;
			registers[0x231c / 4] = 0x0;
			registers[0x2320 / 4] = 0x0;
			registers[0x2324 / 4] = 0x101;
			registers[NV3062_SET_COLOR_FORMAT] = 0xa;
			registers[NV3062_SET_PITCH] = 0x400040;
			registers[NV3062_SET_OFFSET_SOURCE] = 0x0;
			registers[NV3062_SET_OFFSET_DESTIN] = 0x0;
			registers[0x8300 / 4] = 0x1000a;
			registers[0x8304 / 4] = 0x0;
			registers[0xc184 / 4] = 0xfeed0000;
			registers[0xc198 / 4] = 0x313371c3;
			registers[0xc2fc / 4] = 0x1;
			registers[0xc300 / 4] = 0x3;
			registers[0xc304 / 4] = 0x3;
			registers[0xc308 / 4] = 0x0;
			registers[0xc30c / 4] = 0x0;
			registers[0xc310 / 4] = 0x0;
			registers[0xc314 / 4] = 0x0;
			registers[0xc318 / 4] = 0x0;
			registers[0xc31c / 4] = 0x0;
			registers[0xc400 / 4] = 0x10002;
			registers[0xc404 / 4] = 0x10000;
			registers[0xc408 / 4] = 0x0;
			registers[0xc40c / 4] = 0x0;
			registers[NV308A_POINT] = 0x0;
			registers[NV308A_SIZE_OUT] = 0x0;
			registers[NV308A_SIZE_IN] = 0x0;
			registers[NV406E_SET_REFERENCE] = umax;
			if (auto rsx = Emu.IsStopped() ? nullptr : get_current_renderer(); rsx && rsx->ctrl) rsx->ctrl->ref = u32{umax};
		}

		{
			// Signal definitions
			state_signals[NV4097_SET_SHADER_CONTROL] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 0] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 1] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 2] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 3] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 4] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 5] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 6] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 7] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 8] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TEX_COORD_CONTROL + 9] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_TWO_SIDE_LIGHT_EN] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_POINT_SPRITE_CONTROL] = rsx::fragment_program_state_dirty;
			state_signals[NV4097_SET_USER_CLIP_PLANE_CONTROL] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_TRANSFORM_BRANCH_BITS] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_CLIP_MIN] = rsx::invalidate_zclip_bits;
			state_signals[NV4097_SET_CLIP_MAX] = rsx::invalidate_zclip_bits;
			state_signals[NV4097_SET_POINT_SIZE] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_ALPHA_FUNC] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_ALPHA_REF] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_ALPHA_TEST_ENABLE] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_ANTI_ALIASING_CONTROL] = rsx::fragment_state_dirty | rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_SHADER_PACKER] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_SHADER_WINDOW] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_FOG_MODE] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_SCISSOR_HORIZONTAL] = rsx::scissor_config_state_dirty;
			state_signals[NV4097_SET_SCISSOR_VERTICAL] = rsx::scissor_config_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_HORIZONTAL] = rsx::scissor_config_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_VERTICAL] = rsx::scissor_config_state_dirty;
			state_signals[NV4097_SET_FOG_PARAMS + 0] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_FOG_PARAMS + 1] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_SCALE + 0] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_SCALE + 1] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_SCALE + 2] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_OFFSET + 0] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_OFFSET + 1] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_VIEWPORT_OFFSET + 2] = rsx::vertex_state_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE] = rsx::fragment_state_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 0] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 1] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 2] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 3] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 4] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 5] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 6] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 7] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 8] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 9] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 10] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 11] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 12] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 13] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 14] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 15] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 16] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 17] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 18] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 19] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 20] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 21] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 22] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 23] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 24] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 25] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 26] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 27] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 28] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 29] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 30] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 31] = rsx::polygon_stipple_pattern_dirty;
			state_signals[NV4097_SET_POLY_OFFSET_FILL_ENABLE] = rsx::polygon_offset_state_dirty;
			state_signals[NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR] = rsx::polygon_offset_state_dirty;
			state_signals[NV4097_SET_POLYGON_OFFSET_BIAS] = rsx::polygon_offset_state_dirty;
			state_signals[NV4097_SET_DEPTH_BOUNDS_TEST_ENABLE] = rsx::depth_bounds_state_dirty;
			state_signals[NV4097_SET_DEPTH_BOUNDS_MIN] = rsx::depth_bounds_state_dirty;
			state_signals[NV4097_SET_DEPTH_BOUNDS_MAX] = rsx::depth_bounds_state_dirty;
			state_signals[NV4097_SET_CULL_FACE_ENABLE] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_ZMIN_MAX_CONTROL] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_LOGIC_OP_ENABLE] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_LOGIC_OP] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_BLEND_ENABLE] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_BLEND_ENABLE_MRT] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_STENCIL_FUNC] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_BACK_STENCIL_FUNC] = rsx::pipeline_config_dirty;
			state_signals[NV4097_SET_RESTART_INDEX_ENABLE] = rsx::pipeline_config_dirty;
		}

		// Sanity checks
		for (size_t id = 0; id < methods.size(); ++id)
		{
			if (methods[id] && state_signals[id])
			{
				rsx_log.error("FIXME: Method register 0x%x is registered as a method and signal. The signal will be ignored.");
			}
		}
	}

	void rsx_state::reset()
	{
		// TODO: Name unnamed registers and constants, better group methods
		registers[NV406E_SET_CONTEXT_DMA_SEMAPHORE] = 0x56616661;

		registers[NV4097_SET_OBJECT] = 0x31337000;
		registers[NV4097_SET_CONTEXT_DMA_NOTIFIES] = 0x66604200;
		registers[NV4097_SET_CONTEXT_DMA_A] = 0xfeed0000;
		registers[NV4097_SET_CONTEXT_DMA_B] = 0xfeed0001;
		registers[NV4097_SET_CONTEXT_DMA_COLOR_B] = 0xfeed0000;
		registers[NV4097_SET_CONTEXT_DMA_STATE] = 0x0;
		registers[NV4097_SET_CONTEXT_DMA_COLOR_A] = 0xfeed0000;
		registers[NV4097_SET_CONTEXT_DMA_ZETA] = 0xfeed0000;
		registers[NV4097_SET_CONTEXT_DMA_VERTEX_A] = 0xfeed0000;
		registers[NV4097_SET_CONTEXT_DMA_VERTEX_B] = 0xfeed0001;
		registers[NV4097_SET_CONTEXT_DMA_SEMAPHORE] = 0x66606660;
		registers[NV4097_SET_CONTEXT_DMA_REPORT] = 0x66626660;
		registers[NV4097_SET_CONTEXT_DMA_CLIP_ID] = 0x0;
		registers[NV4097_SET_CONTEXT_DMA_CULL_DATA] = 0x0;
		registers[NV4097_SET_CONTEXT_DMA_COLOR_C] = 0xfeed0000;
		registers[NV4097_SET_CONTEXT_DMA_COLOR_D] = 0xfeed0000;
		registers[NV406E_SET_CONTEXT_DMA_SEMAPHORE] = 0x66616661;
		registers[NV4097_SET_SURFACE_CLIP_HORIZONTAL] = 0x0;
		registers[NV4097_SET_SURFACE_CLIP_VERTICAL] = 0x0;
		registers[NV4097_SET_SURFACE_FORMAT] = 0x121;
		registers[NV4097_SET_SURFACE_PITCH_A] = 0x40;
		registers[NV4097_SET_SURFACE_COLOR_AOFFSET] = 0x0;
		registers[NV4097_SET_SURFACE_ZETA_OFFSET] = 0x0;
		registers[NV4097_SET_SURFACE_COLOR_BOFFSET] = 0x0;
		registers[NV4097_SET_SURFACE_PITCH_B] = 0x40;
		registers[NV4097_SET_SURFACE_COLOR_TARGET] = 0x1;
		registers[0x224 / 4] = 0x80;
		registers[0x228 / 4] = 0x100;
		registers[NV4097_SET_SURFACE_PITCH_Z] = 0x40;
		registers[0x230 / 4] = 0x0;
		registers[NV4097_SET_SURFACE_PITCH_C] = 0x40;
		registers[NV4097_SET_SURFACE_PITCH_D] = 0x40;
		registers[NV4097_SET_SURFACE_COLOR_COFFSET] = 0x0;
		registers[NV4097_SET_SURFACE_COLOR_DOFFSET] = 0x0;
		registers[0x1d80 / 4] = 0x3;
		registers[NV4097_SET_WINDOW_OFFSET] = 0x0;
		registers[0x02bc / 4] = 0x0;
		registers[0x02c0 / 4] = 0xfff0000;
		registers[0x02c4 / 4] = 0xfff0000;
		registers[0x02c8 / 4] = 0xfff0000;
		registers[0x02cc / 4] = 0xfff0000;
		registers[0x02d0 / 4] = 0xfff0000;
		registers[0x02d4 / 4] = 0xfff0000;
		registers[0x02d8 / 4] = 0xfff0000;
		registers[0x02dc / 4] = 0xfff0000;
		registers[0x02e0 / 4] = 0xfff0000;
		registers[0x02e4 / 4] = 0xfff0000;
		registers[0x02e8 / 4] = 0xfff0000;
		registers[0x02ec / 4] = 0xfff0000;
		registers[0x02f0 / 4] = 0xfff0000;
		registers[0x02f4 / 4] = 0xfff0000;
		registers[0x02f8 / 4] = 0xfff0000;
		registers[0x02fc / 4] = 0xfff0000;
		registers[0x1d98 / 4] = 0xfff0000;
		registers[0x1d9c / 4] = 0xfff0000;
		registers[0x1da4 / 4] = 0x0;
		registers[NV4097_SET_CONTROL0] = 0x100000;
		registers[0x1454 / 4] = 0x0;
		registers[NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK] = 0x3fffff;
		registers[NV4097_SET_FREQUENCY_DIVIDER_OPERATION] = 0x0;
		registers[NV4097_SET_ATTRIB_COLOR] = 0x6144321;
		registers[NV4097_SET_ATTRIB_TEX_COORD] = 0xedcba987;
		registers[NV4097_SET_ATTRIB_TEX_COORD_EX] = 0x6f;
		registers[NV4097_SET_ATTRIB_UCLIP0] = 0x171615;
		registers[NV4097_SET_ATTRIB_UCLIP1] = 0x1b1a19;
		registers[NV4097_SET_TEX_COORD_CONTROL] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 1] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 2] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 3] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 4] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 5] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 6] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 7] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 8] = 0x0;
		registers[NV4097_SET_TEX_COORD_CONTROL + 9] = 0x0;
		registers[0xa0c / 4] = 0x0;
		registers[0xa60 / 4] = 0x0;
		registers[NV4097_SET_POLY_OFFSET_LINE_ENABLE] = 0x0;
		registers[NV4097_SET_POLY_OFFSET_FILL_ENABLE] = 0x0;
		registers[NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR] = 0x0;
		registers[NV4097_SET_POLYGON_OFFSET_BIAS] = 0x0;
		registers[0x1428 / 4] = 0x1;
		registers[NV4097_SET_SHADER_WINDOW] = 0x1000;
		registers[0x1e94 / 4] = 0x11;
		registers[0x1450 / 4] = 0x80003;
		registers[0x1d64 / 4] = 0x2000000;
		registers[0x145c / 4] = 0x1;
		registers[NV4097_SET_REDUCE_DST_COLOR] = 0x1;
		registers[NV4097_SET_TEXTURE_CONTROL2] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 1] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 2] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 3] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 4] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 5] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 6] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 7] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 8] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 9] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 10] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 11] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 12] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 13] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 14] = 0x2dc8;
		registers[NV4097_SET_TEXTURE_CONTROL2 + 15] = 0x2dc8;
		registers[NV4097_SET_FOG_MODE] = 0x800;
		registers[NV4097_SET_FOG_PARAMS] = 0x0;
		registers[NV4097_SET_FOG_PARAMS + 1] = 0x0;
		registers[NV4097_SET_FOG_PARAMS + 2] = 0x0;
		registers[0x240 / 4] = 0xffff;
		registers[0x244 / 4] = 0x0;
		registers[0x248 / 4] = 0x0;
		registers[0x24c / 4] = 0x0;
		registers[NV4097_SET_ANISO_SPREAD] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 1] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 2] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 3] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 4] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 5] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 6] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 7] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 8] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 9] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 10] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 11] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 12] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 13] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 14] = 0x10101;
		registers[NV4097_SET_ANISO_SPREAD + 15] = 0x10101;
		registers[0x400 / 4] = 0x7421;
		registers[0x404 / 4] = 0x7421;
		registers[0x408 / 4] = 0x7421;
		registers[0x40c / 4] = 0x7421;
		registers[0x410 / 4] = 0x7421;
		registers[0x414 / 4] = 0x7421;
		registers[0x418 / 4] = 0x7421;
		registers[0x41c / 4] = 0x7421;
		registers[0x420 / 4] = 0x7421;
		registers[0x424 / 4] = 0x7421;
		registers[0x428 / 4] = 0x7421;
		registers[0x42c / 4] = 0x7421;
		registers[0x430 / 4] = 0x7421;
		registers[0x434 / 4] = 0x7421;
		registers[0x438 / 4] = 0x7421;
		registers[0x43c / 4] = 0x7421;
		registers[0x440 / 4] = 0x9aabaa98;
		registers[0x444 / 4] = 0x66666789;
		registers[0x448 / 4] = 0x98766666;
		registers[0x44c / 4] = 0x89aabaa9;
		registers[0x450 / 4] = 0x99999999;
		registers[0x454 / 4] = 0x88888889;
		registers[0x458 / 4] = 0x98888888;
		registers[0x45c / 4] = 0x99999999;
		registers[0x460 / 4] = 0x56676654;
		registers[0x464 / 4] = 0x33333345;
		registers[0x468 / 4] = 0x54333333;
		registers[0x46c / 4] = 0x45667665;
		registers[0x470 / 4] = 0xaabbba99;
		registers[0x474 / 4] = 0x66667899;
		registers[0x478 / 4] = 0x99876666;
		registers[0x47c / 4] = 0x99abbbaa;
		registers[NV4097_SET_VERTEX_DATA_BASE_OFFSET] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_BASE_INDEX] = 0x0;
		registers[GCM_SET_DRIVER_OBJECT] = 0xcafebabe;
		registers[NV4097_SET_ALPHA_FUNC] = 0x207;
		registers[NV4097_SET_ALPHA_REF] = 0x0;
		registers[NV4097_SET_ALPHA_TEST_ENABLE] = 0x0;
		registers[NV4097_SET_BACK_STENCIL_FUNC] = 0x207;
		registers[NV4097_SET_BACK_STENCIL_FUNC_REF] = 0x0;
		registers[NV4097_SET_BACK_STENCIL_FUNC_MASK] = 0xff;
		registers[NV4097_SET_BACK_STENCIL_MASK] = 0xff;
		registers[NV4097_SET_BACK_STENCIL_OP_FAIL] = 0x1e00;
		registers[NV4097_SET_BACK_STENCIL_OP_ZFAIL] = 0x1e00;
		registers[NV4097_SET_BACK_STENCIL_OP_ZPASS] = 0x1e00;
		registers[NV4097_SET_BLEND_COLOR] = 0x0;
		registers[NV4097_SET_BLEND_COLOR2] = 0x0;
		registers[NV4097_SET_BLEND_ENABLE] = 0x0;
		registers[NV4097_SET_BLEND_ENABLE_MRT] = 0x0;
		registers[NV4097_SET_BLEND_EQUATION] = 0x80068006;
		registers[NV4097_SET_BLEND_FUNC_SFACTOR] = 0x10001;
		registers[NV4097_SET_BLEND_FUNC_DFACTOR] = 0x0;
		registers[NV4097_SET_ZSTENCIL_CLEAR_VALUE] = 0xffffff00;
		registers[NV4097_CLEAR_SURFACE] = 0x0;
		registers[NV4097_NO_OPERATION] = 0x0;
		registers[NV4097_SET_COLOR_MASK] = 0x1010101;
		registers[NV4097_SET_CULL_FACE_ENABLE] = 0x0;
		registers[NV4097_SET_CULL_FACE] = 0x405;
		registers[NV4097_SET_DEPTH_BOUNDS_MIN] = 0x0;
		registers[NV4097_SET_DEPTH_BOUNDS_MAX] = 0x3f800000;
		registers[NV4097_SET_DEPTH_BOUNDS_TEST_ENABLE] = 0x0;
		registers[NV4097_SET_DEPTH_FUNC] = 0x201;
		registers[NV4097_SET_DEPTH_MASK] = 0x1;
		registers[NV4097_SET_DEPTH_TEST_ENABLE] = 0x0;
		registers[NV4097_SET_DITHER_ENABLE] = 0x1;
		registers[NV4097_SET_SHADER_PACKER] = 0x0;
		registers[NV4097_SET_FREQUENCY_DIVIDER_OPERATION] = 0x0;
		registers[NV4097_SET_FRONT_FACE] = 0x901;
		registers[NV4097_SET_LINE_WIDTH] = 0x8;
		registers[NV4097_SET_LOGIC_OP_ENABLE] = 0x0;
		registers[NV4097_SET_LOGIC_OP] = 0x1503;
		registers[NV4097_SET_POINT_SIZE] = 0x3f800000;
		registers[NV4097_SET_POLY_OFFSET_FILL_ENABLE] = 0x0;
		registers[NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR] = 0x0;
		registers[NV4097_SET_POLYGON_OFFSET_BIAS] = 0x0;
		registers[NV4097_SET_RESTART_INDEX_ENABLE] = 0x0;
		registers[NV4097_SET_RESTART_INDEX] = 0xffffffff;
		registers[NV4097_SET_SCISSOR_HORIZONTAL] = 0x10000000;
		registers[NV4097_SET_SCISSOR_VERTICAL] = 0x10000000;
		registers[NV4097_SET_SHADE_MODE] = 0x1d01;
		registers[NV4097_SET_STENCIL_FUNC] = 0x207;
		registers[NV4097_SET_STENCIL_FUNC_REF] = 0x0;
		registers[NV4097_SET_STENCIL_FUNC_MASK] = 0xff;
		registers[NV4097_SET_STENCIL_MASK] = 0xff;
		registers[NV4097_SET_STENCIL_OP_FAIL] = 0x1e00;
		registers[NV4097_SET_STENCIL_OP_ZFAIL] = 0x1e00;
		registers[NV4097_SET_STENCIL_OP_ZPASS] = 0x1e00;
		registers[NV4097_SET_STENCIL_TEST_ENABLE] = 0x0;
		registers[NV4097_SET_TEXTURE_ADDRESS] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 8] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 8] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 8] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 8] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 8] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 16] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 16] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 16] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 24] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 24] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 24] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 24] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 32] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 32] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 32] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 32] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 40] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 40] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 40] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 40] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 48] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 48] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 48] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 48] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 56] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 56] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 56] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 56] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 64] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 64] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 64] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 64] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 72] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 72] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 72] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 72] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 80] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 80] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 80] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 80] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 88] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 88] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 88] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 88] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 96] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 96] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 96] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 96] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 104] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 104] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 104] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 104] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 112] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 112] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 112] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 112] = 0x2052000;
		registers[NV4097_SET_TEXTURE_ADDRESS + 120] = 0x30101;
		registers[NV4097_SET_TEXTURE_BORDER_COLOR + 120] = 0x0;
		registers[NV4097_SET_TEXTURE_CONTROL0 + 120] = 0x60000;
		registers[NV4097_SET_TEXTURE_FILTER + 120] = 0x2052000;
		registers[NV4097_SET_TWO_SIDED_STENCIL_TEST_ENABLE] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 1] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 1] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 2] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 2] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 3] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 3] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 4] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 4] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 5] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 5] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 6] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 6] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 7] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 7] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 8] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 8] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 9] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 9] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 10] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 10] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 11] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 11] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 12] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 12] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 13] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 13] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 14] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 14] = 0x0;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT + 15] = 0x2;
		registers[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + 15] = 0x0;
		registers[NV4097_SET_VIEWPORT_HORIZONTAL] = 0x10000000;
		registers[NV4097_SET_VIEWPORT_VERTICAL] = 0x10000000;
		registers[NV4097_SET_CLIP_MIN] = 0x0;
		registers[NV4097_SET_CLIP_MAX] = 0x3f800000;
		registers[NV4097_SET_VIEWPORT_OFFSET + 0] = 0x45000000;
		registers[NV4097_SET_VIEWPORT_OFFSET + 1] = 0x45000000;
		registers[NV4097_SET_VIEWPORT_OFFSET + 2] = 0x3f000000;
		registers[NV4097_SET_VIEWPORT_OFFSET + 3] = 0x0;
		registers[NV4097_SET_VIEWPORT_SCALE + 0] = 0x45000000;
		registers[NV4097_SET_VIEWPORT_SCALE + 1] = 0x45000000;
		registers[NV4097_SET_VIEWPORT_SCALE + 2] = 0x3f000000;
		registers[NV4097_SET_VIEWPORT_SCALE + 3] = 0x0;
		// NOTE: Realhw emits this sequence twice, likely to work around a hardware bug. Similar behavior can be seen in other buggy register blocks
		//registers[NV4097_SET_VIEWPORT_OFFSET + 0] = 0x45000000;
		//registers[NV4097_SET_VIEWPORT_OFFSET + 1] = 0x45000000;
		//registers[NV4097_SET_VIEWPORT_OFFSET + 2] = 0x3f000000;
		//registers[NV4097_SET_VIEWPORT_OFFSET + 3] = 0x0;
		//registers[NV4097_SET_VIEWPORT_SCALE + 0] = 0x45000000;
		//registers[NV4097_SET_VIEWPORT_SCALE + 1] = 0x45000000;
		//registers[NV4097_SET_VIEWPORT_SCALE + 2] = 0x3f000000;
		//registers[NV4097_SET_VIEWPORT_SCALE + 3] = 0x0;
		registers[NV4097_SET_ANTI_ALIASING_CONTROL] = 0xffff0000;
		registers[NV4097_SET_BACK_POLYGON_MODE] = 0x1b02;
		registers[NV4097_SET_COLOR_CLEAR_VALUE] = 0x0;
		registers[NV4097_SET_COLOR_MASK_MRT] = 0x0;
		registers[NV4097_SET_FRONT_POLYGON_MODE] = 0x1b02;
		registers[NV4097_SET_LINE_SMOOTH_ENABLE] = 0x0;
		registers[NV4097_SET_LINE_STIPPLE] = 0x0;
		registers[NV4097_SET_POINT_PARAMS_ENABLE] = 0x0;
		registers[NV4097_SET_POINT_SPRITE_CONTROL] = 0x0;
		registers[NV4097_SET_POLY_SMOOTH_ENABLE] = 0x0;
		registers[NV4097_SET_POLYGON_STIPPLE] = 0x0;
		registers[NV4097_SET_RENDER_ENABLE] = 0x1000000;
		registers[NV4097_SET_USER_CLIP_PLANE_CONTROL] = 0x0;
		registers[NV4097_SET_VERTEX_ATTRIB_INPUT_MASK] = 0xffff;
		registers[NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS] = 0x101;
		registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0] = 0x60000;
		registers[NV4097_SET_VERTEX_TEXTURE_FILTER] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS + 8] = 0x101;
		registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + 8] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0 + 8] = 0x60000;
		registers[NV4097_SET_VERTEX_TEXTURE_FILTER + 8] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS + 16] = 0x101;
		registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + 16] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0 + 16] = 0x60000;
		registers[NV4097_SET_VERTEX_TEXTURE_FILTER + 16] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_ADDRESS + 24] = 0x101;
		registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + 24] = 0x0;
		registers[NV4097_SET_VERTEX_TEXTURE_CONTROL0 + 24] = 0x60000;
		registers[NV4097_SET_VERTEX_TEXTURE_FILTER + 24] = 0x0;
		registers[NV4097_SET_CYLINDRICAL_WRAP] = 0x0;
		registers[NV4097_SET_ZMIN_MAX_CONTROL] = 0x1;
		registers[NV4097_SET_TWO_SIDE_LIGHT_EN] = 0x0;
		registers[NV4097_SET_TRANSFORM_BRANCH_BITS] = 0x0;
		registers[NV4097_SET_NO_PARANOID_TEXTURE_FETCHES] = 0x0;

		registers[NV0039_SET_OBJECT] = 0x31337303;
		registers[NV0039_SET_CONTEXT_DMA_NOTIFIES] = 0x66604200;
		registers[NV0039_SET_CONTEXT_DMA_BUFFER_IN] = 0xfeed0001;
		registers[NV0039_SET_CONTEXT_DMA_BUFFER_OUT] = 0xfeed0000;

		registers[NV3062_SET_OBJECT] = 0x313371c3;
		registers[NV3062_SET_CONTEXT_DMA_NOTIFIES] = 0x66604200;
		registers[NV3062_SET_CONTEXT_DMA_IMAGE_SOURCE] = 0xfeed0000;
		registers[NV3062_SET_CONTEXT_DMA_IMAGE_DESTIN] = 0xfeed0000;

		registers[0xa000 / 4] = 0x31337808;
		registers[0xa180 / 4] = 0x66604200;
		registers[0xa184 / 4] = 0x0;
		registers[0xa188 / 4] = 0x0;
		registers[0xa18c / 4] = 0x0;
		registers[0xa190 / 4] = 0x0;
		registers[0xa194 / 4] = 0x0;
		registers[0xa198 / 4] = 0x0;
		registers[0xa19c / 4] = 0x313371c3;
		registers[0xa2fc / 4] = 0x3;
		registers[0xa300 / 4] = 0x4;
		registers[0x8000 / 4] = 0x31337a73;
		registers[0x8180 / 4] = 0x66604200;
		registers[0x8184 / 4] = 0xfeed0000;
		registers[0xc000 / 4] = 0x3137af00;
		registers[0xc180 / 4] = 0x66604200;

		registers[NV406E_SEMAPHORE_OFFSET] = 0x10;
	}

	void rsx_state::decode(u32 reg, u32 value)
	{
		// Store new value and save previous
		register_previous_value = std::exchange(registers[reg], value);
	}

	bool rsx_state::test(u32 reg, u32 value) const
	{
		return registers[reg] == value;
	}

	void draw_clause::operator()(utils::serial& ar)
	{
		ar(draw_command_ranges, draw_command_barriers, current_range_index, primitive, command, is_immediate_draw, is_disjoint_primitive, primitive_barrier_enable, inline_vertex_array);
	}

	void draw_clause::insert_command_barrier(command_barrier_type type, u32 arg, u32 index)
	{
		ensure(!draw_command_ranges.empty());

		auto _do_barrier_insert = [this](barrier_t&& val)
		{
			if (draw_command_barriers.empty() || draw_command_barriers.back() < val)
			{
				draw_command_barriers.push_back(val);
				return;
			}

			for (auto it = draw_command_barriers.begin(); it != draw_command_barriers.end(); it++)
			{
				if (*it < val)
				{
					continue;
				}

				draw_command_barriers.insert(it, val);
				break;
			}
		};

		if (type == primitive_restart_barrier)
		{
			// Rasterization flow barrier
			const auto& last = draw_command_ranges[current_range_index];
			const auto address = last.first + last.count;

			_do_barrier_insert({ current_range_index, 0, address, index, arg, 0, type });
		}
		else
		{
			// Execution dependency barrier. Requires breaking the current draw call sequence and start another.
			if (draw_command_ranges.back().count > 0)
			{
				append_draw_command({});
			}
			else
			{
				// In case of back-to-back modifiers, do not add duplicates
				current_range_index = draw_command_ranges.size() - 1;
			}

			_do_barrier_insert({ current_range_index, rsx::get_shared_tag(), ~0u, index, arg, 0, type });
			last_execution_barrier_index = current_range_index;
		}
	}

	void draw_clause::reset(primitive_type type)
	{
		current_range_index = ~0u;
		last_execution_barrier_index = 0;

		command = draw_command::none;
		primitive = type;
		primitive_barrier_enable = false;

		draw_command_ranges.clear();
		draw_command_barriers.clear();
		inline_vertex_array.clear();

		is_disjoint_primitive = is_primitive_disjointed(primitive);
	}

	u32 draw_clause::execute_pipeline_dependencies() const
	{
		u32 result = 0;

		for (const auto &barrier : draw_command_barriers)
		{
			if (barrier.draw_id != current_range_index)
				continue;

			switch (barrier.type)
			{
			case primitive_restart_barrier:
				break;
			case index_base_modifier_barrier:
				// Change index base offset
				method_registers.decode(NV4097_SET_VERTEX_DATA_BASE_INDEX, barrier.arg);
				result |= index_base_changed;
				break;
			case vertex_base_modifier_barrier:
				// Change vertex base offset
				method_registers.decode(NV4097_SET_VERTEX_DATA_BASE_OFFSET, barrier.arg);
				result |= vertex_base_changed;
				break;
			case vertex_array_offset_modifier_barrier:
				// Change vertex array offset
				method_registers.decode(NV4097_SET_VERTEX_DATA_ARRAY_OFFSET + barrier.index, barrier.arg);
				result |= vertex_arrays_changed;
				break;
			default:
				fmt::throw_exception("Unreachable");
			}
		}

		return result;
	}

	namespace method_detail
	{
		template <u32 Id, u32 Step, u32 Count, template<u32> class T, u32 Index = 0>
		struct bind_range_impl_t
		{
			static inline void impl()
			{
				methods[Id] = &T<Index>::impl;

				if constexpr (Count > 1)
				{
					bind_range_impl_t<Id + Step, Step, Count - 1, T, Index + 1>::impl();
				}
			}
		};

		template <u32 Id, u32 Step, u32 Count, template<u32> class T, u32 Index = 0>
		static inline void bind_range()
		{
			static_assert(Step && Count && Id + u64{Step} * (Count - 1) < 0x10000 / 4);

			bind_range_impl_t<Id, Step, Count, T, Index>::impl();
		}
	}

	// TODO: implement this as virtual function: rsx::thread::init_methods() or something
	// TODO: this is unused
	static const bool s_methods_init = []() -> bool
	{
		using namespace method_detail;

		methods.fill(&invalid_method);

		auto bind = [](u32 id, rsx_method_t func)
		{
			::at32(methods, id) = func;
		};

		auto bind_array = [](u32 id, u32 step, u32 count, rsx_method_t func)
		{
			ensure(step && count && id + u64{step} * (count - 1) < 0x10000 / 4);

			for (u32 i = id; i < id + count * step; i += step)
			{
				methods[i] = func;
			}
		};

		// NV40_CHANNEL_DMA (NV406E)
		methods[NV406E_SET_REFERENCE]                     = nullptr;
		methods[NV406E_SET_CONTEXT_DMA_SEMAPHORE]         = nullptr;
		methods[NV406E_SEMAPHORE_OFFSET]                  = nullptr;
		methods[NV406E_SEMAPHORE_ACQUIRE]                 = nullptr;
		methods[NV406E_SEMAPHORE_RELEASE]                 = nullptr;

		// NV40_CURIE_PRIMITIVE	(NV4097)
		methods[NV4097_SET_OBJECT]                        = nullptr;
		methods[NV4097_NO_OPERATION]                      = nullptr;
		methods[NV4097_NOTIFY]                            = nullptr;
		methods[NV4097_WAIT_FOR_IDLE]                     = nullptr;
		methods[NV4097_PM_TRIGGER]                        = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_NOTIFIES]          = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_A]                 = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_B]                 = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_COLOR_B]           = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_STATE]             = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_COLOR_A]           = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_ZETA]              = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_VERTEX_A]          = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_VERTEX_B]          = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_SEMAPHORE]         = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_REPORT]            = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_CLIP_ID]           = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_CULL_DATA]         = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_COLOR_C]           = nullptr;
		methods[NV4097_SET_CONTEXT_DMA_COLOR_D]           = nullptr;
		methods[NV4097_SET_SURFACE_CLIP_HORIZONTAL]       = nullptr;
		methods[NV4097_SET_SURFACE_CLIP_VERTICAL]         = nullptr;
		methods[NV4097_SET_SURFACE_FORMAT]                = nullptr;
		methods[NV4097_SET_SURFACE_PITCH_A]               = nullptr;
		methods[NV4097_SET_SURFACE_COLOR_AOFFSET]         = nullptr;
		methods[NV4097_SET_SURFACE_ZETA_OFFSET]           = nullptr;
		methods[NV4097_SET_SURFACE_COLOR_BOFFSET]         = nullptr;
		methods[NV4097_SET_SURFACE_PITCH_B]               = nullptr;
		methods[NV4097_SET_SURFACE_COLOR_TARGET]          = nullptr;
		methods[0x224 >> 2]                               = nullptr;
		methods[0x228 >> 2]                               = nullptr;
		methods[0x230 >> 2]                               = nullptr;
		methods[NV4097_SET_SURFACE_PITCH_Z]               = nullptr;
		methods[NV4097_INVALIDATE_ZCULL]                  = nullptr;
		methods[NV4097_SET_CYLINDRICAL_WRAP]              = nullptr;
		methods[NV4097_SET_CYLINDRICAL_WRAP1]             = nullptr;
		methods[0x240 >> 2]                               = nullptr;
		methods[0x244 >> 2]                               = nullptr;
		methods[0x248 >> 2]                               = nullptr;
		methods[0x24C >> 2]                               = nullptr;
		methods[NV4097_SET_SURFACE_PITCH_C]               = nullptr;
		methods[NV4097_SET_SURFACE_PITCH_D]               = nullptr;
		methods[NV4097_SET_SURFACE_COLOR_COFFSET]         = nullptr;
		methods[NV4097_SET_SURFACE_COLOR_DOFFSET]         = nullptr;
		methods[NV4097_SET_WINDOW_OFFSET]                 = nullptr;
		methods[NV4097_SET_WINDOW_CLIP_TYPE]              = nullptr;
		methods[NV4097_SET_WINDOW_CLIP_HORIZONTAL]        = nullptr;
		methods[NV4097_SET_WINDOW_CLIP_VERTICAL]          = nullptr;
		methods[0x2c8 >> 2]                               = nullptr;
		methods[0x2cc >> 2]                               = nullptr;
		methods[0x2d0 >> 2]                               = nullptr;
		methods[0x2d4 >> 2]                               = nullptr;
		methods[0x2d8 >> 2]                               = nullptr;
		methods[0x2dc >> 2]                               = nullptr;
		methods[0x2e0 >> 2]                               = nullptr;
		methods[0x2e4 >> 2]                               = nullptr;
		methods[0x2e8 >> 2]                               = nullptr;
		methods[0x2ec >> 2]                               = nullptr;
		methods[0x2f0 >> 2]                               = nullptr;
		methods[0x2f4 >> 2]                               = nullptr;
		methods[0x2f8 >> 2]                               = nullptr;
		methods[0x2fc >> 2]                               = nullptr;
		methods[NV4097_SET_DITHER_ENABLE]                 = nullptr;
		methods[NV4097_SET_ALPHA_TEST_ENABLE]             = nullptr;
		methods[NV4097_SET_ALPHA_FUNC]                    = nullptr;
		methods[NV4097_SET_ALPHA_REF]                     = nullptr;
		methods[NV4097_SET_BLEND_ENABLE]                  = nullptr;
		methods[NV4097_SET_BLEND_FUNC_SFACTOR]            = nullptr;
		methods[NV4097_SET_BLEND_FUNC_DFACTOR]            = nullptr;
		methods[NV4097_SET_BLEND_COLOR]                   = nullptr;
		methods[NV4097_SET_BLEND_EQUATION]                = nullptr;
		methods[NV4097_SET_COLOR_MASK]                    = nullptr;
		methods[NV4097_SET_STENCIL_TEST_ENABLE]           = nullptr;
		methods[NV4097_SET_STENCIL_MASK]                  = nullptr;
		methods[NV4097_SET_STENCIL_FUNC]                  = nullptr;
		methods[NV4097_SET_STENCIL_FUNC_REF]              = nullptr;
		methods[NV4097_SET_STENCIL_FUNC_MASK]             = nullptr;
		methods[NV4097_SET_STENCIL_OP_FAIL]               = nullptr;
		methods[NV4097_SET_STENCIL_OP_ZFAIL]              = nullptr;
		methods[NV4097_SET_STENCIL_OP_ZPASS]              = nullptr;
		methods[NV4097_SET_TWO_SIDED_STENCIL_TEST_ENABLE] = nullptr;
		methods[NV4097_SET_BACK_STENCIL_MASK]             = nullptr;
		methods[NV4097_SET_BACK_STENCIL_FUNC]             = nullptr;
		methods[NV4097_SET_BACK_STENCIL_FUNC_REF]         = nullptr;
		methods[NV4097_SET_BACK_STENCIL_FUNC_MASK]        = nullptr;
		methods[NV4097_SET_BACK_STENCIL_OP_FAIL]          = nullptr;
		methods[NV4097_SET_BACK_STENCIL_OP_ZFAIL]         = nullptr;
		methods[NV4097_SET_BACK_STENCIL_OP_ZPASS]         = nullptr;
		methods[NV4097_SET_SHADE_MODE]                    = nullptr;
		methods[NV4097_SET_BLEND_ENABLE_MRT]              = nullptr;
		methods[NV4097_SET_COLOR_MASK_MRT]                = nullptr;
		methods[NV4097_SET_LOGIC_OP_ENABLE]               = nullptr;
		methods[NV4097_SET_LOGIC_OP]                      = nullptr;
		methods[NV4097_SET_BLEND_COLOR2]                  = nullptr;
		methods[NV4097_SET_DEPTH_BOUNDS_TEST_ENABLE]      = nullptr;
		methods[NV4097_SET_DEPTH_BOUNDS_MIN]              = nullptr;
		methods[NV4097_SET_DEPTH_BOUNDS_MAX]              = nullptr;
		methods[NV4097_SET_CLIP_MIN]                      = nullptr;
		methods[NV4097_SET_CLIP_MAX]                      = nullptr;
		methods[NV4097_SET_CONTROL0]                      = nullptr;
		methods[NV4097_SET_LINE_WIDTH]                    = nullptr;
		methods[NV4097_SET_LINE_SMOOTH_ENABLE]            = nullptr;
		methods[NV4097_SET_ANISO_SPREAD]                  = nullptr;
		methods[NV4097_SET_SCISSOR_HORIZONTAL]            = nullptr;
		methods[NV4097_SET_SCISSOR_VERTICAL]              = nullptr;
		methods[NV4097_SET_FOG_MODE]                      = nullptr;
		methods[NV4097_SET_FOG_PARAMS]                    = nullptr;
		methods[NV4097_SET_FOG_PARAMS + 1]                = nullptr;
		methods[0x8d8 >> 2]                               = nullptr;
		methods[NV4097_SET_SHADER_PROGRAM]                = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_OFFSET]         = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_FORMAT]         = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_ADDRESS]        = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_CONTROL0]       = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_CONTROL3]       = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_FILTER]         = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT]     = nullptr;
		methods[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR]   = nullptr;
		methods[NV4097_SET_VIEWPORT_HORIZONTAL]           = nullptr;
		methods[NV4097_SET_VIEWPORT_VERTICAL]             = nullptr;
		methods[NV4097_SET_POINT_CENTER_MODE]             = nullptr;
		methods[NV4097_ZCULL_SYNC]                        = nullptr;
		methods[NV4097_SET_VIEWPORT_OFFSET]               = nullptr;
		methods[NV4097_SET_VIEWPORT_OFFSET + 1]           = nullptr;
		methods[NV4097_SET_VIEWPORT_OFFSET + 2]           = nullptr;
		methods[NV4097_SET_VIEWPORT_OFFSET + 3]           = nullptr;
		methods[NV4097_SET_VIEWPORT_SCALE]                = nullptr;
		methods[NV4097_SET_VIEWPORT_SCALE + 1]            = nullptr;
		methods[NV4097_SET_VIEWPORT_SCALE + 2]            = nullptr;
		methods[NV4097_SET_VIEWPORT_SCALE + 3]            = nullptr;
		methods[NV4097_SET_POLY_OFFSET_POINT_ENABLE]      = nullptr;
		methods[NV4097_SET_POLY_OFFSET_LINE_ENABLE]       = nullptr;
		methods[NV4097_SET_POLY_OFFSET_FILL_ENABLE]       = nullptr;
		methods[NV4097_SET_DEPTH_FUNC]                    = nullptr;
		methods[NV4097_SET_DEPTH_MASK]                    = nullptr;
		methods[NV4097_SET_DEPTH_TEST_ENABLE]             = nullptr;
		methods[NV4097_SET_POLYGON_OFFSET_SCALE_FACTOR]   = nullptr;
		methods[NV4097_SET_POLYGON_OFFSET_BIAS]           = nullptr;
		methods[NV4097_SET_VERTEX_DATA_SCALED4S_M]        = nullptr;
		methods[NV4097_SET_TEXTURE_CONTROL2]              = nullptr;
		methods[NV4097_SET_TEX_COORD_CONTROL]             = nullptr;
		methods[NV4097_SET_TRANSFORM_PROGRAM]             = nullptr;
		methods[NV4097_SET_SPECULAR_ENABLE]               = nullptr;
		methods[NV4097_SET_TWO_SIDE_LIGHT_EN]             = nullptr;
		methods[NV4097_CLEAR_ZCULL_SURFACE]               = nullptr;
		methods[NV4097_SET_PERFORMANCE_PARAMS]            = nullptr;
		methods[NV4097_SET_FLAT_SHADE_OP]                 = nullptr;
		methods[NV4097_SET_EDGE_FLAG]                     = nullptr;
		methods[NV4097_SET_USER_CLIP_PLANE_CONTROL]       = nullptr;
		methods[NV4097_SET_POLYGON_STIPPLE]               = nullptr;
		methods[NV4097_SET_POLYGON_STIPPLE_PATTERN]       = nullptr;
		methods[NV4097_SET_VERTEX_DATA3F_M]               = nullptr;
		methods[NV4097_SET_VERTEX_DATA_ARRAY_OFFSET]      = nullptr;
		methods[NV4097_INVALIDATE_VERTEX_CACHE_FILE]      = nullptr;
		methods[NV4097_INVALIDATE_VERTEX_FILE]            = nullptr;
		methods[NV4097_PIPE_NOP]                          = nullptr;
		methods[NV4097_SET_VERTEX_DATA_BASE_OFFSET]       = nullptr;
		methods[NV4097_SET_VERTEX_DATA_BASE_INDEX]        = nullptr;
		methods[NV4097_SET_VERTEX_DATA_ARRAY_FORMAT]      = nullptr;
		methods[NV4097_CLEAR_REPORT_VALUE]                = nullptr;
		methods[NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE]      = nullptr;
		methods[NV4097_GET_REPORT]                        = nullptr;
		methods[NV4097_SET_ZCULL_STATS_ENABLE]            = nullptr;
		methods[NV4097_SET_BEGIN_END]                     = nullptr;
		methods[NV4097_ARRAY_ELEMENT16]                   = nullptr;
		methods[NV4097_ARRAY_ELEMENT32]                   = nullptr;
		methods[NV4097_DRAW_ARRAYS]                       = nullptr;
		methods[NV4097_INLINE_ARRAY]                      = nullptr;
		methods[NV4097_SET_INDEX_ARRAY_ADDRESS]           = nullptr;
		methods[NV4097_SET_INDEX_ARRAY_DMA]               = nullptr;
		methods[NV4097_DRAW_INDEX_ARRAY]                  = nullptr;
		methods[NV4097_SET_FRONT_POLYGON_MODE]            = nullptr;
		methods[NV4097_SET_BACK_POLYGON_MODE]             = nullptr;
		methods[NV4097_SET_CULL_FACE]                     = nullptr;
		methods[NV4097_SET_FRONT_FACE]                    = nullptr;
		methods[NV4097_SET_POLY_SMOOTH_ENABLE]            = nullptr;
		methods[NV4097_SET_CULL_FACE_ENABLE]              = nullptr;
		methods[NV4097_SET_TEXTURE_CONTROL3]              = nullptr;
		methods[NV4097_SET_VERTEX_DATA2F_M]               = nullptr;
		methods[NV4097_SET_VERTEX_DATA2S_M]               = nullptr;
		methods[NV4097_SET_VERTEX_DATA4UB_M]              = nullptr;
		methods[NV4097_SET_VERTEX_DATA4S_M]               = nullptr;
		methods[NV4097_SET_TEXTURE_OFFSET]                = nullptr;
		methods[NV4097_SET_TEXTURE_FORMAT]                = nullptr;
		methods[NV4097_SET_TEXTURE_ADDRESS]               = nullptr;
		methods[NV4097_SET_TEXTURE_CONTROL0]              = nullptr;
		methods[NV4097_SET_TEXTURE_CONTROL1]              = nullptr;
		methods[NV4097_SET_TEXTURE_FILTER]                = nullptr;
		methods[NV4097_SET_TEXTURE_IMAGE_RECT]            = nullptr;
		methods[NV4097_SET_TEXTURE_BORDER_COLOR]          = nullptr;
		methods[NV4097_SET_VERTEX_DATA4F_M]               = nullptr;
		methods[NV4097_SET_COLOR_KEY_COLOR]               = nullptr;
		methods[0x1d04 >> 2]                              = nullptr;
		methods[NV4097_SET_SHADER_CONTROL]                = nullptr;
		methods[NV4097_SET_INDEXED_CONSTANT_READ_LIMITS]  = nullptr;
		methods[NV4097_SET_SEMAPHORE_OFFSET]              = nullptr;
		methods[NV4097_BACK_END_WRITE_SEMAPHORE_RELEASE]  = nullptr;
		methods[NV4097_TEXTURE_READ_SEMAPHORE_RELEASE]    = nullptr;
		methods[NV4097_SET_ZMIN_MAX_CONTROL]              = nullptr;
		methods[NV4097_SET_ANTI_ALIASING_CONTROL]         = nullptr;
		methods[NV4097_SET_SURFACE_COMPRESSION]           = nullptr;
		methods[NV4097_SET_ZCULL_EN]                      = nullptr;
		methods[NV4097_SET_SHADER_WINDOW]                 = nullptr;
		methods[NV4097_SET_ZSTENCIL_CLEAR_VALUE]          = nullptr;
		methods[NV4097_SET_COLOR_CLEAR_VALUE]             = nullptr;
		methods[NV4097_CLEAR_SURFACE]                     = nullptr;
		methods[NV4097_SET_CLEAR_RECT_HORIZONTAL]         = nullptr;
		methods[NV4097_SET_CLEAR_RECT_VERTICAL]           = nullptr;
		methods[NV4097_SET_CLIP_ID_TEST_ENABLE]           = nullptr;
		methods[NV4097_SET_RESTART_INDEX_ENABLE]          = nullptr;
		methods[NV4097_SET_RESTART_INDEX]                 = nullptr;
		methods[NV4097_SET_LINE_STIPPLE]                  = nullptr;
		methods[NV4097_SET_LINE_STIPPLE_PATTERN]          = nullptr;
		methods[NV4097_SET_VERTEX_DATA1F_M]               = nullptr;
		methods[NV4097_SET_TRANSFORM_EXECUTION_MODE]      = nullptr;
		methods[NV4097_SET_RENDER_ENABLE]                 = nullptr;
		methods[NV4097_SET_TRANSFORM_PROGRAM_LOAD]        = nullptr;
		methods[NV4097_SET_TRANSFORM_PROGRAM_START]       = nullptr;
		methods[NV4097_SET_ZCULL_CONTROL0]                = nullptr;
		methods[NV4097_SET_ZCULL_CONTROL1]                = nullptr;
		methods[NV4097_SET_SCULL_CONTROL]                 = nullptr;
		methods[NV4097_SET_POINT_SIZE]                    = nullptr;
		methods[NV4097_SET_POINT_PARAMS_ENABLE]           = nullptr;
		methods[NV4097_SET_POINT_SPRITE_CONTROL]          = nullptr;
		methods[NV4097_SET_TRANSFORM_TIMEOUT]             = nullptr;
		methods[NV4097_SET_TRANSFORM_CONSTANT_LOAD]       = nullptr;
		methods[NV4097_SET_TRANSFORM_CONSTANT]            = nullptr;
		methods[NV4097_SET_FREQUENCY_DIVIDER_OPERATION]   = nullptr;
		methods[NV4097_SET_ATTRIB_COLOR]                  = nullptr;
		methods[NV4097_SET_ATTRIB_TEX_COORD]              = nullptr;
		methods[NV4097_SET_ATTRIB_TEX_COORD_EX]           = nullptr;
		methods[NV4097_SET_ATTRIB_UCLIP0]                 = nullptr;
		methods[NV4097_SET_ATTRIB_UCLIP1]                 = nullptr;
		methods[NV4097_INVALIDATE_L2]                     = nullptr;
		methods[NV4097_SET_REDUCE_DST_COLOR]              = nullptr;
		methods[NV4097_SET_NO_PARANOID_TEXTURE_FETCHES]   = nullptr;
		methods[NV4097_SET_SHADER_PACKER]                 = nullptr;
		methods[NV4097_SET_VERTEX_ATTRIB_INPUT_MASK]      = nullptr;
		methods[NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK]     = nullptr;
		methods[NV4097_SET_TRANSFORM_BRANCH_BITS]         = nullptr;

		// NV03_MEMORY_TO_MEMORY_FORMAT	(NV0039)
		methods[NV0039_SET_OBJECT]                        = nullptr;
		bind(0x2100 >> 2, trace_method);
		methods[NV0039_SET_CONTEXT_DMA_NOTIFIES]          = nullptr;
		methods[NV0039_SET_CONTEXT_DMA_BUFFER_IN]         = nullptr;
		methods[NV0039_SET_CONTEXT_DMA_BUFFER_OUT]        = nullptr;
		methods[NV0039_OFFSET_IN]                         = nullptr;
		methods[NV0039_OFFSET_OUT]                        = nullptr;
		methods[NV0039_PITCH_IN]                          = nullptr;
		methods[NV0039_PITCH_OUT]                         = nullptr;
		methods[NV0039_LINE_LENGTH_IN]                    = nullptr;
		methods[NV0039_LINE_COUNT]                        = nullptr;
		methods[NV0039_FORMAT]                            = nullptr;
		methods[NV0039_BUFFER_NOTIFY]                     = nullptr;

		// NV30_CONTEXT_SURFACES_2D	(NV3062)
		methods[NV3062_SET_OBJECT]                        = nullptr;
		methods[NV3062_SET_CONTEXT_DMA_NOTIFIES]          = nullptr;
		methods[NV3062_SET_CONTEXT_DMA_IMAGE_SOURCE]      = nullptr;
		methods[NV3062_SET_CONTEXT_DMA_IMAGE_DESTIN]      = nullptr;
		methods[NV3062_SET_COLOR_FORMAT]                  = nullptr;
		methods[NV3062_SET_PITCH]                         = nullptr;
		methods[NV3062_SET_OFFSET_SOURCE]                 = nullptr;
		methods[NV3062_SET_OFFSET_DESTIN]                 = nullptr;

		// NV30_CONTEXT_SURFACE_SWIZZLED (NV309E)
		methods[NV309E_SET_OBJECT]                        = nullptr;
		methods[NV309E_SET_CONTEXT_DMA_NOTIFIES]          = nullptr;
		methods[NV309E_SET_CONTEXT_DMA_IMAGE]             = nullptr;
		methods[NV309E_SET_FORMAT]                        = nullptr;
		methods[NV309E_SET_OFFSET]                        = nullptr;

		// NV30_IMAGE_FROM_CPU (NV308A)
		methods[NV308A_SET_OBJECT]                        = nullptr;
		methods[NV308A_SET_CONTEXT_DMA_NOTIFIES]          = nullptr;
		methods[NV308A_SET_CONTEXT_COLOR_KEY]             = nullptr;
		methods[NV308A_SET_CONTEXT_CLIP_RECTANGLE]        = nullptr;
		methods[NV308A_SET_CONTEXT_PATTERN]               = nullptr;
		methods[NV308A_SET_CONTEXT_ROP]                   = nullptr;
		methods[NV308A_SET_CONTEXT_BETA1]                 = nullptr;
		methods[NV308A_SET_CONTEXT_BETA4]                 = nullptr;
		methods[NV308A_SET_CONTEXT_SURFACE]               = nullptr;
		methods[NV308A_SET_COLOR_CONVERSION]              = nullptr;
		methods[NV308A_SET_OPERATION]                     = nullptr;
		methods[NV308A_SET_COLOR_FORMAT]                  = nullptr;
		methods[NV308A_POINT]                             = nullptr;
		methods[NV308A_SIZE_OUT]                          = nullptr;
		methods[NV308A_SIZE_IN]                           = nullptr;
		methods[NV308A_COLOR]                             = nullptr;

		// NV30_SCALED_IMAGE_FROM_MEMORY (NV3089)
		methods[NV3089_SET_OBJECT]                        = nullptr;
		methods[NV3089_SET_CONTEXT_DMA_NOTIFIES]          = nullptr;
		methods[NV3089_SET_CONTEXT_DMA_IMAGE]             = nullptr;
		methods[NV3089_SET_CONTEXT_PATTERN]               = nullptr;
		methods[NV3089_SET_CONTEXT_ROP]                   = nullptr;
		methods[NV3089_SET_CONTEXT_BETA1]                 = nullptr;
		methods[NV3089_SET_CONTEXT_BETA4]                 = nullptr;
		methods[NV3089_SET_CONTEXT_SURFACE]               = nullptr;
		methods[NV3089_SET_COLOR_CONVERSION]              = nullptr;
		methods[NV3089_SET_COLOR_FORMAT]                  = nullptr;
		methods[NV3089_SET_OPERATION]                     = nullptr;
		methods[NV3089_CLIP_POINT]                        = nullptr;
		methods[NV3089_CLIP_SIZE]                         = nullptr;
		methods[NV3089_IMAGE_OUT_POINT]                   = nullptr;
		methods[NV3089_IMAGE_OUT_SIZE]                    = nullptr;
		methods[NV3089_DS_DX]                             = nullptr;
		methods[NV3089_DT_DY]                             = nullptr;
		methods[NV3089_IMAGE_IN_SIZE]                     = nullptr;
		methods[NV3089_IMAGE_IN_FORMAT]                   = nullptr;
		methods[NV3089_IMAGE_IN_OFFSET]                   = nullptr;
		methods[NV3089_IMAGE_IN]                          = nullptr;

		//Some custom GCM methods
		methods[GCM_SET_DRIVER_OBJECT]                    = nullptr;
		methods[FIFO::FIFO_DRAW_BARRIER >> 2]             = nullptr;

		bind_array(GCM_FLIP_HEAD, 1, 2, nullptr);
		bind_array(GCM_DRIVER_QUEUE, 1, 8, nullptr);

		bind_array(0x400 >> 2, 1, 0x10, nullptr);
		bind_array(0x440 >> 2, 1, 0x20, nullptr);
		bind_array(NV4097_SET_ANISO_SPREAD, 1, 16, nullptr);
		bind_array(NV4097_SET_VERTEX_TEXTURE_OFFSET, 1, 8 * 4, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA_SCALED4S_M, 1, 32, nullptr);
		bind_array(NV4097_SET_TEXTURE_CONTROL2, 1, 16, nullptr);
		bind_array(NV4097_SET_TEX_COORD_CONTROL, 1, 10, nullptr);
		bind_array(NV4097_SET_TRANSFORM_PROGRAM, 1, 32, nullptr);
		bind_array(NV4097_SET_POLYGON_STIPPLE_PATTERN, 1, 32, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA3F_M, 1, 64, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA_ARRAY_OFFSET, 1, 16, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA_ARRAY_FORMAT, 1, 16, nullptr);
		bind_array(NV4097_SET_TEXTURE_CONTROL3, 1, 16, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA2F_M, 1, 32, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA2S_M, 1, 16, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA4UB_M, 1, 16, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA4S_M, 1, 32, nullptr);
		bind_array(NV4097_SET_TEXTURE_OFFSET, 1, 8 * 16, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA4F_M, 1, 64, nullptr);
		bind_array(NV4097_SET_VERTEX_DATA1F_M, 1, 16, nullptr);
		bind_array(NV4097_SET_COLOR_KEY_COLOR, 1, 16, nullptr);

		// Unknown (NV4097?)
		bind(0x171c >> 2, trace_method);

		// NV406E
		bind(NV406E_SET_REFERENCE, nv406e::set_reference);
		bind(NV406E_SEMAPHORE_ACQUIRE, nv406e::semaphore_acquire);
		bind(NV406E_SEMAPHORE_RELEASE, nv406e::semaphore_release);

		// NV4097
		bind(NV4097_SET_CULL_FACE, nv4097::set_face_property);
		bind(NV4097_SET_FRONT_FACE, nv4097::set_face_property);
		bind(NV4097_TEXTURE_READ_SEMAPHORE_RELEASE, nv4097::texture_read_semaphore_release);
		bind(NV4097_BACK_END_WRITE_SEMAPHORE_RELEASE, nv4097::back_end_write_semaphore_release);
		bind(NV4097_SET_BEGIN_END, nv4097::set_begin_end);
		bind(NV4097_CLEAR_SURFACE, nv4097::clear);
		bind(NV4097_DRAW_ARRAYS, nv4097::draw_arrays);
		bind(NV4097_DRAW_INDEX_ARRAY, nv4097::draw_index_array);
		bind(NV4097_INLINE_ARRAY, nv4097::draw_inline_array);
		bind(NV4097_ARRAY_ELEMENT16, nv4097::set_array_element16);
		bind(NV4097_ARRAY_ELEMENT32, nv4097::set_array_element32);
		bind_range<NV4097_SET_VERTEX_DATA_SCALED4S_M, 1, 32, nv4097::set_vertex_data_scaled4s_m>();
		bind_range<NV4097_SET_VERTEX_DATA4UB_M, 1, 16, nv4097::set_vertex_data4ub_m>();
		bind_range<NV4097_SET_VERTEX_DATA1F_M, 1, 16, nv4097::set_vertex_data1f_m>();
		bind_range<NV4097_SET_VERTEX_DATA2F_M, 1, 32, nv4097::set_vertex_data2f_m>();
		bind_range<NV4097_SET_VERTEX_DATA3F_M, 1, 64, nv4097::set_vertex_data3f_m>();
		bind_range<NV4097_SET_VERTEX_DATA4F_M, 1, 64, nv4097::set_vertex_data4f_m>();
		bind_range<NV4097_SET_VERTEX_DATA2S_M, 1, 16, nv4097::set_vertex_data2s_m>();
		bind_range<NV4097_SET_VERTEX_DATA4S_M, 1, 32, nv4097::set_vertex_data4s_m>();
		bind_array(NV4097_SET_TRANSFORM_CONSTANT, 1, 32, nv4097::set_transform_constant::impl);
		bind_array(NV4097_SET_TRANSFORM_PROGRAM, 1, 32, nv4097::set_transform_program::impl);
		bind(NV4097_GET_REPORT, nv4097::get_report);
		bind(NV4097_CLEAR_REPORT_VALUE, nv4097::clear_report_value);
		bind(NV4097_SET_SURFACE_CLIP_HORIZONTAL, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_CLIP_VERTICAL, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_COLOR_AOFFSET, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_COLOR_BOFFSET, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_COLOR_COFFSET, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_COLOR_DOFFSET, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_COLOR_TARGET, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_ZETA_OFFSET, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_CONTEXT_DMA_COLOR_A, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_CONTEXT_DMA_COLOR_B, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_CONTEXT_DMA_COLOR_C, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_CONTEXT_DMA_COLOR_D, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_CONTEXT_DMA_ZETA, nv4097::set_surface_dirty_bit);
		bind(NV4097_NOTIFY, nv4097::set_notify);
		bind(NV4097_SET_SURFACE_FORMAT, nv4097::set_surface_format);
		bind(NV4097_SET_SURFACE_PITCH_A, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_PITCH_B, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_PITCH_C, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_PITCH_D, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_SURFACE_PITCH_Z, nv4097::set_surface_dirty_bit);
		bind(NV4097_SET_WINDOW_OFFSET, nv4097::set_surface_dirty_bit);
		bind_range<NV4097_SET_TEXTURE_OFFSET, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_FORMAT, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_ADDRESS, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_CONTROL0, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_CONTROL1, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_CONTROL2, 1, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_CONTROL3, 1, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_FILTER, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_IMAGE_RECT, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_TEXTURE_BORDER_COLOR, 8, 16, nv4097::set_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_OFFSET, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_FORMAT, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_ADDRESS, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_CONTROL0, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_CONTROL3, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_FILTER, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind_range<NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR, 8, 4, nv4097::set_vertex_texture_dirty_bit>();
		bind(NV4097_SET_RENDER_ENABLE, nv4097::set_render_mode);
		bind(NV4097_SET_ZCULL_EN, nv4097::set_zcull_render_enable);
		bind(NV4097_SET_ZCULL_STATS_ENABLE, nv4097::set_zcull_stats_enable);
		bind(NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE, nv4097::set_zcull_pixel_count_enable);
		bind(NV4097_CLEAR_ZCULL_SURFACE, nv4097::clear_zcull);
		bind(NV4097_SET_DEPTH_TEST_ENABLE, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_DEPTH_FUNC, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_DEPTH_MASK, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_COLOR_MASK, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_COLOR_MASK_MRT, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_TWO_SIDED_STENCIL_TEST_ENABLE, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_STENCIL_TEST_ENABLE, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_STENCIL_MASK, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_STENCIL_OP_ZPASS, nv4097::set_stencil_op);
		bind(NV4097_SET_STENCIL_OP_FAIL, nv4097::set_stencil_op);
		bind(NV4097_SET_STENCIL_OP_ZFAIL, nv4097::set_stencil_op);
		bind(NV4097_SET_BACK_STENCIL_MASK, nv4097::set_surface_options_dirty_bit);
		bind(NV4097_SET_BACK_STENCIL_OP_ZPASS, nv4097::set_stencil_op);
		bind(NV4097_SET_BACK_STENCIL_OP_FAIL, nv4097::set_stencil_op);
		bind(NV4097_SET_BACK_STENCIL_OP_ZFAIL, nv4097::set_stencil_op);
		bind(NV4097_WAIT_FOR_IDLE, nv4097::sync);
		bind(NV4097_INVALIDATE_L2, nv4097::set_shader_program_dirty);
		bind(NV4097_SET_SHADER_PROGRAM, nv4097::set_shader_program_dirty);

		bind(NV4097_SET_TRANSFORM_PROGRAM_START, nv4097::set_transform_program_start);
		bind(NV4097_SET_VERTEX_ATTRIB_OUTPUT_MASK, nv4097::set_vertex_attribute_output_mask);
		bind(NV4097_SET_VERTEX_DATA_BASE_OFFSET, nv4097::set_vertex_base_offset);
		bind(NV4097_SET_VERTEX_DATA_BASE_INDEX, nv4097::set_index_base_offset);
		bind_range<NV4097_SET_VERTEX_DATA_ARRAY_OFFSET, 1, 16, nv4097::set_vertex_array_offset>();
		bind(NV4097_SET_INDEX_ARRAY_DMA, nv4097::check_index_array_dma);
		bind(NV4097_SET_BLEND_EQUATION, nv4097::set_blend_equation);
		bind(NV4097_SET_BLEND_FUNC_SFACTOR, nv4097::set_blend_factor);
		bind(NV4097_SET_BLEND_FUNC_DFACTOR, nv4097::set_blend_factor);

		//NV308A (0xa400..0xbffc!)
		bind_array(NV308A_COLOR, 1, 256 * 7, nv308a::color::impl);

		//NV3089
		bind(NV3089_IMAGE_IN, nv3089::image_in);

		//NV0039
		bind(NV0039_BUFFER_NOTIFY, nv0039::buffer_notify);

		// lv1 hypervisor
		bind_array(GCM_SET_USER_COMMAND, 1, 2, user_command);
		bind_range<GCM_FLIP_HEAD, 1, 2, gcm::driver_flip>();
		bind_range<GCM_DRIVER_QUEUE, 1, 8, gcm::queue_flip>();

		// custom methods
		bind(GCM_FLIP_COMMAND, flip_command);

		// FIFO
		bind(FIFO::FIFO_DRAW_BARRIER >> 2, fifo::draw_barrier);

		method_registers.init();

		return true;
	}();
}
