port: CMapOutdoor + PythonBackground + minimap (1:1 40250)

GameLib map/area/terrain units, PRTerrainLib Terrain/TextureSet,
PythonBackground(+Module) and ColorTransitionHelper ported verbatim;
pending stand-ins and background module stubs removed. Hand edits:
BeginEnvironment's pointer tag via uintptr_t, LoadWaterMap reads the
file's 32-bit longs. Terrain draw passes stay platform no-ops (Godot
draws the terrain). RecordingDevice gains textures, buffers and render
targets; orthographic draws become UI commands so CPythonMiniMap's
masked tiles show through minimap_image_filter in python_ui_surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 03:50:09 +09:00
co-authored by Claude Opus 5.5
parent b69116992c
commit 2ead95756e
100 changed files with 17679 additions and 1514 deletions
@@ -3,6 +3,8 @@
#include "UIRenderCommands.h"
#include "CpuBuffer.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <mutex>
@@ -52,6 +54,148 @@ void copy_color(float out[4], const D3DCOLORVALUE& c)
out[0] = c.r; out[1] = c.g; out[2] = c.b; out[3] = c.a;
}
int format_bytes(D3DFORMAT format)
{
switch (format)
{
case D3DFMT_A4R4G4B4: case D3DFMT_R5G6B5: case D3DFMT_A1R5G5B5: case D3DFMT_X1R5G5B5: case D3DFMT_X4R4G4B4:
case D3DFMT_D16: case D3DFMT_D16_LOCKABLE:
return 2;
case D3DFMT_A8: case D3DFMT_L8:
return 1;
default:
return 4;
}
}
// PORT: device-created resources live in CPU memory with D3D reference counting. Textures made by
// CreateTexture (CTerrain's splat alpha maps and attribute marks, CSnowEnvironment's blur targets)
// keep their mip levels as bytes; nothing samples them because terrain and offscreen passes are
// drawn by the Godot side.
struct CpuTexture;
struct CpuSurface final : IDirect3DSurface8
{
ULONG refs = 1;
CpuTexture* parent = nullptr; // a texture level, or null for a standalone surface
UINT level = 0;
D3DSURFACE_DESC desc = {};
std::vector<uint8_t> bytes; // standalone surfaces only
ULONG AddRef() override { return ++refs; }
ULONG Release() override;
HRESULT GetDesc(D3DSURFACE_DESC* out) override { *out = desc; return S_OK; }
HRESULT LockRect(D3DLOCKED_RECT* locked, const RECT*, DWORD) override;
HRESULT UnlockRect() override { return S_OK; }
};
struct CpuTexture final : IDirect3DTexture8
{
struct Level { D3DSURFACE_DESC desc; std::vector<uint8_t> bytes; };
ULONG refs = 1;
std::vector<Level> levels;
ULONG AddRef() override { return ++refs; }
ULONG Release() override
{
const ULONG left = --refs;
if (!left)
delete this;
return left;
}
DWORD GetLevelCount() override { return DWORD(levels.size()); }
HRESULT GetLevelDesc(UINT level, D3DSURFACE_DESC* out) override
{
if (level >= levels.size())
return E_FAIL;
*out = levels[level].desc;
return S_OK;
}
HRESULT GetSurfaceLevel(UINT level, IDirect3DSurface8** out) override
{
if (level >= levels.size())
return E_FAIL;
auto* surface = new CpuSurface();
surface->parent = this;
surface->level = level;
surface->desc = levels[level].desc;
AddRef();
*out = surface;
return S_OK;
}
HRESULT LockRect(UINT level, D3DLOCKED_RECT* locked, const RECT*, DWORD) override
{
if (level >= levels.size())
return E_FAIL;
locked->Pitch = INT(levels[level].desc.Width * format_bytes(levels[level].desc.Format));
locked->pBits = levels[level].bytes.data();
return S_OK;
}
HRESULT UnlockRect(UINT level) override { return level < levels.size() ? S_OK : E_FAIL; }
};
ULONG CpuSurface::Release()
{
const ULONG left = --refs;
if (!left)
{
if (parent)
parent->Release();
delete this;
}
return left;
}
HRESULT CpuSurface::LockRect(D3DLOCKED_RECT* locked, const RECT* rect, DWORD flags)
{
if (parent)
return parent->LockRect(level, locked, rect, flags);
locked->Pitch = INT(desc.Width * format_bytes(desc.Format));
locked->pBits = bytes.data();
return S_OK;
}
D3DSURFACE_DESC surface_desc(UINT width, UINT height, D3DFORMAT format, DWORD usage, D3DPOOL pool)
{
D3DSURFACE_DESC desc = {};
desc.Format = format;
desc.Type = D3DRTYPE_SURFACE;
desc.Usage = usage;
desc.Pool = pool;
desc.Size = width * height * format_bytes(format);
desc.MultiSampleType = D3DMULTISAMPLE_NONE;
desc.Width = width;
desc.Height = height;
return desc;
}
struct DeviceVertexBuffer final : MtCpuVertexBuffer
{
ULONG refs = 1;
ULONG AddRef() override { return ++refs; }
ULONG Release() override
{
const ULONG left = --refs;
if (!left)
delete this;
return left;
}
};
struct DeviceIndexBuffer final : MtCpuIndexBuffer
{
ULONG refs = 1;
ULONG AddRef() override { return ++refs; }
ULONG Release() override
{
const ULONG left = --refs;
if (!left)
delete this;
return left;
}
};
class RecordingDevice final : public IDirect3DDevice8
{
public:
@@ -63,6 +207,23 @@ public:
std::memset(&m_material, 0, sizeof(m_material));
m_material.Diffuse = { 1, 1, 1, 1 };
std::memset(m_lights, 0, sizeof(m_lights));
auto* backBuffer = new CpuSurface();
backBuffer->desc = surface_desc(width, height, D3DFMT_X8R8G8B8, D3DUSAGE_RENDERTARGET, D3DPOOL_DEFAULT);
m_backBuffer = backBuffer;
auto* depthBuffer = new CpuSurface();
depthBuffer->desc = surface_desc(width, height, D3DFMT_D16, D3DUSAGE_DEPTHSTENCIL, D3DPOOL_DEFAULT);
m_depthBuffer = depthBuffer;
m_renderTarget = m_backBuffer;
m_renderTarget->AddRef();
m_depthStencil = m_depthBuffer;
m_depthStencil->AddRef();
}
~RecordingDevice() override
{
m_renderTarget->Release();
m_depthStencil->Release();
m_backBuffer->Release();
m_depthBuffer->Release();
}
ULONG AddRef() override { return ++m_refs; }
@@ -120,6 +281,84 @@ public:
m_lights[index] = *light;
return S_OK;
}
HRESULT LightEnable(DWORD index, BOOL enable) override
{
if (index < 8)
m_lightEnabled[index] = enable != FALSE;
return S_OK;
}
HRESULT CreateTexture(UINT width, UINT height, UINT levels, DWORD usage, D3DFORMAT format, D3DPOOL pool, IDirect3DTexture8** out) override
{
if (!width || !height)
return E_FAIL;
auto* texture = new CpuTexture();
// Levels == 0 builds the full chain down to 1x1.
for (UINT w = width, h = height; ; w = std::max(1u, w / 2), h = std::max(1u, h / 2))
{
CpuTexture::Level level;
level.desc = surface_desc(w, h, format, usage, pool);
level.bytes.resize(level.desc.Size);
texture->levels.push_back(std::move(level));
if ((levels && texture->levels.size() == levels) || (w == 1 && h == 1))
break;
}
*out = texture;
return S_OK;
}
HRESULT CreateVertexBuffer(UINT length, DWORD, DWORD fvf, D3DPOOL, IDirect3DVertexBuffer8** out) override
{
auto* buffer = new DeviceVertexBuffer();
buffer->bytes.resize(length);
buffer->fvf = fvf;
*out = buffer;
return S_OK;
}
HRESULT CreateIndexBuffer(UINT length, DWORD, D3DFORMAT format, D3DPOOL, IDirect3DIndexBuffer8** out) override
{
auto* buffer = new DeviceIndexBuffer();
buffer->bytes.resize(length);
buffer->format = format;
*out = buffer;
return S_OK;
}
HRESULT CreateDepthStencilSurface(UINT width, UINT height, D3DFORMAT format, D3DMULTISAMPLE_TYPE, IDirect3DSurface8** out) override
{
auto* surface = new CpuSurface();
surface->desc = surface_desc(width, height, format, D3DUSAGE_DEPTHSTENCIL, D3DPOOL_DEFAULT);
*out = surface;
return S_OK;
}
HRESULT GetRenderTarget(IDirect3DSurface8** out) override
{
m_renderTarget->AddRef();
*out = m_renderTarget;
return S_OK;
}
HRESULT GetDepthStencilSurface(IDirect3DSurface8** out) override
{
m_depthStencil->AddRef();
*out = m_depthStencil;
return S_OK;
}
// PORT: an offscreen target (CSnowEnvironment's blur pass) swallows its draws; only the back
// buffer's draws are recorded for the Godot renderer.
HRESULT SetRenderTarget(IDirect3DSurface8* target, IDirect3DSurface8* depth) override
{
if (target)
{
target->AddRef();
m_renderTarget->Release();
m_renderTarget = target;
}
if (depth)
{
depth->AddRef();
m_depthStencil->Release();
m_depthStencil = depth;
}
return S_OK;
}
HRESULT Clear(DWORD, const D3DRECT*, DWORD, D3DCOLOR, float, DWORD) override { return S_OK; }
HRESULT SetRenderState(D3DRENDERSTATETYPE state, DWORD value) override
{
if (unsigned(state) < kRenderStates)
@@ -237,10 +476,118 @@ private:
return true;
}
static void transform_point(const float v[4], const float m[16], float out[4])
{
for (int c = 0; c < 4; ++c)
out[c] = v[0] * m[c] + v[1] * m[4 + c] + v[2] * m[8 + c] + v[3] * m[12 + c];
}
static void multiply(const float a[16], const float b[16], float out[16])
{
for (int r = 0; r < 4; ++r)
transform_point(&a[r * 4], b, &out[r * 4]);
}
// Each two-triangle quad of a UI-space draw becomes an image command: its screen corners through
// world * view * projection, its stage-0 texture (or D3DTA_TFACTOR colour when stage 0 selects it)
// and, when stage 1 generates coordinates from the camera-space position through D3DTS_TEXTURE1
// (the minimap's circular filter), the mask's coordinates at the corners.
void record_ui_quads(D3DPRIMITIVETYPE type, const uint8_t* vertices, size_t vertexBytes, UINT stride,
const VertexLayout& layout, const std::vector<std::uint32_t>& indices)
{
if (layout.uv0 < 0)
return;
unsigned width = 0, height = 0;
UIRenderGetSize(&width, &height);
float worldView[16], worldViewProj[16];
multiply(m_transforms[256], m_transforms[D3DTS_VIEW], worldView);
multiply(worldView, m_transforms[D3DTS_PROJECTION], worldViewProj);
const DWORD* stage0 = m_stageStates[0];
const DWORD* stage1 = m_stageStates[1];
const bool factorColor = stage0[D3DTSS_COLOROP] == D3DTOP_SELECTARG1 && stage0[D3DTSS_COLORARG1] == D3DTA_TFACTOR;
const std::uint32_t argb = factorColor ? (0xFF000000u | (m_renderStates[D3DRS_TEXTUREFACTOR] & 0x00FFFFFFu)) : 0xFFFFFFFFu;
const bool masked = m_textures[1] && (stage1[D3DTSS_TEXCOORDINDEX] & 0xFFFF0000u) == D3DTSS_TCI_CAMERASPACEPOSITION &&
stage1[D3DTSS_TEXTURETRANSFORMFLAGS] == D3DTTFF_COUNT2 && stage1[D3DTSS_COLOROP] == D3DTOP_MODULATE &&
stage1[D3DTSS_ALPHAOP] == D3DTOP_SELECTARG1 && stage1[D3DTSS_ALPHAARG1] == D3DTA_TEXTURE;
const std::string texture = factorColor ? std::string() : UIRenderTextureNameFromHandle(m_textures[0]);
const std::string mask = masked ? UIRenderTextureNameFromHandle(m_textures[1]) : std::string();
std::vector<std::vector<std::uint32_t>> quads;
if (type == D3DPT_TRIANGLELIST)
for (size_t i = 0; i + 6 <= indices.size(); i += 6)
quads.push_back({ indices.begin() + i, indices.begin() + i + 6 });
else if (type == D3DPT_TRIANGLESTRIP && indices.size() == 4)
quads.push_back(indices);
for (const auto& quad : quads)
{
std::vector<std::uint32_t> corners;
for (std::uint32_t index : quad)
if (std::find(corners.begin(), corners.end(), index) == corners.end())
corners.push_back(index);
if (corners.size() != 4)
continue;
float sx[4], sy[4], u[4], v[4], mu[4] = {}, mv[4] = {};
bool inside = true;
for (int i = 0; i < 4; ++i)
{
const size_t offset = size_t(corners[i]) * stride;
if (offset + stride > vertexBytes) { inside = false; break; }
const uint8_t* vertex = vertices + offset;
float position[4] = { 0, 0, 0, 1 }, clip[4], camera[4], coord[4];
std::memcpy(position, vertex, 12);
std::memcpy(&u[i], vertex + layout.uv0, 4);
std::memcpy(&v[i], vertex + layout.uv0 + 4, 4);
transform_point(position, worldViewProj, clip);
if (clip[3] == 0.0f) { inside = false; break; }
sx[i] = (clip[0] / clip[3] * 0.5f + 0.5f) * float(width);
sy[i] = (0.5f - clip[1] / clip[3] * 0.5f) * float(height);
if (masked)
{
transform_point(position, worldView, camera);
camera[3] = 1.0f;
transform_point(camera, m_transforms[D3DTS_TEXTURE1], coord);
mu[i] = coord[0];
mv[i] = coord[1];
}
}
if (!inside)
continue;
UIRenderCommand command{UIRenderCommand::Image, 0, 0, 0, 0, argb};
command.su = *std::min_element(u, u + 4); command.eu = *std::max_element(u, u + 4);
command.sv = *std::min_element(v, v + 4); command.ev = *std::max_element(v, v + 4);
// Corners in the command's TL, TR, BL, BR order, by texture coordinate.
const float cu[4] = { command.su, command.eu, command.su, command.eu };
const float cv[4] = { command.sv, command.sv, command.ev, command.ev };
for (int c = 0; c < 4; ++c)
{
int best = 0;
float bestDistance = INFINITY;
for (int i = 0; i < 4; ++i)
{
const float distance = std::fabs(u[i] - cu[c]) + std::fabs(v[i] - cv[c]);
if (distance < bestDistance) { bestDistance = distance; best = i; }
}
command.qx[c] = sx[best]; command.qy[c] = sy[best];
command.mu[c] = mu[best]; command.mv[c] = mv[best];
}
command.x1 = *std::min_element(command.qx, command.qx + 4);
command.y1 = *std::min_element(command.qy, command.qy + 4);
command.x2 = *std::max_element(command.qx, command.qx + 4);
command.y2 = *std::max_element(command.qy, command.qy + 4);
command.text = texture;
command.quad = true;
command.mask = mask;
UIRenderAdd(std::move(command));
}
}
void record(D3DPRIMITIVETYPE type, UINT primitives, const uint8_t* vertices, size_t vertexBytes, UINT stride,
const std::vector<std::uint32_t>& indices)
{
if (type == D3DPT_POINTLIST || indices.empty() || !vertices)
if (type == D3DPT_POINTLIST || indices.empty() || !vertices || m_renderTarget != m_backBuffer)
return;
VertexLayout layout = fvf_layout(m_fvf);
if (!stride)
@@ -253,6 +600,14 @@ private:
if (layout.uv0 + 8 > int(stride)) layout.uv0 = -1;
if (layout.uv1 + 8 > int(stride)) layout.uv1 = -1;
// An orthographic projection over untransformed vertices is a UI-space draw (CPythonMiniMap's
// terrain tiles under CPythonGraphic::SetOrtho2D): it joins the UI command stream in order.
if (!layout.rhw && m_transforms[D3DTS_PROJECTION][11] == 0.0f)
{
record_ui_quads(type, vertices, vertexBytes, stride, layout, indices);
return;
}
Render3DDraw draw;
std::memcpy(draw.world, m_transforms[256], sizeof(draw.world)); // D3DTS_WORLD
std::memcpy(draw.view, m_transforms[D3DTS_VIEW], sizeof(draw.view));
@@ -340,7 +695,7 @@ private:
copy_color(draw.material_diffuse, m_material.Diffuse);
copy_color(draw.material_ambient, m_material.Ambient);
copy_color(draw.material_emissive, m_material.Emissive);
if (m_lights[0].Type == D3DLIGHT_DIRECTIONAL)
if (m_lightEnabled[0] && m_lights[0].Type == D3DLIGHT_DIRECTIONAL)
{
draw.light0 = true;
draw.light0_direction[0] = m_lights[0].Direction.x;
@@ -360,6 +715,11 @@ private:
IDirect3DBaseTexture8* m_textures[kStages] = {};
D3DMATERIAL8 m_material;
D3DLIGHT8 m_lights[8];
bool m_lightEnabled[8] = {};
IDirect3DSurface8* m_backBuffer = nullptr;
IDirect3DSurface8* m_depthBuffer = nullptr;
IDirect3DSurface8* m_renderTarget = nullptr;
IDirect3DSurface8* m_depthStencil = nullptr;
DWORD m_fvf = 0;
MtCpuVertexBuffer* m_stream = nullptr;
UINT m_streamStride = 0;