// 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 #include #include 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 MakeDDS(DWORD width, DWORD height, DWORD mips) { std::vector dds(128, 0); memcpy(&dds[0], "DDS ", 4); DWORD* h = reinterpret_cast(&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(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 MakePNGHeader(DWORD width, DWORD height) { std::vector 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 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 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 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 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 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 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& 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(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(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(ib.GetD3DIndexBuffer()); const WORD* w = reinterpret_cast(idx->bytes.data()); check(w[3] == 2 && w[5] == 3 && idx->format == D3DFMT_INDEX16, "index words"); } return g_failures ? 1 : 0; }