native render: transliterate D3D8 fixed-function pipeline (unit A)

- Texture coordinates: TEXCOORDINDEX generation (camera-space normal /
  position / reflection vector, LOCALVIEWER) and D3DTS_TEXTUREn under
  TEXTURETRANSFORMFLAGS (incl. PROJECTED) now run in native.vert; the
  RecordingDevice CPU uv baking and the sphere-map / multiplicative-shadow
  shader hacks are removed. Godot consumer uses Render3DStageTexcoords.
- Lighting: 8 D3D lights in camera space, 1/(a0+a1d+a2d^2) without clamp,
  spot cone theta/2 phi/2 with falloff, diffuse/ambient/emissive material
  sources, NORMALIZENORMALS, inverse-transpose normal matrix.
- Alpha test compares 8-bit alpha against ALPHAREF with ALPHAFUNC.
- Back-buffer Clear() is recorded in draw order and executed with
  vkCmdClearAttachments; render pass clears to 0xff000000.
- D3DVIEWPORT8 (incl. MinZ/MaxZ) is dynamic per-draw state.
- Missing vertex diffuse is opaque white; alpha blend mirrors SRC/DESTBLEND.
- CScreen::ms_clearDepth / ms_diffuseColor initialised to 40250's
  1.0f / 0xffffffff (were zero; exposed once clears were executed).
- Capture format v8; --screenshot-out FILE.bmp swapchain readback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-28 12:54:13 +09:00
co-authored by Claude Opus 5.5
parent 425e6f37fb
commit 5285ca85fd
11 changed files with 585 additions and 321 deletions
+16 -3
View File
@@ -403,6 +403,8 @@ Array Metin2PythonHost::render3d_draws() {
out.resize(static_cast<int64_t>(draws.size()));
int64_t draw_index = 0;
for (const Render3DDraw &draw : draws) {
if (draw.clear_flags)
continue; // back-buffer clears are consumed by the native renderer only
Dictionary item;
item["geometry_key"] = static_cast<int64_t>(draw.geometry_key);
item["geometry_revision"] = static_cast<int64_t>(draw.geometry_revision);
@@ -414,8 +416,14 @@ Array Metin2PythonHost::render3d_draws() {
item["pretransformed"] = draw.pretransformed;
item["lines"] = draw.lines;
// Stage-0 coordinates the D3D8 pipeline generates or transforms depend on per-draw matrices,
// so such draws carry them outside the geometry cache.
const bool stage0_texgen = (draw.texcoord_index[0] & 0xFFFF0000u) != 0 ||
(draw.texcoord_index[0] & 0xFFFFu) != 0 || draw.texture_transform_flags[0] != 0;
if (stage0_texgen)
item["geometry_key"] = static_cast<int64_t>(0);
Dictionary geometry;
if (draw.geometry_key != 0) {
if (draw.geometry_key != 0 && !stage0_texgen) {
auto cached = geometry_cache.find(draw.geometry_key);
if (cached != geometry_cache.end() && cached->second.revision == draw.geometry_revision) {
cached->second.last_used = extraction;
@@ -461,7 +469,11 @@ Array Metin2PythonHost::render3d_draws() {
}
return out;
};
if (!draw.uv0.empty())
if (stage0_texgen) {
std::vector<float> stage_uv;
Render3DStageTexcoords(draw, 0, stage_uv);
geometry["uv0"] = uvs(stage_uv);
} else if (!draw.uv0.empty())
geometry["uv0"] = uvs(draw.uv0);
if (!draw.uv1.empty())
geometry["uv1"] = uvs(draw.uv1);
@@ -477,7 +489,7 @@ Array Metin2PythonHost::render3d_draws() {
for (int64_t i = 0; i < indices.size(); ++i)
indices[i] = static_cast<int32_t>(draw.indices[i]);
geometry["indices"] = indices;
if (draw.geometry_key != 0)
if (draw.geometry_key != 0 && !stage0_texgen)
geometry_cache[draw.geometry_key] = {draw.geometry_revision, extraction, geometry};
}
item.merge(geometry);
@@ -530,6 +542,7 @@ Array Metin2PythonHost::render3d_draws() {
item["viewport"] = vp;
out[draw_index++] = item;
}
out.resize(draw_index);
if (extraction % 120 == 0) {
for (auto it = geometry_cache.begin(); it != geometry_cache.end();) {
if (extraction - it->second.last_used > 120)