#include "pch.h"
#include "AZBK.h"
#include "Platform.h"

/////////////////////////////////////////////////////////////////////////////////
//
// AZ Screen
//
/////////////////////////////////////////////////////////////////////////////////

const std::vector<int> CAZBK::m_vAZBPP = { 1, 2, 4, 8 };
const std::vector<int> CAZBK::m_vAZLLEN = { 32 * 2, 64 * 2, 128 * 2, 256 * 2 }; // ширина строки в байтах
const std::vector<int> CAZBK::m_vAZXS = { 1, 2, 4, 8 };
const std::vector<int> CAZBK::m_vAZYS = { 1, 2, 3, 4 };

const std::vector<int> CAZBK::m_vAZLSCRL = { 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 131072, 196608, 262144 };


bool CAZBK::StartVideoThread()
{
	if (!m_lockVidThr.IsLocked())
	{
		m_VideoThread = std::thread(&CAZBK::VideoThreadFunc, this);

		if (m_VideoThread.joinable())
		{
			m_VideoThread.detach();
			return true;
		}
	}

	return false;
}

void CAZBK::StopVideoThread()
{
	if (m_lockVidThr.IsLocked())
	{
		m_lockVid.Lock();

		do
		{
			SleepEx(10, FALSE);
		}
		while (m_lockVidThr.IsLocked());
	}
}

void CAZBK::VideoThreadFunc() const
{
	DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 1);
	LARGE_INTEGER liDueTime1{};    //интервал таймера
	LARGE_INTEGER liDueTime2{};    //интервал таймера
	liDueTime1.QuadPart = -158798;
	liDueTime2.QuadPart = 7869;
	// создаём waitable timer.
	HANDLE hWaitableTimer = ::CreateWaitableTimer(nullptr, FALSE, nullptr);

	if (hWaitableTimer)
	{
		m_lockVidThr.Lock();

		do
		{
			// Устанавливаем таймер.
			::SetWaitableTimer(hWaitableTimer, &liDueTime1, 0, nullptr, nullptr, FALSE);
			FillTexture();

			m_pAZBlt->DoPrepare(m_AZRegs.nBLTC177270, AZ_BANK_TO_OFFSET(m_AZRegs.nBLTP177272));

			//g_Platform.GetMainPtr()->PostMessage(WM_SCR_DRAW, 1);
			g_Platform.GetScreenPtr()->DrawScreen(true, 1); // рисуем напрямую. Так быстрее, но в Direct2D тормозит, когда окно свёрнуто.
			// ждём срабатывания таймера.
			::WaitForSingleObject(hWaitableTimer, INFINITE);
			::SetWaitableTimer(hWaitableTimer, &liDueTime2, 0, nullptr, nullptr, FALSE);
			m_pAZBlt->DoExec();

			// если включен таймер и режим 60Гц - делаем прерывание
			if ((m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER)) == AZ_MMU_V100_ON)
			{
				g_Platform.GetCPUPtr()->TickIRQ2();
			}
			::WaitForSingleObject(hWaitableTimer, INFINITE);
		}
		while (!m_lockVid.IsLocked());       //пока не придёт событие остановки

		::CancelWaitableTimer(hWaitableTimer);
		::CloseHandle(hWaitableTimer);
		m_lockVid.UnLock(); //выставляем событие, что таймер остановился, и можно продолжать остальные действия по остановке
		m_lockVidThr.UnLock();
	}

	::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
}


uint32_t CAZBK::R5G5B5toRGB(uint16_t c) const
{
	uint8_t b = (c & 0x1f) << 3;
	uint8_t g = ((c >> 5) & 0x1f) << 3;
	uint8_t r = ((c >> 10) & 0x1f) << 3;
	return 0xff000000 | (uint32_t(r) << 16) | (uint32_t(g) << 8) | b;
}

uint16_t CAZBK::RGBtoR5G5B5(uint32_t c) const
{
	uint8_t b = (c & 0xff) >> 3;
	uint8_t g = ((c >> 8) & 0xff) >> 3;
	uint8_t r = ((c >> 16) & 0xff) >> 3;
	return (uint16_t(r) << 10) | (uint16_t(g) << 5) | b;
}

void CAZBK::SetScreenParam(uint16_t v)
{
	std::lock_guard<std::mutex> lk(m_mutScrParam);
	m_AZData.nAZVideoMode = v & 7;
	m_AZData.nAZBpp = m_vAZBPP[v & 3];
	m_AZData.nAZLineLen = m_vAZLLEN[(v >> 3) & 3];
	m_AZData.nAZXStretch = m_vAZXS[(v >> 6) & 3];
	m_AZData.nAZYStretch = m_vAZYS[(v >> 9) & 3];
	int idx = (v >> 12) & 0x0f;

	if (idx > 11)
	{
		idx = 11;
	}

	m_AZData.nAZScrlSize = m_vAZLSCRL[idx];
	// теперь посчитаем сколько строк будет рулон
	m_AZData.nAZScrlLines = (m_AZData.nAZScrlSize * 2 / m_AZData.nAZLineLen);
}

// формирование экрана
void CAZBK::FillTexture() const
{
	std::lock_guard<std::mutex> lk(m_mutScrParam);
	uint32_t *pBits = m_pTexture;
	int nOffs = AZ_BANK_TO_OFFSET(m_AZRegs.nSCRPageNum_L0); // это смещение до начала экрана в памяти
	auto pDataBase_L0 = const_cast<uint8_t *>(&m_pMemory[nOffs]); // вот отсюда начинаются данные верхнего слоя
	nOffs = AZ_BANK_TO_OFFSET(m_AZRegs.nSCRPageNum_L1); // это смещение до начала экрана в памяти
	auto pDataBase_L1 = const_cast<uint8_t *>(&m_pMemory[nOffs]); // вот отсюда начинаются данные среднего слоя
	nOffs = AZ_BANK_TO_OFFSET(m_AZRegs.nSCRPageNum_L2); // это смещение до начала экрана в памяти
	auto pDataBase_L2 = const_cast<uint8_t *>(&m_pMemory[nOffs]); // вот отсюда начинаются данные нижнего слоя

	switch (m_AZData.nAZVideoMode)
	{
		default:
		case 0: // 1 бит на цвет (2 цвета)
		{
			int nHeight = 768; // фиксированная реальная высота в строках
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_2_COLOR_IDX]; // начало палитры
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				uint8_t *pData = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines);
				uint32_t *pLine = pBits;   // начало текущей линии

				// !!! Сейчас мы ограничиваем скроллинг шириной строки экрана.
				// !!! А в реальности наверное нужно ограничивать размером значения скроллинга - 256
				int idx = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс

				for (int c = m_AZData.nAZLineLen; c >0; c--) // длина линии в байтах
				{
					uint8_t w = pData[idx]; // берём очередной байт
					idx = ++idx % m_AZData.nAZLineLen;

					for (int i = 8; i > 0; i--) // 8 бит
					{
						const size_t p = w & 1; w >>= 1;
						const uint32_t px = *(pPaletteIdx + p);
						int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px; // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl0; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl0:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 1: // 2 бита на цвет (4 цвета)
		{
			int nHeight = 768;
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_4_COLOR_IDX + m_AZData.nLegacyPalette * 4];
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pData = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines);
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idx = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					uint8_t w = pData[idx]; // берём очередной байт
					idx = ++idx % m_AZData.nAZLineLen;

					for (int i = 4; i > 0; i--) // 4 двухбития
					{
						const size_t p = w & 0x3; w >>= 2;
						const uint32_t px = *(pPaletteIdx + p);
						int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl1; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl1:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 2: // 4 бита на цвет (16 цветов)
		{
			int nHeight = 768;
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_16_COLOR_IDX]; // начало палитры
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pData = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines);
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idx = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					uint8_t w = pData[idx]; // берём очередной байт
					idx = ++idx % m_AZData.nAZLineLen;

					for (int i = 2; i > 0; i--)    // 2 четырёхбития
					{
						const size_t p = w & 0xf; w >>= 4;
						const uint32_t px = *(pPaletteIdx + p);
						int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl2; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl2:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 3: // 8 бит на цвет (256 цветов)
		{
			int nHeight = 768;
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pData = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines);
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idx = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					const size_t p = pData[idx]; // берём очередной байт
					idx = ++idx % m_AZData.nAZLineLen;

					const uint32_t px = *(pPaletteIdx + p);
					int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

					do
					{
						*pBits++ = px;  // рисуем точку

						if (!(--nWidth))
						{
							goto l_endl3; // если нарисовали всю строку, то хватит
						}
					}
					while (--sx);
				}

l_endl3:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 4: // 1 бит на цвет (8 цветов, сборный "слоеный" режим)
		{
			int nHeight = 768; // фиксированная реальная высота в строках
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pDataL0 = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines); // верхний слой
				const uint8_t *pDataL1 = pDataBase_L1 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177246) % m_AZData.nAZScrlLines); // средний слой
				const uint8_t *pDataL2 = pDataBase_L2 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177244) % m_AZData.nAZScrlLines); // нижний слой
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idxL0 = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				int idxL1 = m_AZRegs.nHSCRL177254 % m_AZData.nAZLineLen; // текущий индекс
				int idxL2 = m_AZRegs.nHSCRL177256 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					uint8_t w  = pDataL0[idxL0]; // берём очередной байт
					uint8_t w2 = pDataL1[idxL1]; // берём очередной байт
					uint8_t w3 = pDataL2[idxL2]; // берём очередной байт
					idxL0 = ++idxL0 % m_AZData.nAZLineLen;
					idxL1 = ++idxL1 % m_AZData.nAZLineLen;
					idxL2 = ++idxL2 % m_AZData.nAZLineLen;

					for (int i = 8; i > 0; i--) // 8 бит
					{
						size_t p = (static_cast<size_t>(w & 1)) << 2; w >>= 1;
						p |= (static_cast<size_t>(w2 & 1)) << 1; w2 >>= 1;
						p |= (w3 & 1); w3 >>= 1;
						const uint32_t px = *(pPaletteIdx + p);
						int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px; // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl4; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl4:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 5: // 2 бита на цвет (4 цвета, включены слои)
		{
			// со слоями.
			int nHeight = 768;
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_4_COLOR_IDX + m_AZData.nLegacyPalette * 4];
			const uint32_t *pPaletteIdx2 = &m_azPalette[AZ_4_COLOR_IDX + ((m_AZData.nLegacyPalette + 1) & 0xf) * 4];
			const uint32_t *pPaletteIdx3 = &m_azPalette[AZ_4_COLOR_IDX + ((m_AZData.nLegacyPalette + 2) & 0xf) * 4];
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pDataL0 = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines); // верхний слой
				const uint8_t *pDataL1 = pDataBase_L1 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177246) % m_AZData.nAZScrlLines); // средний слой
				const uint8_t *pDataL2 = pDataBase_L2 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177244) % m_AZData.nAZScrlLines); // нижний слой
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idxL0 = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				int idxL1 = m_AZRegs.nHSCRL177254 % m_AZData.nAZLineLen; // текущий индекс
				int idxL2 = m_AZRegs.nHSCRL177256 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					uint8_t w = pDataL0[idxL0]; // берём очередной байт
					uint8_t w2 = pDataL1[idxL1]; // берём очередной байт
					uint8_t w3 = pDataL2[idxL2]; // берём очередной байт
					idxL0 = ++idxL0 % m_AZData.nAZLineLen;
					idxL1 = ++idxL1 % m_AZData.nAZLineLen;
					idxL2 = ++idxL2 % m_AZData.nAZLineLen;

					for (int i = 4; i > 0; i--) // 4 двухбития
					{
						const size_t p = w & 0x3;
						const size_t p2 = w2 & 0x3;
						const size_t p3 = w3 & 0x3;
						const uint32_t px = p ? *(pPaletteIdx + p) : (p2 ? * (pPaletteIdx2 + p2) : * (pPaletteIdx3 + p3));
						w >>= 2; w2 >>= 2; w3 >>= 2;
						int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl5; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl5:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 6: // 4 бита на цвет (16 цветов, включены слои)
		{
			// со слоями
			int nHeight = 768;
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			const uint32_t *pPaletteIdx2 = &m_azPalette[AZ_256_COLOR_IDX + 16]; // начало палитры
			const uint32_t *pPaletteIdx3 = &m_azPalette[AZ_256_COLOR_IDX + 32]; // начало палитры
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pDataL0 = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines); // верхний слой
				const uint8_t *pDataL1 = pDataBase_L1 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177246) % m_AZData.nAZScrlLines); // средний слой
				const uint8_t *pDataL2 = pDataBase_L2 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177244) % m_AZData.nAZScrlLines); // нижний слой
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idxL0 = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				int idxL1 = m_AZRegs.nHSCRL177254 % m_AZData.nAZLineLen; // текущий индекс
				int idxL2 = m_AZRegs.nHSCRL177256 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					uint8_t w = pDataL0[idxL0]; // берём очередной байт
					uint8_t w2 = pDataL1[idxL1]; // берём очередной байт
					uint8_t w3 = pDataL2[idxL2]; // берём очередной байт
					idxL0 = ++idxL0 % m_AZData.nAZLineLen;
					idxL1 = ++idxL1 % m_AZData.nAZLineLen;
					idxL2 = ++idxL2 % m_AZData.nAZLineLen;

					for (int i = 2; i > 0; i--)    // 2 четырёхбития
					{
						const size_t p = w & 0xf;
						const size_t p2 = w2 & 0xf;
						const size_t p3 = w3 & 0xf;
						const uint32_t px = p ? *(pPaletteIdx + p) : (p2 ? * (pPaletteIdx2 + p2) : * (pPaletteIdx3 + p3));
						w >>= 4; w2 >>= 4; w3 >>= 4;
						int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl6; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl6:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 7: // 8 бит на цвет (256 цветов, включены слои)
		{
			// со слоями
			int nHeight = 768;
			const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				int nWidth = 1024;         // фиксированная реальная ширина в точках
				const uint8_t *pDataL0 = pDataBase_L0 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177250) % m_AZData.nAZScrlLines); // верхний слой
				const uint8_t *pDataL1 = pDataBase_L1 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177246) % m_AZData.nAZScrlLines); // средний слой
				const uint8_t *pDataL2 = pDataBase_L2 + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nVSCRL177244) % m_AZData.nAZScrlLines); // нижний слой
				const uint32_t *pLine = pBits;   // начало текущей линии

				int idxL0 = m_AZRegs.nHSCRL177252 % m_AZData.nAZLineLen; // текущий индекс
				int idxL1 = m_AZRegs.nHSCRL177254 % m_AZData.nAZLineLen; // текущий индекс
				int idxL2 = m_AZRegs.nHSCRL177256 % m_AZData.nAZLineLen; // текущий индекс
				for (int c = m_AZData.nAZLineLen; c > 0; c--) // длина линии в байтах
				{
					const size_t p = pDataL0[idxL0]; // берём очередной байт
					const size_t p2 = pDataL1[idxL1]; // берём очередной байт
					const size_t p3 = pDataL2[idxL2]; // берём очередной байт
					idxL0 = ++idxL0 % m_AZData.nAZLineLen;
					idxL1 = ++idxL1 % m_AZData.nAZLineLen;
					idxL2 = ++idxL2 % m_AZData.nAZLineLen;

					const uint32_t px = p ? *(pPaletteIdx + p) : (p2 ? * (pPaletteIdx + p2) : * (pPaletteIdx + p3));
					int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

					do
					{
						*pBits++ = px;  // рисуем точку

						if (!(--nWidth))
						{
							goto l_endl7; // если нарисовали всю строку, то хватит
						}
					}
					while (--sx);
				}

l_endl7:
				--nHeight; nLinesCount++;
				int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;
	}
}


void CAZBK::Stop50НzThread()
{
	if (m_lock50НzThr.IsLocked()) // если поток запущен
	{
		m_lock50Нz.Lock();

		do
		{
			SleepEx(10, FALSE);
		}
		while (m_lock50НzThr.IsLocked());
	}
}

bool CAZBK::Start50НzThread()
{
	if (!m_lock50НzThr.IsLocked()) // если поток ещё не запущен
	{
		m_50НzThread = std::thread(&CAZBK::Clk50НzThreadFunc, this);

		if (m_50НzThread.joinable())
		{
			m_50НzThread.detach();
			return true;
		}
	}

	return false;
}

void CAZBK::Clk50НzThreadFunc() const
{
	DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 1);
	LARGE_INTEGER liDueTime{};    //интервал таймера
	liDueTime.QuadPart = -10000000LL / 48;
	// создаём waitable timer.
	HANDLE hWaitableTimer = ::CreateWaitableTimer(nullptr, FALSE, nullptr);

	if (hWaitableTimer)
	{
		m_lock50НzThr.Lock();

		do
		{
			// Устанавливаем таймер.
			::SetWaitableTimer(hWaitableTimer, &liDueTime, 0, nullptr, nullptr, FALSE);
			// ждём срабатывания таймера.
			::WaitForSingleObject(hWaitableTimer, INFINITE);

			// если включен таймер и режим 50Гц - делаем прерывание
			if ((m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER)) == (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER))
			{
				g_Platform.GetCPUPtr()->TickIRQ2();
			}
		}
		while (!m_lock50Нz.IsLocked());       //пока не придёт событие остановки

		::CancelWaitableTimer(hWaitableTimer);
		::CloseHandle(hWaitableTimer);
		m_lock50Нz.UnLock(); //выставляем событие, что таймер остановился, и можно продолжать остальные действия по остановке
		m_lock50НzThr.UnLock();
	}

	::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
}
