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>
746 lines
26 KiB
C++
746 lines
26 KiB
C++
#include "RecordingDevice.h"
|
|
#include "RenderCommands3D.h"
|
|
#include "UIRenderCommands.h"
|
|
#include "CpuBuffer.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
|
|
namespace {
|
|
std::mutex g_draws_mutex;
|
|
std::vector<Render3DDraw> g_draws;
|
|
|
|
struct VertexLayout {
|
|
unsigned stride = 0;
|
|
bool rhw = false;
|
|
int normal = -1, diffuse = -1, uv0 = -1, uv1 = -1;
|
|
};
|
|
|
|
// The element offsets of a fixed-function FVF (D3DXGetFVFVertexSize's order).
|
|
VertexLayout fvf_layout(DWORD fvf)
|
|
{
|
|
VertexLayout layout;
|
|
unsigned offset = 0;
|
|
switch (fvf & D3DFVF_POSITION_MASK)
|
|
{
|
|
case D3DFVF_XYZ: offset = 12; break;
|
|
case D3DFVF_XYZRHW: offset = 16; layout.rhw = true; break;
|
|
case D3DFVF_XYZB1: offset = 16; break;
|
|
case D3DFVF_XYZB2: offset = 20; break;
|
|
case D3DFVF_XYZB3: offset = 24; break;
|
|
case D3DFVF_XYZB4: offset = 28; break;
|
|
case D3DFVF_XYZB5: offset = 32; break;
|
|
}
|
|
if (fvf & D3DFVF_NORMAL) { layout.normal = offset; offset += 12; }
|
|
if (fvf & D3DFVF_PSIZE) offset += 4;
|
|
if (fvf & D3DFVF_DIFFUSE) { layout.diffuse = offset; offset += 4; }
|
|
if (fvf & D3DFVF_SPECULAR) offset += 4;
|
|
const unsigned texCount = (fvf & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;
|
|
for (unsigned i = 0; i < texCount; ++i)
|
|
{
|
|
static const unsigned coordSize[4] = { 8, 12, 16, 4 };
|
|
if (i == 0) layout.uv0 = offset;
|
|
if (i == 1) layout.uv1 = offset;
|
|
offset += coordSize[(fvf >> (16 + i * 2)) & 3];
|
|
}
|
|
layout.stride = offset;
|
|
return layout;
|
|
}
|
|
|
|
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:
|
|
RecordingDevice(int width, int height) : m_width(width), m_height(height)
|
|
{
|
|
static const float identity[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
|
|
for (auto& matrix : m_transforms)
|
|
std::memcpy(matrix, identity, sizeof(identity));
|
|
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; }
|
|
ULONG Release() override
|
|
{
|
|
const ULONG refs = --m_refs;
|
|
if (!refs)
|
|
delete this;
|
|
return refs;
|
|
}
|
|
|
|
// PORT: a D3D8 HAL on a T&L card (D3DDEVCAPS_HWTRANSFORMANDLIGHT), 8 lights, 4 texture stages.
|
|
HRESULT GetDeviceCaps(D3DCAPS8* caps) override
|
|
{
|
|
std::memset(caps, 0, sizeof(*caps));
|
|
caps->DevCaps = D3DDEVCAPS_HWTRANSFORMANDLIGHT;
|
|
caps->MaxActiveLights = 8;
|
|
caps->MaxTextureBlendStages = 4;
|
|
caps->MaxSimultaneousTextures = 4;
|
|
caps->MaxTextureWidth = caps->MaxTextureHeight = 4096;
|
|
caps->MaxAnisotropy = 16;
|
|
caps->MaxPrimitiveCount = 0xFFFFF;
|
|
caps->MaxVertexIndex = 0xFFFFF;
|
|
caps->MaxStreams = 8;
|
|
caps->MaxStreamStride = 255;
|
|
caps->TextureAddressCaps = D3DPTADDRESSCAPS_BORDER | D3DPTADDRESSCAPS_CLAMP | D3DPTADDRESSCAPS_WRAP | D3DPTADDRESSCAPS_MIRROR;
|
|
return S_OK;
|
|
}
|
|
HRESULT GetViewport(D3DVIEWPORT8* viewport) override
|
|
{
|
|
*viewport = { 0, 0, DWORD(m_width), DWORD(m_height), 0.0f, 1.0f };
|
|
return S_OK;
|
|
}
|
|
// PORT: CPU memory has no texture budget; report the 64MB of a mid-range 2004 card.
|
|
UINT GetAvailableTextureMem() override { return 64u << 20; }
|
|
HRESULT BeginScene() override { return S_OK; }
|
|
HRESULT EndScene() override { return S_OK; }
|
|
|
|
HRESULT SetTransform(D3DTRANSFORMSTATETYPE state, const D3DMATRIX* matrix) override
|
|
{
|
|
if (unsigned(state) < kTransforms && matrix)
|
|
std::memcpy(m_transforms[state], matrix, sizeof(float) * 16);
|
|
return S_OK;
|
|
}
|
|
HRESULT GetTransform(D3DTRANSFORMSTATETYPE state, D3DMATRIX* matrix) override
|
|
{
|
|
if (unsigned(state) < kTransforms)
|
|
std::memcpy(matrix, m_transforms[state], sizeof(float) * 16);
|
|
return S_OK;
|
|
}
|
|
HRESULT SetMaterial(const D3DMATERIAL8* material) override { m_material = *material; return S_OK; }
|
|
HRESULT SetLight(DWORD index, const D3DLIGHT8* light) override
|
|
{
|
|
if (index < 8)
|
|
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)
|
|
m_renderStates[state] = value;
|
|
return S_OK;
|
|
}
|
|
HRESULT SetTexture(DWORD stage, IDirect3DBaseTexture8* texture) override
|
|
{
|
|
if (stage < kStages)
|
|
m_textures[stage] = texture;
|
|
return S_OK;
|
|
}
|
|
HRESULT SetTextureStageState(DWORD stage, D3DTEXTURESTAGESTATETYPE type, DWORD value) override
|
|
{
|
|
if (stage < kStages && unsigned(type) < kStageStates)
|
|
m_stageStates[stage][type] = value;
|
|
return S_OK;
|
|
}
|
|
// PORT: fixed function only; CGraphicDevice's stream "shaders" are the FVFs they describe.
|
|
HRESULT SetVertexShader(DWORD handle) override { m_fvf = handle; return S_OK; }
|
|
HRESULT SetPixelShader(DWORD) override { return S_OK; }
|
|
HRESULT SetVertexShaderConstant(DWORD, const void*, DWORD) override { return S_OK; }
|
|
HRESULT SetPixelShaderConstant(DWORD, const void*, DWORD) override { return S_OK; }
|
|
HRESULT SetStreamSource(UINT stream, IDirect3DVertexBuffer8* buffer, UINT stride) override
|
|
{
|
|
if (stream == 0)
|
|
{
|
|
m_stream = static_cast<MtCpuVertexBuffer*>(buffer);
|
|
m_streamStride = stride;
|
|
}
|
|
return S_OK;
|
|
}
|
|
HRESULT SetIndices(IDirect3DIndexBuffer8* indices, UINT baseVertex) override
|
|
{
|
|
m_indices = static_cast<MtCpuIndexBuffer*>(indices);
|
|
m_baseVertex = baseVertex;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT DrawPrimitive(D3DPRIMITIVETYPE type, UINT startVertex, UINT primitiveCount) override
|
|
{
|
|
if (!m_stream)
|
|
return E_FAIL;
|
|
const UINT count = index_count(type, primitiveCount);
|
|
std::vector<std::uint32_t> indices(count);
|
|
for (UINT i = 0; i < count; ++i)
|
|
indices[i] = startVertex + i;
|
|
record(type, primitiveCount, m_stream->bytes.data(), m_stream->bytes.size(), m_streamStride, indices);
|
|
return S_OK;
|
|
}
|
|
HRESULT DrawIndexedPrimitive(D3DPRIMITIVETYPE type, UINT, UINT, UINT startIndex, UINT primitiveCount) override
|
|
{
|
|
if (!m_stream || !m_indices)
|
|
return E_FAIL;
|
|
std::vector<std::uint32_t> indices;
|
|
if (!read_indices(m_indices->bytes.data(), m_indices->bytes.size(), m_indices->format, startIndex,
|
|
index_count(type, primitiveCount), m_baseVertex, &indices))
|
|
return E_FAIL;
|
|
record(type, primitiveCount, m_stream->bytes.data(), m_stream->bytes.size(), m_streamStride, indices);
|
|
return S_OK;
|
|
}
|
|
HRESULT DrawPrimitiveUP(D3DPRIMITIVETYPE type, UINT primitiveCount, const void* vertices, UINT stride) override
|
|
{
|
|
const UINT count = index_count(type, primitiveCount);
|
|
std::vector<std::uint32_t> indices(count);
|
|
for (UINT i = 0; i < count; ++i)
|
|
indices[i] = i;
|
|
record(type, primitiveCount, static_cast<const uint8_t*>(vertices), size_t(count) * stride, stride, indices);
|
|
return S_OK;
|
|
}
|
|
HRESULT DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE type, UINT minVertex, UINT numVertices, UINT primitiveCount,
|
|
const void* indexData, D3DFORMAT indexFormat, const void* vertices, UINT stride) override
|
|
{
|
|
const UINT count = index_count(type, primitiveCount);
|
|
const size_t indexSize = indexFormat == D3DFMT_INDEX32 ? 4 : 2;
|
|
std::vector<std::uint32_t> indices;
|
|
if (!read_indices(static_cast<const uint8_t*>(indexData), count * indexSize, indexFormat, 0, count, 0, &indices))
|
|
return E_FAIL;
|
|
record(type, primitiveCount, static_cast<const uint8_t*>(vertices), size_t(minVertex + numVertices) * stride, stride, indices);
|
|
return S_OK;
|
|
}
|
|
|
|
private:
|
|
static constexpr unsigned kTransforms = 512; // D3DTS_WORLDMATRIX(0..255) = 256..511
|
|
static constexpr unsigned kRenderStates = 256;
|
|
static constexpr unsigned kStages = 8;
|
|
static constexpr unsigned kStageStates = 32;
|
|
|
|
static UINT index_count(D3DPRIMITIVETYPE type, UINT primitives)
|
|
{
|
|
switch (type)
|
|
{
|
|
case D3DPT_POINTLIST: return primitives;
|
|
case D3DPT_LINELIST: return primitives * 2;
|
|
case D3DPT_LINESTRIP: return primitives + 1;
|
|
case D3DPT_TRIANGLELIST: return primitives * 3;
|
|
case D3DPT_TRIANGLESTRIP: case D3DPT_TRIANGLEFAN: return primitives + 2;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
static bool read_indices(const uint8_t* bytes, size_t size, D3DFORMAT format, UINT start, UINT count, UINT base,
|
|
std::vector<std::uint32_t>* out)
|
|
{
|
|
const size_t indexSize = format == D3DFMT_INDEX32 ? 4 : 2;
|
|
if ((size_t(start) + count) * indexSize > size)
|
|
return false;
|
|
out->resize(count);
|
|
for (UINT i = 0; i < count; ++i)
|
|
{
|
|
const uint8_t* p = bytes + (size_t(start) + i) * indexSize;
|
|
std::uint32_t index = indexSize == 4 ? (p[0] | (p[1] << 8) | (p[2] << 16) | (std::uint32_t(p[3]) << 24)) : (p[0] | (p[1] << 8));
|
|
(*out)[i] = index + base;
|
|
}
|
|
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 || m_renderTarget != m_backBuffer)
|
|
return;
|
|
VertexLayout layout = fvf_layout(m_fvf);
|
|
if (!stride)
|
|
stride = layout.stride;
|
|
if (!stride || stride < (layout.rhw ? 16u : 12u))
|
|
return;
|
|
// Elements past the stream's stride live in another stream (CreatePTStreamVertexShader).
|
|
if (layout.normal + 12 > int(stride)) layout.normal = -1;
|
|
if (layout.diffuse + 4 > int(stride)) layout.diffuse = -1;
|
|
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));
|
|
std::memcpy(draw.proj, m_transforms[D3DTS_PROJECTION], sizeof(draw.proj));
|
|
draw.texture0 = UIRenderTextureNameFromHandle(m_textures[0]);
|
|
draw.texture1 = UIRenderTextureNameFromHandle(m_textures[1]);
|
|
draw.pretransformed = layout.rhw;
|
|
draw.lines = type == D3DPT_LINELIST || type == D3DPT_LINESTRIP;
|
|
|
|
// Rebase: copy only the vertices the indices reference.
|
|
std::uint32_t lo = indices[0], hi = indices[0];
|
|
for (std::uint32_t index : indices)
|
|
{
|
|
lo = std::min(lo, index);
|
|
hi = std::max(hi, index);
|
|
}
|
|
if ((size_t(hi) + 1) * stride > vertexBytes)
|
|
return;
|
|
const size_t count = size_t(hi - lo) + 1;
|
|
draw.positions.resize(count * 3);
|
|
if (layout.rhw) draw.rhw.resize(count);
|
|
if (layout.normal >= 0) draw.normals.resize(count * 3);
|
|
if (layout.uv0 >= 0) draw.uv0.resize(count * 2);
|
|
if (layout.uv1 >= 0) draw.uv1.resize(count * 2);
|
|
if (layout.diffuse >= 0) draw.diffuse.resize(count);
|
|
for (size_t i = 0; i < count; ++i)
|
|
{
|
|
const uint8_t* v = vertices + (lo + i) * stride;
|
|
std::memcpy(&draw.positions[i * 3], v, 12);
|
|
if (layout.rhw) std::memcpy(&draw.rhw[i], v + 12, 4);
|
|
if (layout.normal >= 0) std::memcpy(&draw.normals[i * 3], v + layout.normal, 12);
|
|
if (layout.uv0 >= 0) std::memcpy(&draw.uv0[i * 2], v + layout.uv0, 8);
|
|
if (layout.uv1 >= 0) std::memcpy(&draw.uv1[i * 2], v + layout.uv1, 8);
|
|
if (layout.diffuse >= 0) std::memcpy(&draw.diffuse[i], v + layout.diffuse, 4);
|
|
}
|
|
|
|
// Strips and fans become lists; D3D flips the winding of every odd strip triangle.
|
|
switch (type)
|
|
{
|
|
case D3DPT_TRIANGLESTRIP:
|
|
for (UINT i = 0; i < primitives; ++i)
|
|
{
|
|
const std::uint32_t a = indices[i] - lo, b = indices[i + 1] - lo, c = indices[i + 2] - lo;
|
|
if (i & 1) draw.indices.insert(draw.indices.end(), { b, a, c });
|
|
else draw.indices.insert(draw.indices.end(), { a, b, c });
|
|
}
|
|
break;
|
|
case D3DPT_TRIANGLEFAN:
|
|
for (UINT i = 0; i < primitives; ++i)
|
|
draw.indices.insert(draw.indices.end(), { indices[0] - lo, indices[i + 1] - lo, indices[i + 2] - lo });
|
|
break;
|
|
case D3DPT_LINESTRIP:
|
|
for (UINT i = 0; i < primitives; ++i)
|
|
draw.indices.insert(draw.indices.end(), { indices[i] - lo, indices[i + 1] - lo });
|
|
break;
|
|
default:
|
|
for (std::uint32_t index : indices)
|
|
draw.indices.push_back(index - lo);
|
|
break;
|
|
}
|
|
|
|
draw.alpha_blend = m_renderStates[D3DRS_ALPHABLENDENABLE];
|
|
draw.src_blend = m_renderStates[D3DRS_SRCBLEND];
|
|
draw.dest_blend = m_renderStates[D3DRS_DESTBLEND];
|
|
draw.alpha_test = m_renderStates[D3DRS_ALPHATESTENABLE];
|
|
draw.alpha_ref = m_renderStates[D3DRS_ALPHAREF];
|
|
draw.alpha_func = m_renderStates[D3DRS_ALPHAFUNC];
|
|
draw.cull_mode = m_renderStates[D3DRS_CULLMODE];
|
|
draw.z_enable = m_renderStates[D3DRS_ZENABLE];
|
|
draw.z_write = m_renderStates[D3DRS_ZWRITEENABLE];
|
|
draw.z_func = m_renderStates[D3DRS_ZFUNC];
|
|
draw.lighting = m_renderStates[D3DRS_LIGHTING];
|
|
draw.texture_factor = m_renderStates[D3DRS_TEXTUREFACTOR];
|
|
draw.fog_enable = m_renderStates[D3DRS_FOGENABLE];
|
|
draw.ambient = m_renderStates[D3DRS_AMBIENT];
|
|
for (int stage = 0; stage < 2; ++stage)
|
|
{
|
|
draw.color_op[stage] = m_stageStates[stage][D3DTSS_COLOROP];
|
|
draw.color_arg1[stage] = m_stageStates[stage][D3DTSS_COLORARG1];
|
|
draw.color_arg2[stage] = m_stageStates[stage][D3DTSS_COLORARG2];
|
|
draw.alpha_op[stage] = m_stageStates[stage][D3DTSS_ALPHAOP];
|
|
draw.alpha_arg1[stage] = m_stageStates[stage][D3DTSS_ALPHAARG1];
|
|
draw.alpha_arg2[stage] = m_stageStates[stage][D3DTSS_ALPHAARG2];
|
|
}
|
|
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_lightEnabled[0] && m_lights[0].Type == D3DLIGHT_DIRECTIONAL)
|
|
{
|
|
draw.light0 = true;
|
|
draw.light0_direction[0] = m_lights[0].Direction.x;
|
|
draw.light0_direction[1] = m_lights[0].Direction.y;
|
|
draw.light0_direction[2] = m_lights[0].Direction.z;
|
|
copy_color(draw.light0_diffuse, m_lights[0].Diffuse);
|
|
copy_color(draw.light0_ambient, m_lights[0].Ambient);
|
|
}
|
|
Render3DAdd(std::move(draw));
|
|
}
|
|
|
|
ULONG m_refs = 1;
|
|
int m_width, m_height;
|
|
float m_transforms[kTransforms][16];
|
|
DWORD m_renderStates[kRenderStates] = {};
|
|
DWORD m_stageStates[kStages][kStageStates] = {};
|
|
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;
|
|
MtCpuIndexBuffer* m_indices = nullptr;
|
|
UINT m_baseVertex = 0;
|
|
};
|
|
}
|
|
|
|
IDirect3DDevice8* MtCreateRecordingDevice(int width, int height) { return new RecordingDevice(width, height); }
|
|
|
|
void Render3DBeginFrame()
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_draws_mutex);
|
|
g_draws.clear();
|
|
}
|
|
|
|
void Render3DAdd(Render3DDraw draw)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_draws_mutex);
|
|
g_draws.push_back(std::move(draw));
|
|
}
|
|
|
|
const std::vector<Render3DDraw>& Render3DDraws() { return g_draws; }
|