// Screen.cpp: файл реализации
//

#include "stdafx.h"
#include "Screen.h"
#include "ScreenColors_Shared.h"

#undef BKSCRDLL_EXPORTS

#include "BKScreenD2D\BKScreenD2D.h"
#include "BKScreenD3D\BKScreenD3D.h"
#include "BKScreenDIB\BKScreenDIB.h"
#include "BKScreenOGL\BKScreenOGL.h"

#include "BKErrMsgSys.h"

const int CScreen::TEXTURE_WIDTH  = (SCREEN_WIDTH  *TEXTURE_MULTIPLER_X);
const int CScreen::TEXTURE_HEIGHT = (SCREEN_HEIGHT *TEXTURE_MULTIPLER_Y);

// const double CScreen::BK_ASPECT_RATIO = (1.0);
// const int CScreen::BK_SCREEN_HEIGHT = 512;
// const int CScreen::BK_SCREEN_WIDTH = (int)(BK_SCREEN_HEIGHT * BK_ASPECT_RATIO + 1);
const double CScreen::BK_ASPECT_RATIO  = (4.0 / 3.0);
const int CScreen::BK_SCREEN_WIDTH = (1024);
const int CScreen::BK_SCREEN_HEIGHT = (int)(BK_SCREEN_WIDTH / BK_ASPECT_RATIO);

// CScreen

IMPLEMENT_DYNAMIC(CScreen, CWnd)

CScreen::CScreen(int nRenderType)
	: m_pBuffer(nullptr)
	, m_nBufSize(0)
	, m_pscrSharedFunc(nullptr)
	, m_hModule(nullptr)
	, m_pColTable32(nullptr)
	, m_pMonoTable32(nullptr)
	, m_bColorMode(true)
	, m_bAdapt(false)
	, m_bExtended(false)
	, m_bLuminoforeEmul(false)
	, m_nOfs(0330)
	, m_nPaletteNum(0)
	, m_nFrame(0)
	, m_nCurFPS(0)
{
	InitVars(nRenderType);
}

CScreen::CScreen(int nRenderType, uint8_t *buffer, size_t size)
	: m_pBuffer(buffer)
	, m_nBufSize(size)
	, m_pscrSharedFunc(nullptr)
	, m_hModule(nullptr)
	, m_pColTable32(nullptr)
	, m_pMonoTable32(nullptr)
	, m_bColorMode(true)
	, m_bAdapt(false)
	, m_bExtended(false)
	, m_bLuminoforeEmul(false)
	, m_nOfs(0330)
	, m_nPaletteNum(0)
	, m_nFrame(0)
	, m_nCurFPS(0)
{
	InitVars(nRenderType);
}
#ifdef _WIN64
#define BK_OGLDLLNAME _T("BKScreenOGL_x64.dll")
#define BK_D2DDLLNAME _T("BKScreenD2D_x64.dll")
#define BK_DIBDLLNAME _T("BKScreenDIB_x64.dll")
#define BK_D3DDLLNAME _T("BKScreenD3D_x64.dll")
#else
#define BK_OGLDLLNAME _T("BKScreenOGL.dll")
#define BK_D2DDLLNAME _T("BKScreenD2D.dll")
#define BK_DIBDLLNAME _T("BKScreenDIB.dll")
#define BK_D3DDLLNAME _T("BKScreenD3D.dll")
#endif

#define BK_SCRDLLFUNC "GetBKScreen"

void CScreen::InitVars(int nRenderType)
{
	switch (nRenderType)//Берём номер из конфига. 0 - OGL, 1 - D2D, 2 - DIB, 3 - D3D (глючный для WinXP)
	{
		case CONF_SCREEN_RENDER_OGL:
			m_strDllName = BK_OGLDLLNAME;
			break;

		case CONF_SCREEN_RENDER_D2D:
			m_strDllName = BK_D2DDLLNAME;
			break;

		default:
		case CONF_SCREEN_RENDER_DIB:
			m_strDllName = BK_DIBDLLNAME;
			break;

		case CONF_SCREEN_RENDER_D3D:
			m_strDllName = BK_D3DDLLNAME;
			break;
	}

	m_hModule = LoadLibrary(m_strDllName);

	if (m_hModule)
	{
		auto pGetBKScr = reinterpret_cast<GETBKSCR>(GetProcAddress(m_hModule, BK_SCRDLLFUNC));

		if (pGetBKScr)
		{
			switch (nRenderType)//Берём номер из конфига. 0 - OGL, 1 - D2D, 2 - DIB, 3 - D3D (глючный для WinXP)
			{
				case CONF_SCREEN_RENDER_OGL:
					m_pscrSharedFunc = dynamic_cast<CScreenOGL *>(pGetBKScr());
					break;

				case CONF_SCREEN_RENDER_D2D:
					m_pscrSharedFunc = dynamic_cast<CScreenD2D *>(pGetBKScr());
					break;

				default:
				case CONF_SCREEN_RENDER_DIB:
					m_pscrSharedFunc = dynamic_cast<CScreenDIB *>(pGetBKScr());
					break;

				case CONF_SCREEN_RENDER_D3D:
					m_pscrSharedFunc = dynamic_cast<CScreenD3D *>(pGetBKScr());
					break;
			}
		}
		else
		{
			CString str;
			str.Format(g_pstrErrors[BK_ERROR_SCRDLLFUNCERR], _T(BK_SCRDLLFUNC));
			CBKMessageBox(str, MB_OK);
		}
	}
	else
	{
		CString str;
		str.Format(g_pstrErrors[BK_ERROR_SCRDLLINITERR], m_strDllName);
		CBKMessageBox(str, MB_OK);
	}

	if (m_pscrSharedFunc)
	{
		m_bReverseScreen = m_pscrSharedFunc->BKSS_GetReverseFlag();
	}
}

CScreen::~CScreen()
{
	ClearObjects();
}


void CScreen::OnDestroy()
{
	KillTimer(BKTIMER_SCREEN_FPS);

	//перед выходом дождёмся прекращения выполнения рисования
	while (m_lockBusy.IsLocked())
	{
		Sleep(1);
	}

	//и заблокируем его во избежание потенциальных дедлоков
	m_lockBusy.Lock();

	if (m_pscrSharedFunc)
	{
		m_pscrSharedFunc->BKSS_ScreenView_Done();
	}

	m_lockBusy.UnLock();
	CWnd::OnDestroy();
}


void CScreen::OnPaint()
{
	//охрененть, если это закомментировать, то перестают работать таймеры и вообще синхронизация
	CPaintDC dc(this); // device context for painting
	DrawScreen(true);
	// Не вызывать CWnd::OnPaint() для сообщений рисования
}

void CScreen::ClearObjects()
{
	SAFE_DELETE_ARRAY(m_pColTable32);
	SAFE_DELETE_ARRAY(m_pMonoTable32);
	SAFE_DELETE(m_pscrSharedFunc);

	if (m_hModule)
	{
		FreeLibrary(m_hModule);
		m_hModule = nullptr;
	}
}

BEGIN_MESSAGE_MAP(CScreen, CWnd)
	ON_WM_CREATE()
	ON_WM_SIZE()
	ON_WM_ERASEBKGND()
	ON_WM_DESTROY()
	ON_WM_PAINT()
	ON_WM_TIMER()
END_MESSAGE_MAP()


// обработчики сообщений CScreen
BOOL CScreen::PreCreateWindow(CREATESTRUCT &cs)
{
	m_pwndParent = FromHandle(cs.hwndParent);
	cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_SAVEBITS,
	                                   ::LoadCursor(nullptr, IDC_ARROW), HBRUSH(::GetStockObject(BLACK_BRUSH)), nullptr);
	return CWnd::PreCreateWindow(cs);
}

int CScreen::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
	{
		return -1;
	}

	if (m_pscrSharedFunc == nullptr)
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_SCRDLLFUNCPTRERR], MB_OK);
		return -1;
	}

	if (!InitColorTables())
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_SCRCOLORTABLEERR], MB_OK);
		return -1;
	}

	SetTimer(BKTIMER_SCREEN_FPS, 1000, nullptr);
	//заполняем параметры экрана.
	ScrsParams scrParam;
	scrParam.nTxX = TEXTURE_WIDTH;
	scrParam.nTxY = TEXTURE_HEIGHT;
	//рассчитаем размеры окна полноэкранного режима
	int wx, wy, dx, dy;
	HDC dc = ::GetDC(nullptr);
	scrParam.rcFSDim.left = 0;
	scrParam.rcFSDim.top =  0;
	dx = scrParam.rcFSDim.right = GetDeviceCaps(dc, HORZRES);   //ширина экрана
	dy = scrParam.rcFSDim.bottom = GetDeviceCaps(dc, VERTRES);  //высота экрана
	wx = int(double(dy) * BK_ASPECT_RATIO);  //ширина экрана при высоте dy в пропорциях 4/3
	wy = int(double(dx) / BK_ASPECT_RATIO);  //высота экрана при ширине dx в пропорциях 4/3
	::ReleaseDC(nullptr, dc);

	//рассчитаем размеры рисуемой картинки в полноэкранном режиме
	if (dx <= dy) //если монитор повёрнут на 90 градусов, или нестандартный - квадратный
	{
		if (dx < wx) //если не влазит по ширине, вписываем в ширину
		{
			wy = dy;
			wx = int(double(wy) * BK_ASPECT_RATIO);
		}
		else
		{
			//то вписываем картинку в высоту
			wx = dx;
			wy = int(double(wx) / BK_ASPECT_RATIO);
		}
	}
	else    //если монитор в обычном положении
	{
		if (dx < wx) //если не влазит по ширине, вписываем в ширину
		{
			wx = dx;
			wy = int(double(wx) / BK_ASPECT_RATIO);
		}
		else
		{
			//то вписываем картинку в высоту
			wy = dy;
			wx = int(double(wy) * BK_ASPECT_RATIO);
		}
	}

	scrParam.rcFSViewPort.left = (dx - wx) / 2;   //выравниваем по центру
	scrParam.rcFSViewPort.top = (dy - wy) / 2;
	scrParam.rcFSViewPort.right = wx;
	scrParam.rcFSViewPort.bottom = wy;
	scrParam.pBits = nullptr;

	if (SUCCEEDED(m_pscrSharedFunc->BKSS_ScreenView_Init(scrParam, this)))
	{
		m_pTexBits = scrParam.pBits;
		memset(m_pTexBits, Color_Black, (TEXTURE_HEIGHT * TEXTURE_WIDTH)* sizeof(uint32_t));
		return true;
	}
	else
	{
		CString str;
		str.Format(g_pstrErrors[BK_ERROR_SCRDLLINITERR], m_strDllName);
		CBKMessageBox(str, MB_OK);
		m_pscrSharedFunc->BKSS_ScreenView_Done();
		ClearObjects();
		return -1;
	}
}

void CScreen::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	m_pscrSharedFunc->BKSS_OnSize(cx, cy);
}


BOOL CScreen::OnEraseBkgnd(CDC *pDC)
{
	return TRUE;
}

BOOL CScreen::PreTranslateMessage(MSG *pMsg)
{
	switch (pMsg->message)
	{
		// транслируем сообщения клавиатуры в CBKView.
		// это крайне необходимо для рендера D3D11, без этого в полноэкранном режиме никак
		// клавиатуру не обработать.
		case WM_KEYDOWN:
		case WM_KEYUP:
		case WM_SYSKEYDOWN:
		case WM_SYSKEYUP:
			TRACE("CScreen translate message to CBKView\n");
			m_pwndParent->PostMessage(pMsg->message, pMsg->wParam, pMsg->lParam);
			return TRUE;
	}

	return CWnd::PreTranslateMessage(pMsg);
}

void CScreen::DrawScreen(bool bCheckFPS)
{
	if (m_pTexBits == nullptr || m_lockChangeMode.IsLocked() || m_lockBusy.IsLocked())
	{
		return;
	}

	m_lockBusy.Lock();
	m_pscrSharedFunc->BKSS_DrawScreen();
	m_lockBusy.UnLock();

	if (bCheckFPS)
	{
		m_mutFPS.lock();
		// Посчитаем FPS
		m_nFrame++;
		m_mutFPS.unlock();
	}
}

void CScreen::OnTimer(UINT_PTR nIDEvent)
{
	if (nIDEvent == BKTIMER_SCREEN_FPS)
	{
		m_mutFPS.lock();
		m_nCurFPS = m_nFrame;
		m_nFrame = 0;
		m_mutFPS.unlock();
	}
}

bool CScreen::SetFullScreenMode()
{
	while (m_lockBusy.IsLocked() || m_lockPrepare.IsLocked())
	{
		Sleep(1);    //если выполняется отрисовка, то подождём, пока процедуры не завершатся
	}

	m_lockChangeMode.Lock();
	bool bRet = m_pscrSharedFunc->BKSS_SetFullScreenMode();
	m_lockChangeMode.UnLock();
	return bRet;
}

bool CScreen::SetWindowMode()
{
	while (m_lockBusy.IsLocked() || m_lockPrepare.IsLocked())
	{
		Sleep(1);    //если выполняется отрисовка, то подождём, пока процедуры не завершатся
	}

	m_lockChangeMode.Lock();
	bool bRet = m_pscrSharedFunc->BKSS_SetWindowMode();
	m_lockChangeMode.UnLock();
	AdjustLayout();
	return bRet;
}

bool CScreen::IsFullScreenMode()
{
	return m_pscrSharedFunc->BKSS_IsFullScreenMode();
}

HBITMAP CScreen::GetScreenshot()
{
	if (!m_bReverseScreen)
	{
		//для DIBDraw надо перевернуть экран, тупо не знаю, как это сделать с помощью готовых средств.
		//поэтому - вручную.
		auto pNewBits = new uint32_t[TEXTURE_WIDTH * TEXTURE_HEIGHT];
		uint32_t *pBits = m_pTexBits + (TEXTURE_WIDTH * TEXTURE_HEIGHT);
		uint32_t *pBitd = pNewBits;

		for (int y = 0; y < TEXTURE_HEIGHT; ++y)
		{
			pBits -= TEXTURE_WIDTH;
			memcpy(pBitd, pBits, TEXTURE_WIDTH * sizeof(uint32_t));
			pBitd += TEXTURE_WIDTH;
		}

		HBITMAP hBm = CreateBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT, 1, 32, pNewBits);
		delete [] pNewBits;
		return hBm;
	}
	else
	{
		HBITMAP hBm = CreateBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT, 1, 32, m_pTexBits);
		return hBm;
	}
}

void CScreen::SetColorMode(bool bColorMode)
{
	m_bColorMode = bColorMode;
	m_pscrSharedFunc->BKSS_SetColorMode();
}
//этот алгоритм применяется только в режиме отладки, когда нужно принудительно перерисовывать весь экран
//и в карте памяти
void CScreen::PrepareScreenRGB32(uint8_t *ScreenBuffer)
{
	if (m_pTexBits == nullptr || m_lockChangeMode.IsLocked())
	{
		return;
	}

	m_lockPrepare.Lock();
	register uint8_t scroll = (m_nOfs - 0330) & 0377;
	register uint8_t *pVideo;
	register uint32_t *pBits, *pBitp, *pPalette;
	// Render to bitmap
	register uint32_t *pCurPalette = (m_bColorMode) ? m_pColTable32 : m_pMonoTable32;
	register int linesToShow = m_bExtended ? SCREEN_HEIGHT / 4 : SCREEN_HEIGHT;

	for (register int y = 0; y < linesToShow; ++y)
	{
		register uint8_t yy = (y + scroll) & 0377;
		//указатель на начало строки
		pBitp = pBits = m_pTexBits + ((m_bReverseScreen ? y : (255 - y)) * TEXTURE_WIDTH * TEXTURE_MULTIPLER_Y);
		pVideo = ScreenBuffer + (yy * 0100);

		for (register uint8_t x = 0; x < SCREEN_WIDTH / 8; ++x) //рисуем побайтно
		{
			pPalette = pCurPalette + (((m_nPaletteNum << 8) + *pVideo++) << 3);

			//один байт - это 8 бит
			for (register uint8_t i = 0; i < 8; ++i)
			{
				//каждый бит надо повторить TEXTURE_MULTIPLER_X раз подряд.
				for (register uint8_t u = 0; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*pBits++ = *pPalette; //один бит - один дворд.
				}

				pPalette++;
			}
		}

		//теперь pBits - указатель на начало новой строки, а pBitp - указатель на начало предыдущей строки
		//скопируем строку столько раз, сколько надо будет
		for (register uint8_t v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
		{
			memcpy(pBits, pBitp, TEXTURE_WIDTH * sizeof(uint32_t));
			pBits += TEXTURE_WIDTH;
			pBitp += TEXTURE_WIDTH;
		}
	}

	if (m_bExtended)
	{
		if (m_bReverseScreen)
		{
			//Для всех обнулять нужно область с 1/4 массива экрана до конца массива
			memset(m_pTexBits + (TEXTURE_WIDTH * TEXTURE_HEIGHT / 4), Color_Black, (TEXTURE_HEIGHT - TEXTURE_HEIGHT / 4) * TEXTURE_WIDTH * sizeof(uint32_t));
		}
		else
		{
			//Для DrawDIB обнулять нужно область с начала массива экрана до 3/4 массива экрана
			memset(m_pTexBits, Color_Black, (TEXTURE_HEIGHT - TEXTURE_HEIGHT / 4) * TEXTURE_WIDTH * sizeof(uint32_t));
		}
	}

	m_lockPrepare.UnLock();
}

#define LUMINOFORE_COEFF 3 / 8
//#define LUMINOFORE_COEFF 2 / 3

//Это старая функция, сейчас не используется.
//Но если что-то надо будет изменить, тренироваться будем на ней, а потом копипастить в PrepareScreenLineWordRGB32
//т.к. та функция - просто дважды повторённая эта.
//на входе у обеих функций:
//nLineNum - номер строки 0..255
//nByteNum - смещение байта/слова в строке относительно начала строки.
void CScreen::PrepareScreenLineByteRGB32(int nLineNum, int nByteNum, uint8_t b)
{
	if (m_pTexBits == nullptr || m_lockChangeMode.IsLocked())
	{
		return;
	}

	m_lockPrepare.Lock();
	register uint8_t nScreenLine = nLineNum & 0377;
	register uint8_t yy = 255 - nScreenLine;

	if (m_bReverseScreen)
	{
		yy = nScreenLine;
	}

	register uint32_t *pBits = m_pTexBits + (yy * TEXTURE_MULTIPLER_Y * TEXTURE_WIDTH + nByteNum * 8 * TEXTURE_MULTIPLER_X);
	register uint32_t *pBitp = pBits; //указатель на начало строки
	register uint32_t *pCurPalette = (m_bColorMode) ? m_pColTable32 : m_pMonoTable32;

	if (m_bExtended && nScreenLine >= (SCREEN_HEIGHT / 4))
	{
		//обнуляем
		if (m_bLuminoforeEmul)
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				register auto c = reinterpret_cast<BCOLOR *>(pBits);
				//blue - 0, green - 1, red - 2, alpha - 3;
				c->bb[0] = LOBYTE(int(c->bb[0]) * LUMINOFORE_COEFF);
				c->bb[1] = LOBYTE(int(c->bb[1]) * LUMINOFORE_COEFF);
				c->bb[2] = LOBYTE(int(c->bb[2]) * LUMINOFORE_COEFF);

				for (register uint8_t u = 1; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*(pBits + 1) = *pBits++;
				}

				pBits++;
			}
		}
		else
		{
			memset(pBits, 0, TEXTURE_MULTIPLER_X * 8 * sizeof(uint32_t));
			pBits += TEXTURE_MULTIPLER_X * 8 * sizeof(uint32_t);
		}
	}
	else
	{
		//копируем 1 байт
		register uint32_t *pPalette = pCurPalette + (((m_nPaletteNum << 8) + b) << 3);

		if (m_bLuminoforeEmul)
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				// эмуляция затухания люминофора. Как-то не очень хорошо.
				// не удаётся подобрать вариант, чтобы и шлейф не мешал
				// и мерцания не было.
				// наверное люминофор гаснет нелинейно.
				register auto c = reinterpret_cast<BCOLOR *>(pBits);
				register auto p = reinterpret_cast<BCOLOR *>(pPalette);
				//blue - 0, green - 1, red - 2, alpha - 3;
				c->bb[0] = p->bb[0] ? p->bb[0] : LOBYTE(int(c->bb[0]) * LUMINOFORE_COEFF);
				c->bb[1] = p->bb[1] ? p->bb[1] : LOBYTE(int(c->bb[1]) * LUMINOFORE_COEFF);
				c->bb[2] = p->bb[2] ? p->bb[2] : LOBYTE(int(c->bb[2]) * LUMINOFORE_COEFF);
				c->bb[3] = p->bb[3];

				for (register uint8_t u = 1; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*(pBits + 1) = *pBits++;
				}

				pBits++;
				pPalette++;
			}
		}
		else
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				for (register uint8_t u = 0; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*pBits++ = *pPalette;
				}

				pPalette++;
			}
		}
	}

	pBits = pBitp + TEXTURE_WIDTH;

	//размножим по вертикали
	for (register uint8_t v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
	{
		memcpy(pBits, pBitp, 8 * TEXTURE_MULTIPLER_X * sizeof(uint32_t));
		pBits += TEXTURE_WIDTH;
		pBitp += TEXTURE_WIDTH;
	}

	m_lockPrepare.UnLock();
}

void CScreen::PrepareScreenLineWordRGB32(int nLineNum, int nByteNum, uint16_t w)
{
	//!много копипасты. если что-то менять, не забыть поменять в трёх местах
	if (m_pTexBits == nullptr || m_lockChangeMode.IsLocked())
	{
		return;
	}

	m_lockPrepare.Lock();
	//первый байт
	uint8_t b = LOBYTE(w);
	register uint8_t nScreenLine = nLineNum & 0377;
	register uint8_t yy = 255 - nScreenLine;

	if (m_bReverseScreen)
	{
		yy = nScreenLine;
	}

	register uint32_t *pBits_A = m_pTexBits + (yy * TEXTURE_MULTIPLER_Y * TEXTURE_WIDTH + nByteNum * 8 * TEXTURE_MULTIPLER_X);
	register uint32_t *pBits = pBits_A;
	register uint32_t *pBitp = pBits_A; //указатель на начало строки
	register uint32_t *pCurPalette = (m_bColorMode) ? m_pColTable32 : m_pMonoTable32;

	if (m_bExtended && nScreenLine >= (SCREEN_HEIGHT / 4))
	{
		//обнуляем
		if (m_bLuminoforeEmul)
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				register auto c = reinterpret_cast<BCOLOR *>(pBits);
				//blue - 0, green - 1, red - 2, alpha - 3;
				c->bb[0] = LOBYTE(int(c->bb[0]) * LUMINOFORE_COEFF);
				c->bb[1] = LOBYTE(int(c->bb[1]) * LUMINOFORE_COEFF);
				c->bb[2] = LOBYTE(int(c->bb[2]) * LUMINOFORE_COEFF);

				for (register uint8_t u = 1; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*(pBits + 1) = *pBits++;
				}

				pBits++;
			}
		}
		else
		{
			memset(pBits, 0, TEXTURE_MULTIPLER_X * 8 * sizeof(uint32_t));
			pBits += TEXTURE_MULTIPLER_X * 8 * sizeof(uint32_t);
		}
	}
	else
	{
		//копируем 1 байт
		register uint32_t *pPalette = pCurPalette + (((m_nPaletteNum << 8) + b) << 3);

		if (m_bLuminoforeEmul)
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				// эмуляция затухания люминофора. Как-то не очень хорошо.
				// не удаётся подобрать вариант, чтобы и шлейф не мешал
				// и мерцания не было.
				// наверное люминофор гаснет нелинейно.
				register auto c = reinterpret_cast<BCOLOR *>(pBits);
				register auto p = reinterpret_cast<BCOLOR *>(pPalette);
				//blue - 0, green - 1, red - 2, alpha - 3;
				c->bb[0] = p->bb[0] ? p->bb[0] : LOBYTE(int(c->bb[0]) * LUMINOFORE_COEFF);
				c->bb[1] = p->bb[1] ? p->bb[1] : LOBYTE(int(c->bb[1]) * LUMINOFORE_COEFF);
				c->bb[2] = p->bb[2] ? p->bb[2] : LOBYTE(int(c->bb[2]) * LUMINOFORE_COEFF);
				c->bb[3] = p->bb[3];

				for (register uint8_t u = 1; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*(pBits + 1) = *pBits++;
				}

				pBits++;
				pPalette++;
			}
		}
		else
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				for (register uint8_t u = 0; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*pBits++ = *pPalette;
				}

				pPalette++;
			}
		}
	}

	pBits = pBitp + TEXTURE_WIDTH;

	//размножим по вертикали
	for (register uint8_t v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
	{
		memcpy(pBits, pBitp, 8 * TEXTURE_MULTIPLER_X * sizeof(uint32_t));
		pBits += TEXTURE_WIDTH;
		pBitp += TEXTURE_WIDTH;
	}

	//копипаста второй байт
	b = HIBYTE(w);
	pBits = pBitp = pBits_A + 8 * TEXTURE_MULTIPLER_X;

	if (m_bExtended && nScreenLine >= (SCREEN_HEIGHT / 4))
	{
		//обнуляем
		if (m_bLuminoforeEmul)
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				register auto c = reinterpret_cast<BCOLOR *>(pBits);
				//blue - 0, green - 1, red - 2, alpha - 3;
				c->bb[0] = LOBYTE(int(c->bb[0]) * LUMINOFORE_COEFF);
				c->bb[1] = LOBYTE(int(c->bb[1]) * LUMINOFORE_COEFF);
				c->bb[2] = LOBYTE(int(c->bb[2]) * LUMINOFORE_COEFF);

				for (register uint8_t u = 1; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*(pBits + 1) = *pBits++;
				}

				pBits++;
			}
		}
		else
		{
			memset(pBits, 0, TEXTURE_MULTIPLER_X * 8 * sizeof(uint32_t));
			pBits += TEXTURE_MULTIPLER_X * 8 * sizeof(uint32_t);
		}
	}
	else
	{
		//копируем 1 байт
		register uint32_t *pPalette = pCurPalette + (((m_nPaletteNum << 8) + b) << 3);

		if (m_bLuminoforeEmul)
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				// эмуляция затухания люминофора. Как-то не очень хорошо.
				// не удаётся подобрать вариант, чтобы и шлейф не мешал
				// и мерцания не было.
				// наверное люминофор гаснет нелинейно.
				register auto c = reinterpret_cast<BCOLOR *>(pBits);
				register auto p = reinterpret_cast<BCOLOR *>(pPalette);
				//blue - 0, green - 1, red - 2, alpha - 3;
				c->bb[0] = p->bb[0] ? p->bb[0] : LOBYTE(int(c->bb[0]) * LUMINOFORE_COEFF);
				c->bb[1] = p->bb[1] ? p->bb[1] : LOBYTE(int(c->bb[1]) * LUMINOFORE_COEFF);
				c->bb[2] = p->bb[2] ? p->bb[2] : LOBYTE(int(c->bb[2]) * LUMINOFORE_COEFF);
				c->bb[3] = p->bb[3];

				for (register uint8_t u = 1; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*(pBits + 1) = *pBits++;
				}

				pBits++;
				pPalette++;
			}
		}
		else
		{
			for (register uint8_t i = 0; i < 8; ++i)
			{
				for (register uint8_t u = 0; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*pBits++ = *pPalette;
				}

				pPalette++;
			}
		}
	}

	pBits = pBitp + TEXTURE_WIDTH;

	//размножим по вертикали
	for (register uint8_t v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
	{
		memcpy(pBits, pBitp, 8 * TEXTURE_MULTIPLER_X * sizeof(uint32_t));
		pBits += TEXTURE_WIDTH;
		pBitp += TEXTURE_WIDTH;
	}

#ifdef _DEBUG
	//////////////////////////////////////////////////////////////////////////
	//Отладочный видимый ход луча
	//Нужно только при отладке экрана, включать только при необходимости.
	//в релизной версии принудительно отключим.
	//PrepareScreenLineByte_Debug(nLineNum, nByteNum);
	//////////////////////////////////////////////////////////////////////////
#endif
	m_lockPrepare.UnLock();
}
#ifdef _DEBUG
//отладочная трассировка луча.
//ставится красная точка на следующем месте, где будет луч.
void CScreen::PrepareScreenLineByte_Debug(int nLineNum, int nByteNum)
{
	nByteNum += 2;

	if (nByteNum >= 0100)
	{
		nByteNum = 0;
		nLineNum++;
		nLineNum &= 0377;
	}

	register uint8_t nScreenLine = nLineNum & 0377;
	register uint8_t yy = 255 - nScreenLine;

	if (m_bReverseScreen)
	{
		yy = nScreenLine;
	}

	register uint32_t *pBits = m_pTexBits + (yy * TEXTURE_MULTIPLER_Y * TEXTURE_WIDTH + nByteNum * 8 * TEXTURE_MULTIPLER_X);
	register uint32_t *pBitp = pBits; //указатель на начало строки
	//копируем 1 байт (ставим красную точку)
	//красную не всегда видно, может что-то ещё надо будет придумать. может ксорить.
	register uint32_t *pPalette = m_pColTable32 + (3 * 8);

	for (register uint8_t i = 0; i < 2; ++i)
	{
		for (register uint8_t u = 0; u < TEXTURE_MULTIPLER_X; ++u)
		{
			*pBits++ = *pPalette;
		}

		pPalette++;
	}

	pBits = pBitp + TEXTURE_WIDTH;

	//размножим по вертикали
	for (register uint8_t v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
	{
		memcpy(pBits, pBitp, 8 * TEXTURE_MULTIPLER_X * sizeof(uint32_t));
		pBits += TEXTURE_WIDTH;
		pBitp += TEXTURE_WIDTH;
	}
}
#endif


constexpr auto COLTABLE_SIZE = (16 * 256 * 8);
//16 палитр, 256 вариантов значений байта, на каждый вариант - 8 uint32_tов,

bool CScreen::InitColorTables()
{
	bool bRet = false;

	while (m_lockPrepare.IsLocked())   //если выполняется подготовка, то подождём, пока процедуры не завершатся
	{
		Sleep(1);
	}

	m_lockChangeMode.Lock();
	// Create color tables of all display modes for faster drawing
	SAFE_DELETE_ARRAY(m_pColTable32);
	SAFE_DELETE_ARRAY(m_pMonoTable32);
	m_pColTable32 = new uint32_t[COLTABLE_SIZE];
	m_pMonoTable32 = new uint32_t[COLTABLE_SIZE];

	if ((m_pColTable32 != nullptr) && (m_pMonoTable32 != nullptr))
	{
		for (int p = 0; p < 16; ++p)   //для каждой из 16 палитр
		{
			//вот это делает одну палитру. а нам надо таких 16.
			for (int i = 0; i < 256; ++i)   //для каждого значения байта (от 0 до 255) делаем маску, чтоб работать сразу с байтами и не заморачиваться с битами
			{
				uint32_t *pColBuff32 = &m_pColTable32[p * (256 * 8) + i * 8];
				uint32_t *pMonBuff32 = &m_pMonoTable32[p * (256 * 8) + i * 8];

				for (int n = 0; n < 4; ++n)   //в каждом байте 4 двухбития, на каждые два бита, у нас отводится по 4 дворда
				{
					int n2 = n * 2;

					switch ((i >> n2) & 3)
					{
						// Black
						case 0:
							pColBuff32[n2] = g_pColorPalettes[p][0];
							pMonBuff32[n2++] = (m_bAdapt) ? g_pAdaptMonochromePalette[0][0] : g_pMonochromePalette[0][0];
							pColBuff32[n2] = g_pColorPalettes[p][0];
							pMonBuff32[n2] = (m_bAdapt) ? g_pAdaptMonochromePalette[1][0] : g_pMonochromePalette[1][0];
							break;

						// Blue and Gray
						case 1:
							pColBuff32[n2] = g_pColorPalettes[p][1];
							pMonBuff32[n2++] = (m_bAdapt) ? g_pAdaptMonochromePalette[0][1] : g_pMonochromePalette[0][1];
							pColBuff32[n2] = g_pColorPalettes[p][1];
							pMonBuff32[n2] = (m_bAdapt) ? g_pAdaptMonochromePalette[1][1] : g_pMonochromePalette[1][1];
							break;

						// Green and Gray
						case 2:
							pColBuff32[n2] = g_pColorPalettes[p][2];
							pMonBuff32[n2++] = (m_bAdapt) ? g_pAdaptMonochromePalette[0][2] : g_pMonochromePalette[0][2];
							pColBuff32[n2] = g_pColorPalettes[p][2];
							pMonBuff32[n2] = (m_bAdapt) ? g_pAdaptMonochromePalette[1][2] : g_pMonochromePalette[1][2];
							break;

						// Red
						case 3:
							pColBuff32[n2] = g_pColorPalettes[p][3];
							pMonBuff32[n2++] = (m_bAdapt) ? g_pAdaptMonochromePalette[0][3] : g_pMonochromePalette[0][3];
							pColBuff32[n2] = g_pColorPalettes[p][3];
							pMonBuff32[n2] = (m_bAdapt) ? g_pAdaptMonochromePalette[1][3] : g_pMonochromePalette[1][3];
							break;
					}
				}
			} //end for(i)
		} //end for(p)

		bRet = true;
	}
	else
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
	}

	m_lockChangeMode.UnLock();
	return bRet;
}

void CScreen::AdjustLayout()
{
	if (m_pwndParent == nullptr)
	{
		return;
	}

	while (m_lockChangeMode.IsLocked() || m_lockBusy.IsLocked())
	{
		Sleep(1);    //если выполняется отрисовка, то подождём, пока процедуры не завершатся
	}

	m_lockChangeMode.Lock();
	CRect rcScreen(0, 0, BK_SCREEN_WIDTH, BK_SCREEN_HEIGHT);
	CRect rcNewScreen = rcScreen;
	CRect rcClient;
	m_pwndParent->GetClientRect(&rcClient);

	if (rcScreen.Width() * rcClient.Height() > rcClient.Width() * rcScreen.Height())
	{
		rcNewScreen.right = rcClient.Width();
		rcNewScreen.bottom = rcScreen.bottom * rcClient.Width() / rcScreen.right;
	}
	else
	{
		rcNewScreen.bottom = rcClient.Height();
		rcNewScreen.right = rcScreen.right * rcClient.Height() / rcScreen.bottom;
	}

	if (rcNewScreen.Width() > BK_SCREEN_WIDTH)
	{
		rcNewScreen.right = BK_SCREEN_WIDTH;
	}

	if (rcNewScreen.Height() > BK_SCREEN_HEIGHT)
	{
		rcNewScreen.bottom = BK_SCREEN_HEIGHT;
	}

	int x_offs = (rcClient.Width() - rcNewScreen.Width()) / 2;
	int y_offs = (rcClient.Height() - rcNewScreen.Height()) / 2;
	rcClient.right = rcClient.left + rcNewScreen.Width();
	rcClient.bottom = rcClient.top + rcNewScreen.Height();
	rcClient.OffsetRect(x_offs, y_offs);
	SetWindowPos(nullptr, rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), SWP_NOZORDER);
	m_lockChangeMode.UnLock();
}


