Jump to content

Recommended Posts

  • Honorable Member
This is the hidden content, please

Alternative download links →

This is the hidden content, please

 

 

Requirements:

Spoiler

DirectX 9+

 

Warning:

Spoiler

After CShaderManager::Begin is called, be careful not to call CShaderManager::Begin again before CShaderManager::End is called.

 

Edited by Mali
Added metin2.download link
  • Metin2 Dev 135
  • Scream 1
  • Good 10
  • Love 2
  • Love 46

 

Link to comment
https://metin2.dev/topic/34068-shader-manager-post-process/
Share on other sites

spacer.png

Works well, thank you 🙂 However, when hitting stones / mobs and rotating the camera around the whole screen (where the post process applies) turns completely white, then back, then white again, then back etc etc. Any idea?

edit: its related to the effects (hitting/spawning mobs from the stone etc), because if i turn all the effects off, the issue disappears, works completely.

https://metin2.download/video/3UUb2XQIiMWGOZtVAy4ZiZdkyls5LN5Q/.mp4

Edited by Metin2 Dev International
Core X - External 2 Internal
  • Think 1

🖥️ SysAdmin — Government (HU)
🛠️ Freelance Metin2 Dev • DevOps
🐧 FreeBSD / Linux | 🛡️ Security & WAF | 🚀 Performance | 🔥 Firewalls | ⚙ Automation
Open to work - Contact me on Discord @matteo_r

  • Honorable Member
46 minutes ago, Matteo said:

spacer.png

Works well, thank you 🙂 However, when hitting stones / mobs and rotating the camera around the whole screen (where the post process applies) turns completely white, then back, then white again, then back etc etc. Any idea?

edit: its related to the effects (hitting/spawning mobs from the stone etc), because if i turn all the effects off, the issue disappears, works completely.

Fixed asikoo will update when he is available

Edited by Metin2 Dev International
Core X - External 2 Internal
  • Love 1

 

  • Active+ Member
12 hours ago, Matteo said:

spacer.png

Works well, thank you 🙂 However, when hitting stones / mobs and rotating the camera around the whole screen (where the post process applies) turns completely white, then back, then white again, then back etc etc. Any idea?

edit: its related to the effects (hitting/spawning mobs from the stone etc), because if i turn all the effects off, the issue disappears, works completely.

https://metin2.download/video/3UUb2XQIiMWGOZtVAy4ZiZdkyls5LN5Q/.mp4

I experienced this problem in my own project a long time ago. I don't remember the exact solution, but it was likely due to a missing backup in post-process rendering.
In particular, before calling SetFVF and SetStreamSource, these two states must be backed up and the backup must be reloaded after rendering.

Otherwise, these two are very likely to create conflicts with other Render functions in the project and are dangerous.
Since everyone's project may have different layouts, doing this backup and restore process within Render will ensure consistent operation across all other projects. 

What I said above also includes Transform backups.

Edited by Metin2 Dev International
Core X - External 2 Internal
  • Metin2 Dev 1
15 hours ago, Mali said:

Fixed asikoo will update when he is available

Thank you, it worked! 

This release opened up a lot of opportunities for simple DX9 clients aswell, a lot of graphical adjustments without needing to upgrade to higher DX versions, 3rd party libraries etc. I've already done some things with it like deathcam and a little color grading. Thank you for this release again. 👑

  • Love 1

🖥️ SysAdmin — Government (HU)
🛠️ Freelance Metin2 Dev • DevOps
🐧 FreeBSD / Linux | 🛡️ Security & WAF | 🚀 Performance | 🔥 Firewalls | ⚙ Automation
Open to work - Contact me on Discord @matteo_r

  • Honorable Member
51 minutes ago, Matteo said:

This release opened up a lot of opportunities for simple DX9 clients aswell

I'm glad you like it💙 My goal was to open a new door, so people could create and share their own shaders etc.

  • Love 1

 

I'm testing around, there is an other issue:

Launch any program on your computer as Administrator (which will display UAC), after that the client is completely frozen. (Im using Win10, confirmed on multiple PCs)

Maybe the issue is on my side at device resetting:
 

int CPythonApplication::CheckDeviceState()
{
	CGraphicDevice::EDeviceState e_deviceState = m_grpDevice.GetDeviceState();

	switch (e_deviceState)
	{
		case CGraphicDevice::DEVICESTATE_NULL:
			return DEVICE_STATE_FALSE;

		case CGraphicDevice::DEVICESTATE_BROKEN:
			return DEVICE_STATE_SKIP;

		case CGraphicDevice::DEVICESTATE_NEEDS_RESET:
			m_pyBackground.ReleaseCharacterShadowTexture();
			CRenderTargetManager::Instance().ReleaseRenderTargetTextures();
			m_PostProcessManager.ReleaseTextures();
			Trace("DEVICESTATE_NEEDS_RESET - attempting");

			if (!m_grpDevice.Reset())
				return DEVICE_STATE_SKIP;

			m_pyBackground.CreateCharacterShadowTexture();
			CRenderTargetManager::Instance().CreateRenderTargetTextures();
			m_PostProcessManager.CreateTextures();
			break;
		case CGraphicDevice::DEVICESTATE_OK: break;
		default:;
	}

	return DEVICE_STATE_OK;
}

Yes, definetly. Device resetting is failing, its spamming "Failed to reset device":
 

bool CGraphicDevice::Reset()
{
	__DestroyPDTVertexBufferList();

	HRESULT hr;
	if (FAILED(hr = ms_lpd3dDevice->Reset(&ms_d3dPresentParameter))) {
		TraceError("Failed to reset device");
		return false;
	}

	m_pStateManager->SetDefaultState();
	__CreatePDTVertexBufferList();
	return true;
}


There is this "TEXT_RENEWAL" thing in the client, thats why some functions are not like default.

Edited by Matteo

🖥️ SysAdmin — Government (HU)
🛠️ Freelance Metin2 Dev • DevOps
🐧 FreeBSD / Linux | 🛡️ Security & WAF | 🚀 Performance | 🔥 Firewalls | ⚙ Automation
Open to work - Contact me on Discord @matteo_r

  • Active+ Member
38 minutes ago, Matteo said:

I'm testing around, there is an other issue:

Launch any program on your computer as Administrator (which will display UAC), after that the client is completely frozen. (Im using Win10, confirmed on multiple PCs)

Maybe the issue is on my side at device resetting:
 

int CPythonApplication::CheckDeviceState()
{
	CGraphicDevice::EDeviceState e_deviceState = m_grpDevice.GetDeviceState();

	switch (e_deviceState)
	{
		case CGraphicDevice::DEVICESTATE_NULL:
			return DEVICE_STATE_FALSE;

		case CGraphicDevice::DEVICESTATE_BROKEN:
			return DEVICE_STATE_SKIP;

		case CGraphicDevice::DEVICESTATE_NEEDS_RESET:
			m_pyBackground.ReleaseCharacterShadowTexture();
			CRenderTargetManager::Instance().ReleaseRenderTargetTextures();
			m_PostProcessManager.ReleaseTextures();
			Trace("DEVICESTATE_NEEDS_RESET - attempting");

			if (!m_grpDevice.Reset())
				return DEVICE_STATE_SKIP;

			m_pyBackground.CreateCharacterShadowTexture();
			CRenderTargetManager::Instance().CreateRenderTargetTextures();
			m_PostProcessManager.CreateTextures();
			break;
		case CGraphicDevice::DEVICESTATE_OK: break;
		default:;
	}

	return DEVICE_STATE_OK;
}

Yes, definetly. Device resetting is failing, its spamming "Failed to reset device":
 

bool CGraphicDevice::Reset()
{
	__DestroyPDTVertexBufferList();

	HRESULT hr;
	if (FAILED(hr = ms_lpd3dDevice->Reset(&ms_d3dPresentParameter))) {
		TraceError("Failed to reset device");
		return false;
	}

	m_pStateManager->SetDefaultState();
	__CreatePDTVertexBufferList();
	return true;
}


There is this "TEXT_RENEWAL" thing in the client, thats why some functions are not like default.

Try:

void CPostProcessManager::ReleaseTextures()
{
	[...]
  
	// add
	for (const auto& elem : m_ShaderMap)
	{
		if (elem.second)
			elem.second->OnLostDevice();
	}
}

 

bool CPostProcessManager::CreateTextures()
{
	[...]
	[...]
	[...]

	// Add
	for (const auto& elem : m_ShaderMap)
	{
		if (elem.second)
			elem.second->OnResetDevice();
	}

	return true;
}

 

  • Metin2 Dev 1
  • muscle 1
45 minutes ago, Kaiser said:

Try:

void CPostProcessManager::ReleaseTextures()
{
	[...]
  
	// add
	for (const auto& elem : m_ShaderMap)
	{
		if (elem.second)
			elem.second->OnLostDevice();
	}
}

 

bool CPostProcessManager::CreateTextures()
{
	[...]
	[...]
	[...]

	// Add
	for (const auto& elem : m_ShaderMap)
	{
		if (elem.second)
			elem.second->OnResetDevice();
	}

	return true;
}

 

It did the trick thanks 😘  A little "correction", it should be:
 

elem.second->GetEffect()->OnLostDevice();

and

elem.second->GetEffect()->OnResetDevice();


Other thought: shouldn’t we also destroy m_ShaderMap in CPythonApplication::Destroy(), just like the render target and the other resources?

🖥️ SysAdmin — Government (HU)
🛠️ Freelance Metin2 Dev • DevOps
🐧 FreeBSD / Linux | 🛡️ Security & WAF | 🚀 Performance | 🔥 Firewalls | ⚙ Automation
Open to work - Contact me on Discord @matteo_r

  • Active+ Member
15 minutes ago, Matteo said:

It did the trick thanks 😘  A little "correction", it should be:
 

elem.second->GetEffect()->OnLostDevice();

and

elem.second->GetEffect()->OnResetDevice();


Other thought: shouldn’t we also destroy m_ShaderMap in CPythonApplication::Destroy(), just like the render target and the other resources?

In rare cases, a crash may or may not occur when the application is closed. This is a memory issue. To completely eliminate the possibility of such risks, it would be prudent to destroy post-processing along with all other systems within Destroy.

Add a void Destroy() function as "public" to the .h and .cpp file and:

CPostProcessManager::~CPostProcessManager()
{
  	Destroy(); // Add -- This is all that will happen here.
  
	// Move all of the following code into the newly created Destroy();
  	/*
	safe_release(m_ScreenQuad);
	ReleaseTextures();

	for (const auto& elem : m_ShaderMap)
	{
		IShader* shader = elem.second;
		delete shader;
	}
    /*
}

And:

void CPythonApplication::Destroy()
{
	[...]
  	m_PostProcessManager.Destroy(); // Must be before m_grpDevice.Destroy()!
	[...]

	m_grpDevice.Destroy(); // !!
	[...]
	[...]
}

 

There is one crash observed by our team, we know the following details:

- Only happens on Windows 11
- Only happens if in NVIDIA Control Center -> Dekstop size and position (or smth) -> Perform scaling on is DISPLAY. (On GPU, it does not crash)
- Need to trigger device reset (for example, launch CS2 or any other fullscreen game / any fullscreen application which is not your native res, or restart your VGA driver)
- Then it "Failed to reset device" and the client freezes out (Error code: 0x8876086C - D3DERR_INVALIDCALL: The method call is invalid)
- Its 100% the shader thing, its not getting crashed when its defined out
- DirectX9

spacer.png

spacer.png

Edited by Matteo
  • Think 1

🖥️ SysAdmin — Government (HU)
🛠️ Freelance Metin2 Dev • DevOps
🐧 FreeBSD / Linux | 🛡️ Security & WAF | 🚀 Performance | 🔥 Firewalls | ⚙ Automation
Open to work - Contact me on Discord @matteo_r

  • Active+ Member
19 hours ago, Matteo said:

There is one crash observed by our team, we know the following details:

- Only happens on Windows 11
- Only happens if in NVIDIA Control Center -> Dekstop size and position (or smth) -> Perform scaling on is DISPLAY. (On GPU, it does not crash)
- Need to trigger device reset (for example, launch CS2 or any other fullscreen game / any fullscreen application which is not your native res, or restart your VGA driver)
- Then it "Failed to reset device" and the client freezes out (Error code: 0x8876086C - D3DERR_INVALIDCALL: The method call is invalid)
- Its 100% the shader thing, its not getting crashed when its defined out
- DirectX9

spacer.png

spacer.png

I'd recommend you to upgrade to Dx9Ex so device never gets lost

  • Metin2 Dev 1

First of all, thanks to Mali for the Post Process it’s truly a beautiful framework!
I’ve added Color Settings to control Contrast, Brightness, and Saturation.
The FX shaders were written by Claude AI (I’m not an expert in shaders).

I’m too lazy to create a full tutorial, so just search for ENABLE_SETTING_COLOR.

Note: I removed Mali’s shaders to make it easier to extract the color-settings shader.


ShaderManager.cpp
 

Spoiler
/**
 * @file   ShaderManager.cpp
 * @author blackdragonx61 / Mali
 * @date   27.10.2025
 * __BL_SHADER__
 */

#include "StdAfx.h"
#include "ShaderManager.h"
#include "PythonApplication.h"
#include "../EterPack/EterPackManager.h"
#include "../EterLib/Camera.h"

#if defined(__BL_SHADER__)
CShaderManager::CShaderManager() :
	m_ScreenQuad(nullptr),
	m_RenderTextureA(nullptr),
	m_RenderTextureB(nullptr),
	m_RenderSurfaceA(nullptr),
	m_RenderSurfaceB(nullptr),
	m_OriginalRenderTexture(nullptr)
{
}

CShaderManager::~CShaderManager()
{
	Destroy();
}

bool CShaderManager::Initialize()
{
	if (!__CreateResources())
		return false;

	if (!__LoadShaders())
		return false;

	return true;
}

void CShaderManager::Destroy()
{
	stl_wipe_second(m_ShaderMap);
	__ReleaseResources();
}

bool CShaderManager::__CreateResources()
{
	LPDIRECT3DDEVICE9 device = STATEMANAGER.GetDevice();
	CPythonApplication& app = CPythonApplication::Instance();
	const int width = app.GetWidth();
	const int height = app.GetHeight();

	SQuadVertex vertices[] =
	{
		{ -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f },
		{ -0.5f, height - 0.5f, 0.5f, 1.0f, 0.0f, 1.0f },
		{ width - 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f },
		{ width - 0.5f, height - 0.5f, 0.5f, 1.0f, 1.0f, 1.0f }
	};

	if (FAILED(device->CreateVertexBuffer(
		sizeof(vertices),
		D3DUSAGE_WRITEONLY,
		D3DFVF_XYZRHW | D3DFVF_TEX1,
		D3DPOOL_DEFAULT,
		&m_ScreenQuad,
		nullptr)))
	{
		TraceError("CShaderManager::__CreateResources CreateVertexBuffer Failed.");
		return false;
	}

	void* data;
	if (FAILED(m_ScreenQuad->Lock(0, 0, &data, 0)))
	{
		TraceError("CShaderManager::__CreateResources Lock Failed.");
		return false;
	}

	memcpy(data, vertices, sizeof(vertices));

	if (FAILED(m_ScreenQuad->Unlock()))
	{
		TraceError("CShaderManager::__CreateResources Unlock Failed.");
		return false;
	}

	if (FAILED(device->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_RenderTextureA, nullptr)))
	{
		TraceError("CShaderManager::__CreateResources m_RenderTextureA: Create Failed.");
		return false;
	}

	if (FAILED(m_RenderTextureA->GetSurfaceLevel(0, &m_RenderSurfaceA)))
	{
		TraceError("CShaderManager::__CreateResources m_RenderTextureA: Surface Level Failed.");
		return false;
	}

	if (FAILED(device->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_RenderTextureB, nullptr)))
	{
		TraceError("CShaderManager::__CreateResources m_RenderTextureB: Create Failed.");
		return false;
	}

	if (FAILED(m_RenderTextureB->GetSurfaceLevel(0, &m_RenderSurfaceB)))
	{
		TraceError("CShaderManager::__CreateResources m_RenderTextureB: Surface Level Failed.");
		return false;
	}

	return true;
}

void CShaderManager::__ReleaseResources()
{
	safe_release(m_ScreenQuad);
	safe_release(m_RenderTextureA);
	safe_release(m_RenderTextureB);
	safe_release(m_RenderSurfaceA);
	safe_release(m_RenderSurfaceB);
	safe_release(m_OriginalRenderTexture);
}

bool CShaderManager::__LoadShaders()
{
#if defined(ENABLE_SETTING_COLOR)
	if (!AddFX(IShader::EType::COLOR_SETTINGS, "d:/ymir work/shader/color_settings.fx"))
		return false;
#endif

	return true;
}

void CShaderManager::LostDevice()
{
	for (const auto& elem : m_ShaderMap)
	{
		IShader* shader = elem.second;
		if (shader)
			shader->LostDevice();
	}

	__ReleaseResources();
}

void CShaderManager::RestoreDevice()
{
	__CreateResources();

	for (const auto& elem : m_ShaderMap)
	{
		IShader* shader = elem.second;
		if (shader)
			shader->ResetDevice();
	}
}

void CShaderManager::Begin()
{
	LPDIRECT3DDEVICE9 device = STATEMANAGER.GetDevice();

	if (FAILED(device->GetRenderTarget(0, &m_OriginalRenderTexture)))
		return;

	device->SetRenderTarget(0, m_RenderSurfaceA);
	device->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0.0f);
}

void CShaderManager::End(IShader::ERenderType renderType)
{
	LPDIRECT3DDEVICE9 device = STATEMANAGER.GetDevice();

	STATEMANAGER.SaveRenderState(D3DRS_ZENABLE, FALSE);
	STATEMANAGER.SaveRenderState(D3DRS_ZWRITEENABLE, FALSE);

	DWORD stateColorArg1;
	STATEMANAGER.GetTextureStageState(0, D3DTSS_COLORARG1, &stateColorArg1);

	STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	STATEMANAGER.SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
	STATEMANAGER.SetStreamSource(0, m_ScreenQuad, sizeof(SQuadVertex));

	LPDIRECT3DTEXTURE9 src = m_RenderTextureA;
	LPDIRECT3DTEXTURE9 dst = m_RenderTextureB;

	for (const auto& elem : m_ShaderMap)
	{
		IShader* shader = elem.second;
		if (!shader || !shader->IsVisible() || !shader->GetEffect() || shader->GetRenderType() != renderType)
			continue;

		if (dst == m_RenderTextureA)
			device->SetRenderTarget(0, m_RenderSurfaceA);
		else
			device->SetRenderTarget(0, m_RenderSurfaceB);

		device->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0.0f);

		shader->ApplySettings(src);
		shader->Render([] {
			STATEMANAGER.GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
			});

		std::swap(src, dst);
	}

	device->SetRenderTarget(0, m_OriginalRenderTexture);
	safe_release(m_OriginalRenderTexture);

	STATEMANAGER.SetTexture(0, src);
	STATEMANAGER.DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

	STATEMANAGER.RestoreRenderState(D3DRS_ZENABLE);
	STATEMANAGER.RestoreRenderState(D3DRS_ZWRITEENABLE);
	STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, stateColorArg1);
}

bool CShaderManager::AddFX(IShader::EType type, const char* path)
{
	if (GetShader(type))
	{
		TraceError("CShaderManager::AddFX Shader Already Exists: %s", path);
		return false;
	}

	IShader* shader;

	switch (type)
	{
#if defined(ENABLE_SETTING_COLOR)
	case IShader::EType::COLOR_SETTINGS:
		shader = new CColorSettingsShader();
		break;
#endif

	default:
		TraceError("CShaderManager::AddFX Unknown Shader Type: %d", type);
		return false;
	}

	if (!shader->Load(path))
	{
		delete shader;
		return false;
	}

	m_ShaderMap[type] = shader;
	return true;
}

IShader* CShaderManager::GetShader(IShader::EType type) const
{
	auto it = m_ShaderMap.find(type);
	if (it == m_ShaderMap.end())
		return nullptr;

	return it->second;
}

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

PyObject* shaderMgrBegin(PyObject* poSelf, PyObject* poArgs)
{
	CShaderManager::Instance().Begin();
	return Py_BuildNone();
}

PyObject* shaderMgrEnd(PyObject* poSelf, PyObject* poArgs)
{
	CShaderManager::Instance().End(IShader::ERenderType::UI);
	return Py_BuildNone();
}

PyObject* shaderMgrShowShader(PyObject* poSelf, PyObject* poArgs)
{
	int iShaderTypeIndex;
	if (!PyTuple_GetInteger(poArgs, 0, &iShaderTypeIndex))
		return Py_BuildException();

	IShader* shader = CShaderManager::Instance().GetShader(static_cast<IShader::EType>(iShaderTypeIndex));
	if (!shader)
	{
		TraceError("shadermgrShowShader Cannot Find Shader Type: (%d)", iShaderTypeIndex);
		return Py_BuildNone();
	}

	shader->Show();
	return Py_BuildNone();
}

PyObject* shaderMgrHideShader(PyObject* poSelf, PyObject* poArgs)
{
	int iShaderTypeIndex;
	if (!PyTuple_GetInteger(poArgs, 0, &iShaderTypeIndex))
		return Py_BuildException();
	
	IShader* shader = CShaderManager::Instance().GetShader(static_cast<IShader::EType>(iShaderTypeIndex));
	if (!shader)
	{
		TraceError("shadermgrHideShader Cannot Find Shader Type: (%d)", iShaderTypeIndex);
		return Py_BuildNone();
	}

	shader->Hide();
	return Py_BuildNone();
}

#if defined(ENABLE_SETTING_COLOR)
PyObject* shaderMgrSetBrightness(PyObject* poSelf, PyObject* poArgs)
{
	float fValue;
	if (!PyTuple_GetFloat(poArgs, 0, &fValue))
		return Py_BuildException();

	IShader* shader = CShaderManager::Instance().GetShader(IShader::EType::COLOR_SETTINGS);
	if (shader)
	{
		CColorSettingsShader* colorSettingShader = static_cast<CColorSettingsShader*>(shader);
		if (colorSettingShader)
			colorSettingShader->SetBrightness(fValue);
	}
	return Py_BuildNone();
}

PyObject* shaderMgrSetSaturation(PyObject* poSelf, PyObject* poArgs)
{
	float fValue;
	if (!PyTuple_GetFloat(poArgs, 0, &fValue))
		return Py_BuildException();

	IShader* shader = CShaderManager::Instance().GetShader(IShader::EType::COLOR_SETTINGS);
	if (shader)
	{
		CColorSettingsShader* colorSettingShader = static_cast<CColorSettingsShader*>(shader);
		if (colorSettingShader)
			colorSettingShader->SetSaturation(fValue);
	}
	return Py_BuildNone();
}

PyObject* shaderMgrSetContrast(PyObject* poSelf, PyObject* poArgs)
{
	float fValue;
	if (!PyTuple_GetFloat(poArgs, 0, &fValue))
		return Py_BuildException();

	IShader* shader = CShaderManager::Instance().GetShader(IShader::EType::COLOR_SETTINGS);
	if (shader)
	{
		CColorSettingsShader* colorSettingShader = static_cast<CColorSettingsShader*>(shader);
		if (colorSettingShader)
			colorSettingShader->SetContrast(fValue);
	}
	return Py_BuildNone();
}
#endif

void initshadermgr()
{
	static PyMethodDef s_methods[] =
	{
		{ "Begin",			shaderMgrBegin,			METH_VARARGS },
		{ "End",			shaderMgrEnd,			METH_VARARGS },
		{ "ShowShader",		shaderMgrShowShader,	METH_VARARGS },
		{ "HideShader",		shaderMgrHideShader,	METH_VARARGS },

#if defined(ENABLE_SETTING_COLOR)
		{ "SetBrightness",	shaderMgrSetBrightness,	METH_VARARGS },
		{ "SetSaturation",	shaderMgrSetSaturation,	METH_VARARGS },
		{ "SetContrast",	shaderMgrSetContrast,	METH_VARARGS },
#endif

		{ NULL,				NULL,					NULL         },
	};

	PyObject* poModule = Py_InitModule("shaderMgr", s_methods);
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_WAVE",				(long)IShader::EType::WAVE);
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_GRAY",				(long)IShader::EType::GRAY);
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_PIXELATE",			(long)IShader::EType::PIXELATE);
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_INVERT",				(long)IShader::EType::INVERT);
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_ATLAS_SPOTLIGHT",	(long)IShader::EType::ATLAS_SPOTLIGHT);
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_UI_BLUR",			(long)IShader::EType::UI_BLUR);
#if defined(ENABLE_SETTING_COLOR)
	PyModule_AddIntConstant(poModule, "SHADER_TYPE_COLOR_SETTINGS", (long)IShader::EType::COLOR_SETTINGS);
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

IShader::IShader() :
	m_Effect(nullptr),
	m_Visible(true)
{
}

IShader::~IShader()
{
	safe_release(m_Effect);
}

bool IShader::Load(const char* path)
{
	CMappedFile file;
	const VOID* data;
	if (!CEterPackManager::Instance().Get(file, path, &data))
	{
		TraceError("IShader::Load - Cannot Load FX: %s", path);
		return false;
	}

	LPD3DXBUFFER errors = nullptr;
	HRESULT hr = D3DXCreateEffect(
		STATEMANAGER.GetDevice(),
		data,
		file.Size(),
		nullptr,
		nullptr,
		D3DXSHADER_OPTIMIZATION_LEVEL3,
		nullptr,
		&m_Effect,
		&errors);

	if (FAILED(hr))
	{
		if (errors)
		{
			OutputDebugStringA((char*)errors->GetBufferPointer());
			TraceError((char*)errors->GetBufferPointer());
			errors->Release();
		}
		TraceError("IShader::Load - Failed To Compile Effect: %s", path);
		return false;
	}

	return true;
}

void IShader::Render(const std::function<void()>& func) const
{
	if (!m_Effect)
		return;

	UINT i = 0;
	D3DXHANDLE hTech = nullptr;

	while ((hTech = m_Effect->GetTechnique(i++)))
	{
		m_Effect->SetTechnique(hTech);

		UINT numPasses = 0;
		m_Effect->Begin(&numPasses, 0);

		for (UINT p = 0; p < numPasses; ++p)
		{
			m_Effect->BeginPass(p);
			func();
			m_Effect->EndPass();
		}

		m_Effect->End();
	}
}

void IShader::LostDevice() const
{
	if (!m_Effect)
		return;

	m_Effect->OnLostDevice();
}

void IShader::ResetDevice() const
{
	if (!m_Effect)
		return;

	m_Effect->OnResetDevice();
}


///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

#if defined(ENABLE_SETTING_COLOR)
CColorSettingsShader::CColorSettingsShader() :
	m_Brightness(0.0f),
	m_Saturation(1.0f),
	m_Contrast(1.0f)
{
}

void CColorSettingsShader::ApplySettings(LPDIRECT3DTEXTURE9 inputTexture) const
{
	if (!m_Effect)
		return;

	D3DXHANDLE hBrightness = m_Effect->GetParameterByName(nullptr, "g_fBrightness");
	D3DXHANDLE hSaturation = m_Effect->GetParameterByName(nullptr, "g_fSaturation");
	D3DXHANDLE hContrast = m_Effect->GetParameterByName(nullptr, "g_fContrast");
	D3DXHANDLE hTexture = m_Effect->GetParameterByName(nullptr, "g_txScene");

	if (hBrightness)
		m_Effect->SetFloat(hBrightness, m_Brightness);
	if (hSaturation)
		m_Effect->SetFloat(hSaturation, m_Saturation);
	if (hContrast)
		m_Effect->SetFloat(hContrast, m_Contrast);
	if (hTexture)
		m_Effect->SetTexture(hTexture, inputTexture);
}
#endif

#endif // __BL_SHADER__

 


ShaderManager.h

 

Spoiler

/**
 * @file   ShaderManager.h
 * @author blackdragonx61 / Mali
 * @date   27.10.2025
 * __BL_SHADER__
 */

#pragma once

#if defined(__BL_SHADER__)
#include <functional>

class IShader
{
public:
	enum class EType
	{
		WAVE,
		GRAY,
		PIXELATE,
		INVERT,
		ATLAS_SPOTLIGHT,
		UI_BLUR,
#if defined(ENABLE_SETTING_COLOR)
		COLOR_SETTINGS,
#endif
	};

	enum class ERenderType
	{
		GAME_SCREEN,
		ATLAS,
		UI,
		OTHER,
	};

	explicit IShader();
	virtual ~IShader();

	virtual void ApplySettings(LPDIRECT3DTEXTURE9 inputTexture) const = 0;
	virtual EType GetType() const = 0;
	virtual ERenderType GetRenderType() const = 0;

	bool Load(const char* path);
	void Render(const std::function<void()>& func) const;
	void LostDevice() const;
	void ResetDevice() const;

	virtual void Show() { m_Visible = true; }
	virtual void Hide() { m_Visible = false; }

	ID3DXEffect* GetEffect() const { return m_Effect; }
	bool IsVisible() const { return m_Visible; }

protected:
	ID3DXEffect* m_Effect;
	bool m_Visible;
};

#if defined(ENABLE_SETTING_COLOR)
class CColorSettingsShader : public IShader
{
public:
	CColorSettingsShader();
	~CColorSettingsShader() override = default;

	void ApplySettings(LPDIRECT3DTEXTURE9 inputTexture) const override;
	EType GetType() const override { return EType::COLOR_SETTINGS; }
	ERenderType GetRenderType() const override { return ERenderType::GAME_SCREEN; }

	void SetBrightness(float value) { m_Brightness = value; }
	void SetSaturation(float value) { m_Saturation = value; }
	void SetContrast(float value) { m_Contrast = value; }

	float GetBrightness() const { return m_Brightness; }
	float GetSaturation() const { return m_Saturation; }
	float GetContrast() const { return m_Contrast; }

private:
	float m_Brightness; // Range: -0.2 to 0.2
	float m_Saturation; // Range: 0.0 to 2.0
	float m_Contrast;   // Range: 0.5 to 1.5
};
#endif

class CShaderManager : public CSingleton<CShaderManager>
{
public:
	CShaderManager();
	~CShaderManager();

	bool Initialize();
	void Destroy();

	void LostDevice();
	void RestoreDevice();

	void Begin();
	void End(IShader::ERenderType renderType);

	bool AddFX(IShader::EType type, const char* path);
	IShader* GetShader(IShader::EType type) const;

private:
	bool __CreateResources();
	void __ReleaseResources();
	bool __LoadShaders();

private:
	struct SQuadVertex
	{
		float x, y, z, rhw;
		float u, v;
	};
	LPDIRECT3DVERTEXBUFFER9 m_ScreenQuad;

	LPDIRECT3DTEXTURE9 m_RenderTextureA;
	LPDIRECT3DTEXTURE9 m_RenderTextureB;
	LPDIRECT3DSURFACE9 m_RenderSurfaceA;
	LPDIRECT3DSURFACE9 m_RenderSurfaceB;

	LPDIRECT3DSURFACE9 m_OriginalRenderTexture;

	std::unordered_map<IShader::EType, IShader*> m_ShaderMap;
};

#endif // __BL_SHADER__

 

 

color_settings.fx

 

Spoiler
/**
 * @file   color_settings.fx
 * @brief  Color adjustment shader for brightness, saturation, and contrast
 * ENABLE_SETTING_COLOR
 */

// Input texture
texture g_txScene;

// Shader parameters
float g_fBrightness = 0.0f;  // Range: -0.2 to 0.2
float g_fSaturation = 1.0f;  // Range: 0.0 to 2.0
float g_fContrast = 1.0f;    // Range: 0.5 to 1.5

// Sampler
sampler SceneSampler = sampler_state
{
    Texture = <g_txScene>;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    MipFilter = LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};

// Vertex shader output structure
struct VS_OUTPUT
{
    float4 Position : POSITION;
    float2 TexCoord : TEXCOORD0;
};

// Vertex shader (pass-through)
VS_OUTPUT VS_ColorSettings(float4 Position : POSITION, float2 TexCoord : TEXCOORD0)
{
    VS_OUTPUT Output;
    Output.Position = Position;
    Output.TexCoord = TexCoord;
    return Output;
}

// Convert RGB to luminance (for saturation calculation)
float GetLuminance(float3 color)
{
    return dot(color, float3(0.299f, 0.587f, 0.114f));
}

// Pixel shader
float4 PS_ColorSettings(VS_OUTPUT Input) : COLOR0
{
    // Sample the scene texture
    float4 color = tex2D(SceneSampler, Input.TexCoord);
    
    // Apply brightness adjustment
    color.rgb += g_fBrightness;
    
    // Apply saturation adjustment
    float luminance = GetLuminance(color.rgb);
    color.rgb = lerp(float3(luminance, luminance, luminance), color.rgb, g_fSaturation);
    
    // Apply contrast adjustment
    // Contrast formula: ((color - 0.5) * contrast) + 0.5
    color.rgb = ((color.rgb - 0.5f) * g_fContrast) + 0.5f;
    
    // Clamp to valid range
    color.rgb = saturate(color.rgb);
    
    return color;
}

// Technique
technique ColorSettings
{
    pass P0
    {
        VertexShader = compile vs_2_0 VS_ColorSettings();
        PixelShader = compile ps_2_0 PS_ColorSettings();
    }
}

 

 

For testing 
 

Spoiler
import ui
import shaderMgr

class TestWindow(ui.BoardWithTitleBar):
	def __init__(self):
		ui.BoardWithTitleBar.__init__(self)
		self.brightness = 0.0
		self.saturation = 1.0
		self.contrast = 1.0
		self.LoadWindow()

	def __del__(self):
		ui.BoardWithTitleBar.__del__(self)

	def LoadWindow(self):
		self.SetSize(300, 130)
		self.AddFlag("movable")
		self.AddFlag("float")
		self.SetCenterPosition()
		self.SetCloseEvent(ui.__mem_func__(self.Hide))
		self.SetTitleName("Color Settings")

		self.brightness_text = ui.TextLine()
		self.brightness_text.SetParent(self)
		self.brightness_text.SetPosition(20, 40)
		self.brightness_text.SetText("Brightness:")
		self.brightness_text.Show()

		self.brightness_slider = ui.SliderBar()
		self.brightness_slider.SetParent(self)
		self.brightness_slider.SetPosition(90, 40)
		self.brightness_slider.SetEvent(ui.__mem_func__(self.OnMoveBrightnessSlider))
		self.brightness_slider.Show()

		self.brightness_value = ui.TextLine()
		self.brightness_value.SetParent(self)
		self.brightness_value.SetPosition(180, 40)
		self.brightness_value.SetText("0.00")
		self.brightness_value.Show()

		self.saturation_text = ui.TextLine()
		self.saturation_text.SetParent(self)
		self.saturation_text.SetPosition(20, 70)
		self.saturation_text.SetText("Saturation:")
		self.saturation_text.Show()

		self.saturation_slider = ui.SliderBar()
		self.saturation_slider.SetParent(self)
		self.saturation_slider.SetPosition(90, 70)
		self.saturation_slider.SetEvent(ui.__mem_func__(self.OnMoveSaturationSlider))
		self.saturation_slider.Show()

		self.saturation_value = ui.TextLine()
		self.saturation_value.SetParent(self)
		self.saturation_value.SetPosition(180, 70)
		self.saturation_value.SetText("1.00")
		self.saturation_value.Show()

		self.contrast_text = ui.TextLine()
		self.contrast_text.SetParent(self)
		self.contrast_text.SetPosition(20, 100)
		self.contrast_text.SetText("Contrast:")
		self.contrast_text.Show()

		self.contrast_slider = ui.SliderBar()
		self.contrast_slider.SetParent(self)
		self.contrast_slider.SetPosition(90, 100)
		self.contrast_slider.SetEvent(ui.__mem_func__(self.OnMoveContrastSlider))
		self.contrast_slider.Show()

		self.contrast_value = ui.TextLine()
		self.contrast_value.SetParent(self)
		self.contrast_value.SetPosition(180, 100)
		self.contrast_value.SetText("1.00")
		self.contrast_value.Show()

		self.ResetToDefaults()

	def OnMoveBrightnessSlider(self):
		pos = self.brightness_slider.GetSliderPos()
		self.brightness = (pos - 0.5) * 0.4
		self.brightness_value.SetText("{:.2f}".format(self.brightness))
		self.UpdateSettings()

	def OnMoveSaturationSlider(self):
		pos = self.saturation_slider.GetSliderPos()
		self.saturation = pos * 2.0
		self.saturation_value.SetText("{:.2f}".format(self.saturation))
		self.UpdateSettings()
		
	def OnMoveContrastSlider(self):
		pos = self.contrast_slider.GetSliderPos()
		self.contrast = 0.5 + pos
		self.contrast_value.SetText("{:.2f}".format(self.contrast))
		self.UpdateSettings()
		
	def UpdateSettings(self):
		shaderMgr.SetBrightness(self.brightness)
		shaderMgr.SetSaturation(self.saturation)
		shaderMgr.SetContrast(self.contrast)

	def ResetToDefaults(self):
		self.brightness_slider.SetSliderPos(0.5)
		self.saturation_slider.SetSliderPos(0.5)
		self.contrast_slider.SetSliderPos(0.5)

		self.OnMoveBrightnessSlider()
		self.OnMoveSaturationSlider()
		self.OnMoveContrastSlider()
		
	def Show(self):
		self.SetTop()
		self.SetCenterPosition()
		ui.BoardWithTitleBar.Show(self)
		self.SetTop()
		
	def Close(self):
		self.Hide()

a = TestWindow()
a.Show()

 

 

Preview:

This is the hidden content, please

Edited by Metin2 Dev International
Core X - External 2 Internal
  • Metin2 Dev 62
  • Good 3
  • muscle 1
  • Love 2
  • Love 13

An excellent idea and a good method for working on your shaders
thanks @ Mali
Depending on how your shadows work, you may need to add them in the End, otherwise you may encounter problems. At least, that was the case with the changes I made to the shadow. I'm sharing this in case anyone else needs it, although it's something that's extremely easy to fix.

 

// search 
void CShaderManager::End(IShader::ERenderType renderType)
{
	LPDIRECT3DDEVICE9 device = STATEMANAGER.GetDevice();

	STATEMANAGER.SaveRenderState(D3DRS_ZENABLE, FALSE);
	STATEMANAGER.SaveRenderState(D3DRS_ZWRITEENABLE, FALSE);

//add:
	STATEMANAGER.SaveRenderState(D3DRS_FOGENABLE, FALSE);

// again inside void CShaderManager::End(IShader::ERenderType renderType)

	STATEMANAGER.RestoreRenderState(D3DRS_ZENABLE);
	STATEMANAGER.RestoreRenderState(D3DRS_ZWRITEENABLE);
//add
	STATEMANAGER.RestoreRenderState(D3DRS_FOGENABLE);

	STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, state_COLORARG1);
}

 

  • Metin2 Dev 1

Don't use any images from : imgur, turkmmop, freakgamers, inforge, hizliresim... Or your content will be deleted without notice...
Use : https://metin2.download/media/add/

Please use https://metin2.download/ when uploading files smaller than 100MB, otherwise the approval will take longer due to manual upload.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.