port EterLib/GrpShadowTexture verbatim; D3D top-left fill rule for the shadow target (unit C/D)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-28 13:05:55 +09:00
co-authored by Claude Opus 5.5
parent 5be37b6363
commit 86e602cb32
6 changed files with 281 additions and 62 deletions
+1
View File
@@ -560,3 +560,4 @@
{"date": "2026-09-28T03:54:13+00:00", "unit": "EterLib/GrpScreen.cpp + native D3D8 fixed-function emulation (RecordingDevice / native_render)", "action": "unit A: D3D8 texcoord generation + D3DTS_TEXTUREn in shader (CPU uv baking removed), 8-light D3D lighting (no attenuation clamp, spot theta/2 phi/2, emissive source, NORMALIZENORMALS, inverse-transpose normals), 8-bit alpha test, clears in draw order, dynamic D3D viewport + MinZ/MaxZ, black back-buffer clear, white default diffuse, alpha blend mirrors colour factors; removed sphere-map and multiplicative-shadow hacks; CScreen ms_clearDepth/ms_diffuseColor initialisers fixed to 40250 values; capture v8; --screenshot-out readback", "evidence": "ctest 35/35 (login_live excluded); mt_native_render fake-server 240/900-frame screenshots vs baseline: game frame mean abs diff 0.55/255, char select model now placed by introselect.py grp.SetViewport(270/w,...); Windows reference frame pending (NEEDS_LIVE)"}
{"date": "2026-09-28T03:59:30+00:00", "unit": "EterLib/GrpLightManager.cpp", "action": "unit B: platform stub removed, 40250 GrpLightManager.cpp copied verbatim into port/ (port_copy.py); CPythonApplication::Create now calls m_LightManager.Initialize() (40250 :1263) so light IDs start at skip index 1; 40250 never calls FlushLight/RestoreLight, so no device-light change", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs baseline mean abs diff 0.21/255"}
{"date": "2026-09-28T04:02:30+00:00", "unit": "EterLib/LensFlare.cpp + ScreenFilter.cpp + Decal.cpp", "action": "unit C (part 1): platform stubs removed, 40250 sources copied verbatim (GL code is commented out in 40250 too); CMapOutdoor callers were already ported, so lens flare, screen filter and CDecal clipping (TerrainDecal) now run", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs unit B mean abs diff 0.014/255; flare/filter visuals NEEDS_LIVE"}
{"date": "2026-09-28T04:05:55+00:00", "unit": "EterLib/GrpShadowTexture.cpp + character shadow render target (RecordingDevice)", "action": "unit C part 2 / unit D: GrpShadowTexture stub replaced with verbatim 40250 copy (dead in 40250); CPU shadow-target rasterizer kept (flat TFACTOR pass, output-equivalent) and switched to the D3D top-left fill rule; Vulkan offscreen targets not needed since snow blur is hard-disabled in 40250", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs previous mean abs diff 0.013/255 (frame noise); shadow parity NEEDS_LIVE"}
@@ -0,0 +1,78 @@
{
"reference": "EterLib/GrpShadowTexture.cpp",
"reference_sha256": "0b538a6e4a772ae16cbbf9095c656edd403bd01e60802b6eaa66df352e9507a6",
"priority": "P4",
"contracts": [],
"functions": {
"CGraphicShadowTexture::Destroy": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Destroy"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::Create": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Create"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::Set": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Set"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::GetLightVPMatrixReference": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::GetLightVPMatrixReference"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::GetD3DTexture": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::GetD3DTexture"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::Begin": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Begin"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::End": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::End"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::Initialize": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Initialize"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::CGraphicShadowTexture": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::CGraphicShadowTexture"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
},
"CGraphicShadowTexture::~CGraphicShadowTexture": {
"status": "PORTED",
"impl": [
"extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::~CGraphicShadowTexture"
],
"note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)"
}
}
}
@@ -33,7 +33,7 @@
"impl": [
"extension/src/platform/GameLib/MapOutdoorCharacterShadow.cpp:CMapOutdoor::BeginRenderCharacterShadowToTexture"
],
"note": "Restores 40250 light view, target, viewport and render states; the small R5G6B5 target is rasterized on CPU. Native fake-server capture contains nonwhite shadow pixels; matched Windows screenshot parity remains open."
"note": "Restores 40250 light view, target, viewport and render states; the small R5G6B5 target is rasterized on CPU. Native fake-server capture contains nonwhite shadow pixels; matched Windows screenshot parity remains open. The pass is SELECTARG1 TFACTOR with ALPHAOP DISABLE into a white-cleared R5G6B5 target, so the CPU rasterizer is output-equivalent to a GPU target; it now follows the D3D top-left fill rule. The only other offscreen target in 40250 (CSnowEnvironment blur) is hard-disabled (m_bBlurEnable = FALSE), so no Vulkan offscreen path is needed."
},
"CMapOutdoor::EndRenderCharacterShadowToTexture": {
"status": "NEEDS_LIVE",
@@ -1,59 +0,0 @@
// Platform skeleton for EterLib/GrpShadowTexture.h (40250 EterLib/GrpShadowTexture.cpp), generated by platform_stub.py.
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
#include "EterLib/StdAfx.h"
#include "EterLib/GrpShadowTexture.h"
#include "../PlatformStub.h"
CGraphicShadowTexture::CGraphicShadowTexture()
{
MT_PLATFORM_STUB();
}
CGraphicShadowTexture::~CGraphicShadowTexture()
{
MT_PLATFORM_STUB();
}
auto CGraphicShadowTexture::Destroy() -> void
{
MT_PLATFORM_STUB();
}
auto CGraphicShadowTexture::Create(int, int) -> bool
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
}
auto CGraphicShadowTexture::Begin() -> void
{
MT_PLATFORM_STUB();
}
auto CGraphicShadowTexture::End() -> void
{
MT_PLATFORM_STUB();
}
auto CGraphicShadowTexture::Set(int) const -> void
{
MT_PLATFORM_STUB();
}
auto CGraphicShadowTexture::GetLightVPMatrixReference() const -> const D3DXMATRIX &
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<const D3DXMATRIX &>();
}
auto CGraphicShadowTexture::GetD3DTexture() const -> LPDIRECT3DTEXTURE8
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<LPDIRECT3DTEXTURE8>();
}
auto CGraphicShadowTexture::Initialize() -> void
{
MT_PLATFORM_STUB();
}
@@ -642,14 +642,22 @@ private:
const int y0 = std::max(0, int(std::floor(std::min({a.y, b.y, c.y}))));
const int x1 = std::min(width - 1, int(std::ceil(std::max({a.x, b.x, c.x}))));
const int y1 = std::min(height - 1, int(std::ceil(std::max({a.y, b.y, c.y}))));
// D3D top-left fill rule: a pixel centre exactly on an edge is covered only when that edge
// is a left edge (weight grows to the right) or a top edge (horizontal, weight grows downward).
auto top_left = [area](const Point& p, const Point& q) {
const float dx = (q.y - p.y) / area, dy = (p.x - q.x) / area;
return dx > 0.0f || (dx == 0.0f && dy > 0.0f);
};
const bool tlA = top_left(b, c), tlB = top_left(c, a), tlC = top_left(a, b);
auto covered = [](float w, bool tl) { return w > 0.0f || (w == 0.0f && tl); };
for (int y = y0; y <= y1; ++y)
for (int x = x0; x <= x1; ++x)
{
const float px = float(x) + 0.5f, py = float(y) + 0.5f;
const float wa = edge(b, c, px, py) / area;
const float wb = edge(c, a, px, py) / area;
const float wc = 1.0f - wa - wb;
if (wa < 0.0f || wb < 0.0f || wc < 0.0f) continue;
const float wc = edge(a, b, px, py) / area;
if (!covered(wa, tlA) || !covered(wb, tlB) || !covered(wc, tlC)) continue;
const float z = wa * a.z + wb * b.z + wc * c.z;
const size_t pixel = size_t(y) * width + x;
if (z < 0.0f || z > 1.0f || z > m_offscreenDepth[pixel]) continue;
@@ -0,0 +1,191 @@
#include "StdAfx.h"
#include "GrpShadowTexture.h"
#include "StateManager.h"
//////////////////////////////////////////////////////////////////////////
void CGraphicShadowTexture::Destroy()
{
CGraphicTexture::Destroy();
if (m_lpd3dShadowSurface)
{
m_lpd3dShadowSurface->Release();
m_lpd3dShadowSurface = NULL;
}
if (m_lpd3dDepthSurface)
{
m_lpd3dDepthSurface->Release();
m_lpd3dDepthSurface = NULL;
}
if (m_lpd3dShadowTexture)
{
m_lpd3dShadowTexture->Release();
m_lpd3dShadowTexture = NULL;
}
Initialize();
}
bool CGraphicShadowTexture::Create(int width, int height)
{
Destroy();
m_width = width;
m_height = height;
if (FAILED(ms_lpd3dDevice->CreateTexture(m_width, m_height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_lpd3dShadowTexture)))
return false;
if (FAILED(m_lpd3dShadowTexture->GetSurfaceLevel(0, &m_lpd3dShadowSurface)))
return false;
if (FAILED(ms_lpd3dDevice->CreateDepthStencilSurface(m_width, m_height, D3DFMT_D16, D3DMULTISAMPLE_NONE, &m_lpd3dDepthSurface)))
return false;
return true;
}
void CGraphicShadowTexture::Set(int stage) const
{
STATEMANAGER.SetTexture(stage, m_lpd3dShadowTexture);
}
const D3DXMATRIX& CGraphicShadowTexture::GetLightVPMatrixReference() const
{
return m_d3dLightVPMatrix;
}
LPDIRECT3DTEXTURE8 CGraphicShadowTexture::GetD3DTexture() const
{
return m_lpd3dShadowTexture;
}
void CGraphicShadowTexture::Begin()
{
D3DXMatrixMultiply(&m_d3dLightVPMatrix, &ms_matView, &ms_matProj);
ms_lpd3dDevice->GetRenderTarget(&m_lpd3dOldBackBufferSurface);
ms_lpd3dDevice->GetDepthStencilSurface(&m_lpd3dOldDepthBufferSurface);
ms_lpd3dDevice->GetViewport(&m_d3dOldViewport);
ms_lpd3dDevice->SetRenderTarget(m_lpd3dShadowSurface, m_lpd3dDepthSurface);
D3DVIEWPORT8 d3dViewport;
d3dViewport.MinZ = 0.0f;
d3dViewport.MaxZ = 1.0f;
d3dViewport.X = 0;
d3dViewport.Y = 0;
d3dViewport.Width = m_width;
d3dViewport.Height = m_height;
ms_lpd3dDevice->SetViewport(&d3dViewport);
ms_lpd3dDevice->BeginScene();
ms_lpd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0L);
STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
STATEMANAGER.SaveRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, true);
STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, true);
STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, 0xbb000000);
STATEMANAGER.SetTexture(0, NULL);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_POINT);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_POINT);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_POINT);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
STATEMANAGER.SetTexture(1, NULL);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_MINFILTER, D3DTEXF_POINT);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_MAGFILTER, D3DTEXF_POINT);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_MIPFILTER, D3DTEXF_POINT);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
STATEMANAGER.SaveTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
}
void CGraphicShadowTexture::End()
{
assert(m_lpd3dOldBackBufferSurface != NULL);
assert(m_lpd3dOldDepthBufferSurface != NULL);
ms_lpd3dDevice->EndScene();
ms_lpd3dDevice->SetRenderTarget(m_lpd3dOldBackBufferSurface, m_lpd3dOldDepthBufferSurface);
ms_lpd3dDevice->SetViewport(&m_d3dOldViewport);
m_lpd3dOldBackBufferSurface->Release();
m_lpd3dOldDepthBufferSurface->Release();
m_lpd3dOldBackBufferSurface = NULL;
m_lpd3dOldDepthBufferSurface = NULL;
STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE);
STATEMANAGER.RestoreRenderState(D3DRS_ZFUNC);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE);
STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG2);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ALPHAARG1);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ALPHAARG2);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ALPHAOP);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MINFILTER);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MAGFILTER);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MIPFILTER);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSU);
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSV);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_COLORARG1);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_COLORARG2);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_COLOROP);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ALPHAARG1);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ALPHAARG2);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ALPHAOP);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_MINFILTER);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_MAGFILTER);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_MIPFILTER);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ADDRESSU);
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ADDRESSV);
}
void CGraphicShadowTexture::Initialize()
{
CGraphicTexture::Initialize();
m_lpd3dShadowSurface = NULL;
m_lpd3dDepthSurface = NULL;
m_lpd3dOldBackBufferSurface = NULL;
m_lpd3dOldDepthBufferSurface = NULL;
m_lpd3dShadowTexture = NULL;
}
CGraphicShadowTexture::CGraphicShadowTexture()
{
Initialize();
}
CGraphicShadowTexture::~CGraphicShadowTexture()
{
Destroy();
}