Files
EVEngine/src/modules/image/Image.cpp
T
西风逍遥游 34d1fdb63b refactor(modules): clear remaining back-edges and split the largest TUs (#127)
* chore(ci): add clang-format gate, CODEOWNERS and agent collaboration rules

* refactor(modules): clear remaining back-edges and split the largest TUs

Three known dependency back-edges are gone (module_depgraph.py KNOWN_BACK_EDGES
is now empty; --check passes with 0 violations, 0 cycles):

- window -> graphics: window queries the new IWindowSurfaceHost capability
  (common/WindowSurfaceHost.h) instead of holding a Graphics pointer;
  Window::setGraphics was removed and all call sites updated.
- window -> image: Window::setIcon was replaced by setIconRGBA(const uint8_t*,
  w, h); the window no longer decodes ImageData.
- scene -> graphics: pickScreenAt / collectFrustumIdsAt implementations moved
  to graphics/ScenePicking.cpp (graphics -> scene is a legal downward edge);
  scene now depends only on spatial.

TU splits (pure moves, verified identical method sets and brace balance):

- vulkan/Graphics.cpp (5118 lines) -> Graphics.cpp (lifecycle), GraphicsPipeline,
  Graphics2D, Graphics3D, GraphicsMesh + shared GraphicsInternal.h.
- scene/Scene.cpp -> Scene.cpp (script layer / host lifecycle / serialization)
  + SceneNodes.cpp + shared SceneInternal.h.

Verified with a real build (MSVC 19.51, prebuilt third-party): full build green;
window.* and Scene.* suites pass, including scene picking/culling and GPU cases.

* refactor(graphics): narrow Graphics interfaces and clear public-header leaks

Step 4 — interface segregation:
- New lean interfaces: IGraphics2D, IGraphics3D, IResourceFactory, IPostFX
  (pure virtuals, forward-declared types only; graphics/Color.h extracted from
  Canvas.h so modules that only need the color type stop pulling Canvas.h).
- Graphics now implements all four facets; consumers can depend on the narrow
  interface they use instead of the 129-method god class.
- Migrated voxel (VoxelWorld::drawVisible -> IGraphics3D*) and model3d
  (ModelRenderer::buildRenderable(s) -> IResourceFactory&).
- Graphics.h slims from 24 sub-header includes to a handful of small type
  headers + forward declarations; the destructor moved out-of-line so
  unique_ptr effect members are destroyed where types are complete.

Step 5 — header leaks (module_depgraph.py '*' marks: 14 -> 7):
- map/TileLayer.h, particles/ParticleEmitter.h, avatar/AvatarInstance.h,
  animation/SpineAnim.h, map/TileSystem.h, model3d/ModelData.h,
  model3d/ModelRenderer.h, audio/Audio.h + Source.h, image/ImageData.h +
  Image.h, dialogue/Dialogue.h, procgen Palette/OutputSpec/TextureRecipe/
  PbrMaterial -> forward declarations, includes moved to .cpp.
- voxel/VoxelWorld.h: terrain sampler Pimpl'd (unique_ptr + out-of-line
  destructor), removing the procgen header dependency.
- gpgpu/vulkan backend headers stop pulling graphics/vulkan/Graphics.h.
- Remaining '*' marks are deliberate: tiny POD/enum headers (GridConfig,
  MapObject, BlendMode, Color), an internal backend header
  (GraphicsInternal.h -> filesystem), and lean interface references.

Verified with a full clean build (MSVC) and tests: window/Scene/voxel/
RenderSystem/Quad/shader/TextureSampler/resourceLifetime pass; broad suite
only shows the pre-existing PHYSFS-init filesystem-environment failures.

* feat(resources): revive ResourceManager as unified IAssetReloader-backed cache

* fix(resources): leak ResourceManager singleton to avoid exit-time teardown crashes

Cached CPU resources may own third-party handles (FreeType faces, Assimp
scenes, image decode handlers) whose libraries are torn down at process
exit in an unspecified TU order; destroying cached entries from the
singleton destructor can segfault at exit (seen on CI Windows in
model3d.materialApi.objMtl). Keep the singleton alive until OS reclaim;
explicit unload()/clear() still release entries during the run.

* refactor(devtools): consume business modules through capability interfaces

DevTools / cmdline no longer include business-module headers, so trimming
scene / physics / procgen / particles / audio / ui / graphics no longer
requires disabling devtools, and devtools can be switched off with
-DEVENGINE_MODULE_DEVTOOLS=OFF.

New capability interfaces in common/:
- ISceneQuery (scene) - host/node inspection + mutation + transform sync
- IRenderCapture (graphics) - render status, PNG capture, camera control,
  visible-entity snapshot, entity-ID mask, G-buffer PNGs
- IPhysicsQuery (physics) - 2D world lifecycle + ray casts
- IProcgenQuery (procgen) - recipe listing, map/mesh generation
- IParticlesQuery (particles) - emitter status + one-shot emit
- IAudioQuery (audio) - volume / stop-all
- IEditorHost (ui) - declarative editor host (used by cmdline too)

Providers register in their module constructors. DevTools rewires McpServer
(render/scene/procgen/physics/particles/audio/host tools), SceneInspect
(now delegates to ISceneQuery + IRenderCapture) and RenderVision (uses
IRenderCapture::capturePngDataUrl); cmdline/Mcp.cpp uses IEditorHost.

manifest: devtools DEPS graphics scene physics procgen particles audio ui
image event filesystem -> event; cmdline DEPS filesystem ui -> filesystem.

Verified: full build green; devtools.mcp.* tests pass; broad suite shows
only the pre-existing PHYSFS-init filesystem-environment failures.

* style: apply clang-format to changed lines after dev merge

* fix(webgpu): include RenderControl/TextureSampler explicitly after Graphics.h slimming

* fix(webgpu): provide newFont/print stubs to satisfy IGraphics2D/IResourceFactory vtables

* fix(common): make ProcgenQuery.h self-sufficient (cstdint/utility for GCC)
2026-08-20 05:53:33 +08:00

239 lines
6.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Copyright (c) 2006-2021 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "Image.h"
#include "common/Exception.h"
#include "common/Resource.h"
#include "common/config.h"
#include "medialoader/image/PNGHandler.h"
#include "medialoader/image/STBHandler.h"
#include "medialoader/image/EXRHandler.h"
#include "medialoader/image/ddsHandler.h"
#include "medialoader/image/PVRHandler.h"
#include "medialoader/image/KTXHandler.h"
#include "medialoader/image/PKMHandler.h"
#include "medialoader/image/ASTCHandler.h"
#include <simplesquirrel/simplesquirrel.hpp>
namespace eve
{
namespace image
{
Module_IMPL(Image, new Image());
Image::Image()
{
using namespace medialoader;
float16Init(); // Makes sure half-float conversions can be used.
formatHandlers = {
new PNGHandler,
new STBHandler,
new EXRHandler,
new DDSHandler,
new PVRHandler,
new KTXHandler,
new PKMHandler,
new ASTCHandler,
};
}
Image::~Image()
{
// ImageData objects reference the FormatHandlers in our list, so we should
// release them instead of deleting them completely here.
// TODO: Check if this is necessary
// for (FormatHandler *handler : formatHandlers)
// handler->release();
}
ImageData *Image::newImageData(Data *data)
{
return new ImageData(data);
}
ImageData *Image::newImageDataFromFile(std::string path)
{
if (path.empty())
throw eve::Exception("Image::newImageDataFromFile: empty path");
eve::Resource *resource = eve::ResourceManager::getInstance().get(path);
if (!resource)
throw eve::Exception("Could not load image file: %s", path.c_str());
return static_cast<ImageData *>(resource);
}
ImageData *Image::newImageData(int width, int height, std::string format)
{
return new ImageData(width, height, format);
}
ImageData *Image::newImageData(int width, int height, std::string format, void *data, bool own)
{
return new ImageData(width, height, format, data, own);
}
bool Image::isCompressed(Data *data)
{
for (FormatHandler *handler : formatHandlers)
{
if (handler->canParseCompressed((const char*)data->getData(), data->getSize()))
return true;
}
return false;
}
const std::list<medialoader::FormatHandler *> &Image::getFormatHandlers() const
{
return formatHandlers;
}
ImageData *Image::newPastedImageData(ImageData *src, int sx, int sy, int w, int h)
{
ImageData *res = newImageData(w, h, src->getFormat());
try
{
res->paste(src, 0, 0, sx, sy, w, h);
}
catch (eve::Exception &)
{
// res->release();
throw;
}
return res;
}
std::vector<eve::ref<ImageData>> Image::newCubeFaces(ImageData *src)
{
// The faces array is always ordered +x, -x, +y, -y, +z, -z.
std::vector<eve::ref<ImageData>> faces;
int totalW = src->getWidth();
int totalH = src->getHeight();
if (totalW % 3 == 0 && totalH % 4 == 0 && totalW / 3 == totalH / 4)
{
// +y
// +z +x -z
// -y
// -x
int w = totalW / 3;
int h = totalH / 4;
faces.emplace_back(newPastedImageData(src, 1*w, 1*h, w, h));
faces.emplace_back(newPastedImageData(src, 1*w, 3*h, w, h));
faces.emplace_back(newPastedImageData(src, 1*w, 0*h, w, h));
faces.emplace_back(newPastedImageData(src, 1*w, 2*h, w, h));
faces.emplace_back(newPastedImageData(src, 0*w, 1*h, w, h));
faces.emplace_back(newPastedImageData(src, 2*w, 1*h, w, h));
}
else if (totalW % 4 == 0 && totalH % 3 == 0 && totalW / 4 == totalH / 3)
{
// +y
// -x +z +x -z
// -y
int w = totalW / 4;
int h = totalH / 3;
faces.emplace_back(newPastedImageData(src, 2*w, 1*h, w, h));
faces.emplace_back(newPastedImageData(src, 0*w, 1*h, w, h));
faces.emplace_back(newPastedImageData(src, 1*w, 0*h, w, h));
faces.emplace_back(newPastedImageData(src, 1*w, 2*h, w, h));
faces.emplace_back(newPastedImageData(src, 1*w, 1*h, w, h));
faces.emplace_back(newPastedImageData(src, 3*w, 1*h, w, h));
}
else if (totalH % 6 == 0 && totalW == totalH / 6)
{
// +x
// -x
// +y
// -y
// +z
// -z
int w = totalW;
int h = totalH / 6;
for (int i = 0; i < 6; i++)
faces.emplace_back(newPastedImageData(src, 0, i * h, w, h));
}
else if (totalW % 6 == 0 && totalW / 6 == totalH)
{
// +x -x +y -y +z -z
int w = totalW / 6;
int h = totalH;
for (int i = 0; i < 6; i++)
faces.emplace_back(newPastedImageData(src, i * w, 0, w, h));
}
else
throw eve::Exception("Unknown cubemap image dimensions!");
return faces;
}
std::vector<eve::ref<ImageData>> Image::newVolumeLayers(ImageData *src)
{
std::vector<eve::ref<ImageData>> layers;
int totalW = src->getWidth();
int totalH = src->getHeight();
if (totalW % totalH == 0)
{
for (int i = 0; i < totalW / totalH; i++)
layers.emplace_back(newPastedImageData(src, i * totalH, 0, totalH, totalH));
}
else if (totalH % totalW == 0)
{
for (int i = 0; i < totalH / totalW; i++)
layers.emplace_back(newPastedImageData(src, 0, i * totalW, totalW, totalW));
}
else
throw eve::Exception("Cannot extract volume layers from source ImageData.");
return layers;
}
void Image::expose(ssq::Table &table) {
auto cls = table.addClass(name, Image::create, false);
expose(cls);
}
void Image::expose(ssq::Class &cls) {
cls.addFunc("getName", &Image::getName);
cls.addFunc("newImageDataFromFile", &Image::newImageDataFromFile);
}
} // image
} // eve