2V0-f: run the real system.py app loop in Godot

- system.py runs app.Loop() on the script fiber; each UIUpdate is one
  CPythonApplication::Process(). Logo -> Login, popup ESC/click and
  app.Exit teardown (launcher cleared first, as in 40250 Main) work.
- Image commands are base-texture quads with UVs (a .sub shares its parent
  texture); ImageInstance/ExpandedImageInstance OnRender and SetRenderingRect
  ported verbatim, so scale/rotation/rendering rect are drawn.
- CGraphicBase::GetColor / grp.GenerateColor ported (ThinBoard background).
- port.app_loop test, python_host_test app-loop checks, MT_TEST_MODE=python_ui.
- TODO: .fnt/CGraphicText font pipeline, ExpandedImage blend modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 13:38:13 +09:00
co-authored by Claude Opus 5.5
parent 5590714b16
commit 141206cb69
20 changed files with 916 additions and 57 deletions
+14 -3
View File
@@ -4,6 +4,7 @@
#include "EterLib/GrpBase.h"
#include "../PlatformStub.h"
#include <cstring>
#include "UIRenderCommands.h"
auto CGraphicBase::GetAvailableTextureMemory() -> DWORD
@@ -171,10 +172,20 @@ auto CGraphicBase::SetScreenEffectFlashing(float, const D3DXCOLOR &) -> void
MT_PLATFORM_STUB();
}
auto CGraphicBase::GetColor(float, float, float, float) -> DWORD
// 40250 GrpBase.cpp:458: the bytes B, G, R, A of a little-endian D3DCOLOR.
auto CGraphicBase::GetColor(float r, float g, float b, float a) -> DWORD
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<DWORD>();
BYTE argb[4] =
{
(BYTE) (255.0f * b),
(BYTE) (255.0f * g),
(BYTE) (255.0f * r),
(BYTE) (255.0f * a)
};
DWORD color;
std::memcpy(&color, argb, sizeof(color));
return color;
}
auto CGraphicBase::GetFaceCount() -> DWORD
@@ -5,6 +5,9 @@
#include "../PlatformStub.h"
#include "EterBase/CRC32.h"
#include "UIRenderCommands.h"
#include <cmath>
auto CGraphicExpandedImageInstance::Type() -> DWORD
{
@@ -19,7 +22,7 @@ CGraphicExpandedImageInstance::CGraphicExpandedImageInstance()
CGraphicExpandedImageInstance::~CGraphicExpandedImageInstance()
{
MT_PLATFORM_STUB();
Destroy();
}
auto CGraphicExpandedImageInstance::Destroy() -> void
@@ -53,10 +56,19 @@ auto CGraphicExpandedImageInstance::SetScale(float x, float y) -> void
m_v2Scale = D3DXVECTOR2(x, y);
}
auto CGraphicExpandedImageInstance::SetRenderingRect(float left, float top, float right, float bottom) -> void
// In 40250, the arguments are fractions of the image size (uiToolTip gauges, SetPercentage).
auto CGraphicExpandedImageInstance::SetRenderingRect(float fLeft, float fTop, float fRight, float fBottom) -> void
{
m_RenderingRect = {static_cast<LONG>(left), static_cast<LONG>(top),
static_cast<LONG>(right), static_cast<LONG>(bottom)};
if (IsEmpty())
return;
float fWidth = float(GetWidth());
float fHeight = float(GetHeight());
m_RenderingRect.left = fWidth * fLeft;
m_RenderingRect.top = fHeight * fTop;
m_RenderingRect.right = fWidth * fRight;
m_RenderingRect.bottom = fHeight * fBottom;
}
auto CGraphicExpandedImageInstance::SetRenderingMode(int mode) -> void
@@ -74,9 +86,68 @@ auto CGraphicExpandedImageInstance::Initialize() -> void
m_iRenderingMode = RENDERING_MODE_NORMAL;
}
// 40250 GrpExpandedImageInstance.cpp:29-142. The vertex and texture-coordinate arithmetic is
// verbatim; the D3D stream/blend-state calls become one UIRenderCommand with the four vertices.
auto CGraphicExpandedImageInstance::OnRender() -> void
{
CGraphicImageInstance::OnRender();
CGraphicImage * pImage = m_roImage.GetPointer();
CGraphicTexture * pTexture = pImage->GetTexturePointer();
if (pTexture->GetWidth() <= 0 || pTexture->GetHeight() <= 0)
return;
const RECT& c_rRect = pImage->GetRectReference();
float texReverseWidth = 1.0f / float(pTexture->GetWidth());
float texReverseHeight = 1.0f / float(pTexture->GetHeight());
float su = (c_rRect.left - m_RenderingRect.left) * texReverseWidth;
float sv = (c_rRect.top - m_RenderingRect.top) * texReverseHeight;
float eu = (c_rRect.left + m_RenderingRect.right + (c_rRect.right-c_rRect.left)) * texReverseWidth;
float ev = (c_rRect.top + m_RenderingRect.bottom + (c_rRect.bottom-c_rRect.top)) * texReverseHeight;
float x[4], y[4];
for (int i = 0; i < 4; ++i)
{
x[i] = m_v2Position.x-0.5f;
y[i] = m_v2Position.y-0.5f;
}
if (0.0f == m_fRotation)
{
float fimgWidth = float(pImage->GetWidth()) * m_v2Scale.x;
float fimgHeight = float(pImage->GetHeight()) * m_v2Scale.y;
x[0] -= m_RenderingRect.left;
y[0] -= m_RenderingRect.top;
x[1] += fimgWidth + m_RenderingRect.right;
y[1] -= m_RenderingRect.top;
x[2] -= m_RenderingRect.left;
y[2] += fimgHeight + m_RenderingRect.bottom;
x[3] += fimgWidth + m_RenderingRect.right;
y[3] += fimgHeight + m_RenderingRect.bottom;
// PORT: 40250 flips D3DRS_CULLMODE for a mirrored scale; the Godot canvas has no culling.
}
else
{
float fimgHalfWidth = float(pImage->GetWidth())/2.0f * m_v2Scale.x;
float fimgHalfHeight = float(pImage->GetHeight())/2.0f * m_v2Scale.y;
for (int i = 0; i < 4; ++i)
{
x[i] += m_v2Origin.x;
y[i] += m_v2Origin.y;
}
float fRadian = D3DXToRadian(m_fRotation);
x[0] += (-fimgHalfWidth*cosf(fRadian)) - (-fimgHalfHeight*sinf(fRadian));
y[0] += (-fimgHalfWidth*sinf(fRadian)) + (-fimgHalfHeight*cosf(fRadian));
x[1] += (+fimgHalfWidth*cosf(fRadian)) - (-fimgHalfHeight*sinf(fRadian));
y[1] += (+fimgHalfWidth*sinf(fRadian)) + (-fimgHalfHeight*cosf(fRadian));
x[2] += (-fimgHalfWidth*cosf(fRadian)) - (+fimgHalfHeight*sinf(fRadian));
y[2] += (-fimgHalfWidth*sinf(fRadian)) + (+fimgHalfHeight*cosf(fRadian));
x[3] += (+fimgHalfWidth*cosf(fRadian)) - (+fimgHalfHeight*sinf(fRadian));
y[3] += (+fimgHalfWidth*sinf(fRadian)) + (+fimgHalfHeight*cosf(fRadian));
}
UIRenderAddImage(pTexture, x, y, su, sv, eu, ev, UIRenderColor(m_DiffuseColor), m_iRenderingMode);
}
auto CGraphicExpandedImageInstance::OnSetImagePointer() -> void
@@ -6,7 +6,6 @@
#include "../PlatformStub.h"
#include "UIRenderCommands.h"
#include "EterBase/CRC32.h"
#include <algorithm>
auto CGraphicImageInstance::Type() -> DWORD
{
@@ -27,12 +26,15 @@ CGraphicImageInstance::CGraphicImageInstance()
CGraphicImageInstance::~CGraphicImageInstance()
{
MT_PLATFORM_STUB();
Destroy();
}
// 40250 Destroy(): drop the image reference, then Initialize() (white, at the origin).
auto CGraphicImageInstance::Destroy() -> void
{
m_roImage.Clear();
m_DiffuseColor = D3DXCOLOR(1, 1, 1, 1);
m_v2Position = D3DXVECTOR2(0, 0);
}
auto CGraphicImageInstance::Render() -> void
@@ -78,14 +80,13 @@ auto CGraphicImageInstance::GetHeight() -> int
auto CGraphicImageInstance::GetTexturePointer() -> CGraphicTexture *
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<CGraphicTexture *>();
CGraphicImage* pkImage = m_roImage.GetPointer();
return pkImage ? pkImage->GetTexturePointer() : NULL;
}
auto CGraphicImageInstance::GetTextureReference() const -> const CGraphicTexture &
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<const CGraphicTexture &>();
return m_roImage->GetTextureReference();
}
auto CGraphicImageInstance::GetGraphicImagePointer() -> CGraphicImage *
@@ -104,20 +105,31 @@ auto CGraphicImageInstance::Initialize() -> void
MT_PLATFORM_STUB();
}
// 40250 GrpImageInstance.cpp:44-96: the image's rect inside its texture (a .sub shares its parent's)
// becomes the texture coordinates of one quad.
auto CGraphicImageInstance::OnRender() -> void
{
CGraphicImage* image = m_roImage.GetPointer();
if (!image) return;
auto channel = [](float value) -> std::uint32_t {
return static_cast<std::uint32_t>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
};
const std::uint32_t color = (channel(m_DiffuseColor.a) << 24) |
(channel(m_DiffuseColor.r) << 16) | (channel(m_DiffuseColor.g) << 8) |
channel(m_DiffuseColor.b);
UIRenderCommand command{UIRenderCommand::Image, m_v2Position.x, m_v2Position.y,
m_v2Position.x + image->GetWidth(), m_v2Position.y + image->GetHeight(), color};
command.text = image->GetFileName();
UIRenderAdd(std::move(command));
CGraphicImage * pImage = m_roImage.GetPointer();
CGraphicTexture * pTexture = pImage->GetTexturePointer();
if (pTexture->GetWidth() <= 0 || pTexture->GetHeight() <= 0)
return;
float fimgWidth = pImage->GetWidth();
float fimgHeight = pImage->GetHeight();
const RECT& c_rRect = pImage->GetRectReference();
float texReverseWidth = 1.0f / float(pTexture->GetWidth());
float texReverseHeight = 1.0f / float(pTexture->GetHeight());
float su = c_rRect.left * texReverseWidth;
float sv = c_rRect.top * texReverseHeight;
float eu = (c_rRect.left + (c_rRect.right-c_rRect.left)) * texReverseWidth;
float ev = (c_rRect.top + (c_rRect.bottom-c_rRect.top)) * texReverseHeight;
const float x[4] = {m_v2Position.x-0.5f, m_v2Position.x + fimgWidth-0.5f,
m_v2Position.x-0.5f, m_v2Position.x + fimgWidth-0.5f};
const float y[4] = {m_v2Position.y-0.5f, m_v2Position.y-0.5f,
m_v2Position.y + fimgHeight-0.5f, m_v2Position.y + fimgHeight-0.5f};
UIRenderAddImage(pTexture, x, y, su, sv, eu, ev, UIRenderColor(m_DiffuseColor));
}
auto CGraphicImageInstance::OnSetImagePointer() -> void
@@ -9,7 +9,33 @@
namespace {
unsigned little16(const unsigned char* data) { return data[0] | (unsigned(data[1]) << 8); }
unsigned little32(const unsigned char* data) { return little16(data) | (little16(data + 2) << 16); }
unsigned big16(const unsigned char* data) { return (unsigned(data[0]) << 8) | data[1]; }
unsigned big32(const unsigned char* data) { return (unsigned(data[0]) << 24) | (unsigned(data[1]) << 16) | (unsigned(data[2]) << 8) | data[3]; }
// Walks the JPEG markers to the first SOFn (the frame header holds the size), as D3DX does when it
// sniffs a .jpg; login/loading backgrounds (serverlist.jpg, loading/*.jpg) are JPEGs.
bool jpeg_size(const unsigned char* data, unsigned size, unsigned* width, unsigned* height)
{
if (size < 4 || data[0] != 0xFF || data[1] != 0xD8) return false;
unsigned pos = 2;
while (pos + 4 <= size) {
if (data[pos] != 0xFF) return false;
const unsigned char marker = data[pos + 1];
if (marker == 0xFF) { ++pos; continue; }
if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) { pos += 2; continue; }
const unsigned length = big16(data + pos + 2);
if (length < 2) return false;
const bool sof = marker >= 0xC0 && marker <= 0xCF && marker != 0xC4 && marker != 0xC8 && marker != 0xCC;
if (sof) {
if (pos + 9 > size) return false;
*height = big16(data + pos + 5);
*width = big16(data + pos + 7);
return true;
}
pos += 2 + length;
}
return false;
}
}
CGraphicImageTexture::CGraphicImageTexture()
@@ -45,6 +71,9 @@ auto CGraphicImageTexture::CreateFromTexturePointer(const CGraphicTexture *sourc
m_width = source->GetWidth();
m_height = source->GetHeight();
m_bEmpty = source->IsEmpty();
// 40250 shares the source's D3D texture; the canvas names textures by pack path instead.
if (const auto* image = dynamic_cast<const CGraphicImageTexture*>(source))
m_stFileName = image->m_stFileName;
}
auto CGraphicImageTexture::CreateFromDiskFile(const char *, D3DFORMAT, DWORD) -> bool
@@ -62,6 +91,11 @@ auto CGraphicImageTexture::CreateFromMemoryFile(UINT size, const void *bytes, D3
height = little32(data + 12); width = little32(data + 16);
} else if (size >= 24 && std::memcmp(data, "\x89PNG\r\n\x1a\n", 8) == 0) {
width = big32(data + 16); height = big32(data + 20);
} else if (jpeg_size(data, size, &width, &height)) {
} else if (size >= 26 && data[0] == 'B' && data[1] == 'M') {
width = little32(data + 18);
const int bmp_height = static_cast<int>(little32(data + 22)); // negative: top-down rows
height = static_cast<unsigned>(bmp_height < 0 ? -bmp_height : bmp_height);
} else if (size >= 18 && (data[2] == 2 || data[2] == 10 || data[2] == 3)) {
width = little16(data + 12); height = little16(data + 14);
}
@@ -82,6 +116,17 @@ auto CGraphicImageTexture::SetFileName(const char *name) -> void
m_stFileName = name ? name : "";
}
// PORT: where 40250 binds pTexture->GetD3DTexture(), the canvas gets the pack path of the file the
// texture was decoded from (m_stFileName is protected, hence the derived accessor).
std::string UIRenderTextureName(const CGraphicTexture* texture)
{
struct Access : CGraphicImageTexture {
static const std::string& name(const CGraphicImageTexture* t) { return t->*(&Access::m_stFileName); }
};
const auto* image = dynamic_cast<const CGraphicImageTexture*>(texture);
return image ? Access::name(image) : std::string();
}
auto CGraphicImageTexture::Lock(int *, void **, int) -> bool
{
MT_PLATFORM_STUB();
@@ -1,5 +1,7 @@
#include "UIRenderCommands.h"
#include <algorithm>
namespace {
std::vector<UIRenderCommand> commands;
int canvas_width = 0;
@@ -34,3 +36,21 @@ void UIRenderRestoreClip() {
clip_x1 = saved_x1; clip_y1 = saved_y1;
clip_x2 = saved_x2; clip_y2 = saved_y2;
}
void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4],
float su, float sv, float eu, float ev, std::uint32_t argb, int blend) {
UIRenderCommand command{UIRenderCommand::Image, 0, 0, 0, 0, argb};
for (int i = 0; i < 4; ++i) {
command.qx[i] = x[i] + 0.5f;
command.qy[i] = y[i] + 0.5f;
}
command.x1 = std::min({command.qx[0], command.qx[1], command.qx[2], command.qx[3]});
command.y1 = std::min({command.qy[0], command.qy[1], command.qy[2], command.qy[3]});
command.x2 = std::max({command.qx[0], command.qx[1], command.qx[2], command.qx[3]});
command.y2 = std::max({command.qy[0], command.qy[1], command.qy[2], command.qy[3]});
command.text = UIRenderTextureName(texture);
command.quad = true;
command.su = su; command.sv = sv; command.eu = eu; command.ev = ev;
command.blend = blend;
UIRenderAdd(std::move(command));
}
@@ -12,8 +12,32 @@ struct UIRenderCommand {
std::uint32_t argb;
float clip_x1 = 0, clip_y1 = 0, clip_x2 = 0, clip_y2 = 0;
std::string text;
// Image only: text is the texture's file, drawn as the quad (vertices 0..3 = TL, TR, BL, BR)
// with texture coordinates su,sv..eu,ev; x1..y2 is the quad's bounding box.
bool quad = false;
float qx[4] = {}, qy[4] = {};
float su = 0, sv = 0, eu = 1, ev = 1;
int blend = 0; // CGraphicExpandedImageInstance::RENDERING_MODE_*
};
// D3DXCOLOR (0..1 floats) -> the 0xAARRGGBB the commands carry.
template <class Color>
std::uint32_t UIRenderColor(const Color& color) {
auto channel = [](float value) -> std::uint32_t {
return static_cast<std::uint32_t>((value < 0 ? 0 : value > 1 ? 1 : value) * 255.0f + 0.5f);
};
return (channel(color.a) << 24) | (channel(color.r) << 16) | (channel(color.g) << 8) | channel(color.b);
}
class CGraphicTexture;
std::string UIRenderTextureName(const CGraphicTexture* texture);
// A textured quad from 40250's TPDTVertex[4] (TL, TR, BL, BR) positions and texture coordinates.
// PORT: D3D8 puts pixel centres on integers, which is why 40250 subtracts 0.5 from every vertex; the
// Godot canvas puts them on .5, so the quad is shifted back by +0.5 here.
void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4],
float su, float sv, float eu, float ev, std::uint32_t argb, int blend = 0);
void UIRenderSetSize(int width, int height);
void UIRenderGetSize(unsigned* width, unsigned* height);
void UIRenderBeginFrame();