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>
212 lines
8.4 KiB
C++
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;
|
|
}
|