Files
mtgodot-poc/tests/port/port_grp_resource_test.cpp
T
shenleiandClaude Opus 5.5 65777331f7 port: GrpImageInstance/GrpExpandedImageInstance verbatim; fix SCREEN blend factor
Both units are the 40250 bodies; only the PDT-stream draw becomes
UIRenderAddImage (with the rendering mode). SCREEN/COLOR_DODGE were packed as
0x28 (INVDESTALPHA+ONE); 40250 uses INVDESTCOLOR+ONE (0x2A). OnIsType calls the
base OnIsType instead of 40250's recursing IsType. port.grp_resource covers the
quads, UVs, colour and blend mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:21:13 +09:00

212 lines
8.4 KiB
C++

// 40250 EterLib texture/image/buffer resources on the platform: CGraphicImageTexture (the DDS header check
// and its low-texture-memory mip bias, memory textures), CGraphicImage, CGraphicVertexBuffer/IndexBuffer.
#include "EterLib/StdAfx.h"
#include "EterLib/GrpImage.h"
#include "EterLib/GrpExpandedImageInstance.h"
#include "EterLib/GrpImageTexture.h"
#include "EterLib/GrpIndexBuffer.h"
#include "EterLib/GrpVertexBuffer.h"
#include "../../src/platform/EterLib/CpuBuffer.h"
#include "../../src/platform/EterLib/RecordingDevice.h"
#include "../../src/platform/EterLib/UIRenderCommands.h"
#include <cstdio>
#include <cstring>
#include <vector>
namespace {
int g_failures = 0;
void check(bool ok, const char* name)
{
if (!ok) {
std::fprintf(stderr, "FAIL %s\n", name);
++g_failures;
}
}
struct BaseAccess : CGraphicBase
{
static void SetDevice(IDirect3DDevice8* device) { ms_lpd3dDevice = device; }
static void SetLowTextureMemory(bool low) { ms_isLowTextureMemory = low; }
};
// "DDS " + DDSURFACEDESC2 (LINEARSIZE, MIPMAPCOUNT) + the DXT1 levels of a width x height texture.
std::vector<BYTE> MakeDDS(DWORD width, DWORD height, DWORD mips)
{
std::vector<BYTE> dds(128, 0);
memcpy(&dds[0], "DDS ", 4);
DWORD* h = reinterpret_cast<DWORD*>(&dds[4]);
h[0] = 124;
h[1] = 0x1 | 0x2 | 0x4 | 0x1000 | 0x20000 | 0x80000;
h[2] = height;
h[3] = width;
h[4] = width * height / 2;
h[6] = mips;
h[18] = 32;
h[19] = 0x4;
memcpy(&h[20], "DXT1", 4);
for (DWORD size = width * height / 2, i = 0; i < mips; ++i, size >>= 2)
dds.resize(dds.size() + std::max<DWORD>(size, 8), 0);
return dds;
}
struct ImageAccess : CGraphicImage
{
ImageAccess(const char* name) : CGraphicImage(name) {}
// CResource::Load with the pack bytes: OnLoad, then the state that stops a reference from loading again.
bool Load(int size, const void* data)
{
if (!OnLoad(size, data))
return false;
me_state = STATE_EXIST;
return true;
}
};
std::vector<BYTE> MakePNGHeader(DWORD width, DWORD height)
{
std::vector<BYTE> png = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 0, 0, 0, 13, 'I', 'H', 'D', 'R'};
for (DWORD v : {width, height})
for (int shift = 24; shift >= 0; shift -= 8)
png.push_back(BYTE(v >> shift));
png.resize(160, 0); // past the 128 bytes the DDS header check reads
return png;
}
}
int main()
{
IDirect3DDevice8* device = MtCreateRecordingDevice(800, 600);
BaseAccess::SetDevice(device);
BaseAccess::SetLowTextureMemory(false);
// A DXT .dds goes through CDXTCImage::LoadHeaderFromMemory + CreateDDSTexture; the handle names the file.
{
std::vector<BYTE> dds = MakeDDS(64, 32, 3);
CGraphicImageTexture texture;
texture.SetFileName("d:\\ymir work\\ui\\a.dds");
check(texture.CreateFromMemoryFile(UINT(dds.size()), dds.data(), D3DFMT_UNKNOWN, 0), "DDS create");
check(!texture.IsEmpty() && texture.GetWidth() == 64 && texture.GetHeight() == 32, "DDS size");
check(UIRenderTextureNameFromHandle(texture.GetD3DTexture()) == "d:\\ymir work\\ui\\a.dds", "DDS handle name");
}
// Low texture memory skips the top mip of a mipmapped .dds (40250 halves the reported size) ...
BaseAccess::SetLowTextureMemory(true);
{
std::vector<BYTE> dds = MakeDDS(64, 32, 3);
CGraphicImageTexture texture;
texture.SetFileName("b.dds");
check(texture.CreateFromMemoryFile(UINT(dds.size()), dds.data(), D3DFMT_UNKNOWN, 0), "low-memory DDS create");
check(texture.GetWidth() == 32 && texture.GetHeight() == 16, "low-memory DDS half size");
}
// ... but not one with a single level.
{
std::vector<BYTE> dds = MakeDDS(64, 32, 1);
CGraphicImageTexture texture;
texture.SetFileName("c.dds");
check(texture.CreateFromMemoryFile(UINT(dds.size()), dds.data(), D3DFMT_UNKNOWN, 0) && texture.GetWidth() == 64,
"low-memory single-level DDS keeps its size");
}
BaseAccess::SetLowTextureMemory(false);
// Other formats (D3DXCreateTextureFromFileInMemoryEx): the size comes from the file header.
{
std::vector<BYTE> png = MakePNGHeader(300, 200);
CGraphicImageTexture texture;
texture.SetFileName("d.png");
check(texture.CreateFromMemoryFile(UINT(png.size()), png.data(), D3DFMT_UNKNOWN, 0), "PNG create");
check(texture.GetWidth() == 300 && texture.GetHeight() == 200, "PNG size");
check(!texture.CreateFromMemoryFile(4, "junk", D3DFMT_UNKNOWN, 0), "unknown bytes refused");
}
// CGraphicImage::OnLoad (CResource::Load with the pack bytes) sets the rect from the texture; Clear empties it.
{
std::vector<BYTE> png = MakePNGHeader(40, 24);
ImageAccess image("e.png");
check(image.Load(int(png.size()), png.data()), "CGraphicImage OnLoad");
check(image.GetWidth() == 40 && image.GetHeight() == 24 && image.GetRectReference().right == 40, "CGraphicImage rect");
image.Clear();
check(image.IsEmpty() && image.GetRectReference().right == 0, "CGraphicImage Clear");
}
// CGraphicImageInstance draws the image rect as one quad at its position (40250's -0.5 pixel-centre
// offset, undone by +0.5 in the command); CGraphicExpandedImageInstance adds scale, the rendering
// rect and its blend mode.
{
std::vector<BYTE> png = MakePNGHeader(40, 20);
ImageAccess* image = new ImageAccess("f.png");
check(image->Load(int(png.size()), png.data()), "instance image load");
image->AddReference(); // held by the test, so releasing the instances never self-destructs it
CGraphicImageInstance instance;
instance.SetImagePointer(image);
instance.SetPosition(10, 5);
instance.SetDiffuseColor(1, 0, 0, 0.5f);
UIRenderBeginFrame();
instance.Render();
const std::vector<UIRenderCommand>& commands = UIRenderCommands();
check(commands.size() == 1, "image instance emits one quad");
if (commands.size() == 1)
{
const UIRenderCommand& c = commands[0];
check(c.text == "f.png" && c.qx[0] == 10 && c.qy[0] == 5 && c.qx[3] == 50 && c.qy[3] == 25, "image instance quad");
check(c.su == 0 && c.eu == 1 && c.argb == 0x80FF0000u && c.blend == 0, "image instance uv/colour");
}
CGraphicExpandedImageInstance expanded;
expanded.SetImagePointer(image);
check(expanded.IsType(CGraphicExpandedImageInstance::Type()) && expanded.IsType(CGraphicImageInstance::Type()), "expanded IsType");
expanded.SetPosition(0, 0);
expanded.SetScale(2, 1);
expanded.SetRenderingRect(0, 0, -0.5f, 0);
expanded.SetRenderingMode(CGraphicExpandedImageInstance::RENDERING_MODE_MODULATE);
UIRenderBeginFrame();
expanded.Render();
check(commands.size() == 1, "expanded instance emits one quad");
if (commands.size() == 1)
{
const UIRenderCommand& c = commands[0];
// Width 40 x 2 plus the right edge pulled in by half the image (-20).
check(c.qx[1] == 60 && c.qy[2] == 20 && c.eu == 0.5f, "expanded quad and rendering rect");
check(c.blend == CGraphicExpandedImageInstance::RENDERING_MODE_MODULATE, "expanded blend mode");
}
instance.Destroy();
expanded.Destroy();
}
// A memory texture (font page): Lock hands out A4R4G4B4 words, Unlock republishes them as ARGB32.
{
CGraphicImageTexture texture;
check(texture.Create(4, 2, D3DFMT_A4R4G4B4, 0) && !texture.IsEmpty(), "memory texture create");
int pitch = 0;
void* pixels = NULL;
check(texture.Lock(&pitch, &pixels) && pitch == 8, "memory texture lock");
static_cast<WORD*>(pixels)[5] = 0xF84C;
texture.Unlock();
UIMemoryTexture out;
const std::string name = UIRenderTextureName(&texture);
check(UIRenderMemoryTexture(name, &out) && out.width == 4 && out.argb[5] == 0xFF8844CC, "memory texture pixels");
check(name.find("@1") != std::string::npos, "Unlock bumps the revision");
}
// Vertex and index buffers keep their bytes in memory; Unlock bumps the revision the renderer checks.
{
CGraphicVertexBuffer vb;
check(vb.Create(3, D3DFVF_XYZ | D3DFVF_DIFFUSE, 0, D3DPOOL_MANAGED) && vb.GetVertexStride() == 16 && vb.GetVertexCount() == 3,
"vertex buffer create");
const float data[12] = {1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0};
check(vb.Copy(sizeof(data), data), "vertex buffer copy");
auto* cpu = static_cast<MtCpuVertexBuffer*>(vb.GetD3DVertexBuffer());
check(memcmp(cpu->bytes.data(), data, sizeof(data)) == 0 && cpu->revision == 1, "vertex bytes");
CGraphicIndexBuffer ib;
TFace faces[2] = {{{0, 1, 2}}, {{2, 1, 3}}};
check(ib.Create(2, faces) && ib.GetIndexCount() == 6, "index buffer from faces");
auto* idx = static_cast<MtCpuIndexBuffer*>(ib.GetD3DIndexBuffer());
const WORD* w = reinterpret_cast<const WORD*>(idx->bytes.data());
check(w[3] == 2 && w[5] == 3 && idx->format == D3DFMT_INDEX16, "index words");
}
return g_failures ? 1 : 0;
}