Introduce the headless-vs-window test policy (docs/dev/测试覆盖.md §0):
functional GPU tests run via Graphics::initHeadless + offscreen canvas
readback; visual/demo tests (ClassicScenes, RenderImageAudit, animation,
voxel, particles, UI, swapchain-readback cases) keep real windows.
Mechanics:
- Graphics::initHeadless is now re-entrant: the process-wide singleton
updates the logical viewport on repeat calls instead of throwing, so
multiple cases in a bundle can share one headless device.
- test/Fixtures.h adds openHeadlessGfx() and a useHeadless mode for
GfxFixture; EVENGINE_TEST_VIEW_W/H overrides apply to both paths.
Migrated (window-free, no behavior change to assertions):
- test/graphics_font.cpp: atlas + print-to-canvas pixel readback; the
interactive debug-preview loop is guarded by isHeadless().
- test/TextureSampler.cpp: sampler parameter round-trips.
graphics_headless.cpp now asserts the re-entrant resize semantics.
- Add RGBA-float drawSolidRect/drawTexturedRect overloads so the C++ names
match the script-facing API (script already bound those names).
- Add string-based setTextureSampler(Texture*, filter, mipmap, aniso, lodBias)
overload with strict magic-string validation (unknown filter/mipmap now
throws instead of silently falling back to linear); keep
setTextureSamplerParams as a deprecated alias. Script binding now targets
the overload with an explicit cast.
- Document error contracts: DataModule encode/decode (new[] ownership +
throws), Filesystem setSource/mount/unmount (false on recoverable failure,
no throw), ModelData getMesh vs numeric getters (nullptr vs throw), plus a
two-state error-semantics section in API-CONVENTIONS.md.
- Tests: TextureSampler string validation (valid + deprecated alias + typo
throws), headless test now exercises the RGBA-float overloads.
Add Graphics::initHeadless(width, height) (Vulkan backend): creates an
instance/device with no VkSurfaceKHR, a plain color+depth render pass for
the shared pipeline builders, offscreen canvas support and no-op present().
The windowed initWithWindow path is refactored to share createInstanceAndDevice()
and keeps its surface-based device selection (set_surface). Headless selection
uses VKBuilder defer_surface_initialization() because InstanceBuilder's headless
flag is not propagated to PhysicalDeviceSelector.
Includes test/graphics_headless.cpp: invalid-size/double-init error contracts,
canvas draw + pixel readback, present() no-op, texture creation.