Files
EVEngine/test/voxel_render_lifecycle.cpp
T

459 lines
18 KiB
C++

#include "SceneColor.h"
#include "VoxelRenderFixtures.h"
TEST_CASE("voxel.render.emptyInstanceInputsAreNoOps") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 200, 150);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *white = makeSolid(gfx, 255, 255, 255);
REQUIRE(white != nullptr);
const uint32_t packed = PackedRect::pack(0, 0, 0, 1, 1, 0).bits;
const float aspect = float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 vp = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f) *
glm::lookAtRH(glm::vec3(0.5f, 0.5f, 5.f), glm::vec3(0.5f, 0.5f, 1.f), glm::vec3(0, 1, 0));
struct EmptyInput {
const uint32_t *data;
int count;
const char *direction;
Texture *atlas;
};
const EmptyInput inputs[] = {{nullptr, 0, "posZ", nullptr},
{nullptr, 10, "posY", white},
{nullptr, 0, "posY", white},
{&packed, 0, "posZ", nullptr}};
const Color background(0.1f, 0.1f, 0.12f, 1.f);
// Compare against the actual cleared frame, including the backend's display transform.
gfx->setBackgroundColor(background);
gfx->begin3DFrame();
REQUIRE(gfx->had3DThisFrame());
RenderSystem::render(*gfx);
const Color cleared = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
for (const auto &input : inputs) {
std::printf("empty input null=%d count=%d face=%s atlas=%d\n", input.data == nullptr, input.count,
input.direction, input.atlas != nullptr);
gfx->setBackgroundColor(background);
gfx->begin3DFrame();
REQUIRE(gfx->had3DThisFrame());
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(input.data, input.count, 0.f, 0.f, 0.f, input.direction, input.atlas, 1);
RenderSystem::render(*gfx);
const Color empty = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(std::fabs(empty.r - cleared.r) < 0.01f);
REQUIRE(std::fabs(empty.g - cleared.g) < 0.01f);
REQUIRE(std::fabs(empty.b - cleared.b) < 0.01f);
// A no-op must not prevent a subsequent valid draw in the same frame.
gfx->begin3DFrame();
REQUIRE(gfx->had3DThisFrame());
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(input.data, input.count, 0.f, 0.f, 0.f, input.direction, input.atlas, 1);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posZ", nullptr, 1);
RenderSystem::render(*gfx);
const Color drawn = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(drawn.r > 0.2f);
REQUIRE(drawn.g > 0.2f);
REQUIRE(drawn.b > 0.2f);
}
win->close();
}
TEST_CASE("voxel.render.multiBatchSameFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Two separated pillars → multiple face batches.
for (int y = 0; y < 6; ++y) {
world->setVoxel(2, y, 2, 1);
world->setVoxel(10, y, 10, 1);
}
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 180, 170, 90);
const glm::vec3 eye(6.f, 8.f, 22.f);
const glm::vec3 target(6.f, 3.f, 6.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleBatchCount() >= 2);
CHECK(world->getVisibleRectCount() >= 2);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.drawVisibleMatchesManualBatches") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 3; ++z)
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 3; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 90, 140, 220);
const float aspect = float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(1.5f, 1.5f, 10.f);
const glm::vec3 target(1.5f, 1.5f, 1.5f);
const glm::mat4 view = glm::lookAtRH(eye, target, glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 200.f, true);
const int batches = world->getVisibleBatchCount();
REQUIRE(batches > 0);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->setMesh3DCameraPos(eye);
for (int i = 0; i < batches; ++i) {
const DrawBatch &b = world->getVisibleBatch(i);
gfx->drawVoxelFaceInstances(b.packed, b.count, b.chunk->originX(), b.chunk->originY(), b.chunk->originZ(),
faceDirName(b.dir), atlas, 1);
}
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.06f);
}
TEST_CASE("voxel.render.requiresBegin3DFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 160, 120);
uint32_t dummy = PackedRect::pack(0, 0, 0, 1, 1, 1).bits;
bool threw = false;
try {
gfx->drawVoxelFaceInstances(&dummy, 1, 0.f, 0.f, 0.f, "posZ", nullptr, 1);
} catch (...) {
threw = true;
}
CHECK(threw);
}
TEST_CASE("voxel.render.largeInstanceCount") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// Many non-merged top faces: sparse pillars.
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 16; ++z)
for (int x = 0; x < 16; ++x)
if (((x * 3 + z * 5) % 7) == 0) world->setVoxel(x, 0, z, 1);
world->remeshDirty();
CHECK(world->getOrCreateChunk(0, 0, 0)->totalRectCount() > 20);
Texture *atlas = makeSolid(gfx, 200, 180, 60);
const glm::vec3 eye(8.f, 10.f, 28.f);
const glm::vec3 target(8.f, 0.f, 8.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleRectCount() > 10);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.multiFrameSlotReuse") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->setVoxel(0, 0, 0, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 160, 160, 200);
const glm::vec3 eye(0.5f, 0.5f, 5.f);
const glm::vec3 target(0.5f, 0.5f, 0.5f);
for (int frame = 0; frame < 6; ++frame) {
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
}
TEST_CASE("voxel.render.editRemeshChangesPixels") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
Texture *atlas = makeSolid(gfx, 230, 230, 240);
const glm::vec3 eye(4.f, 4.f, 18.f);
const glm::vec3 target(4.f, 4.f, 4.f);
// Empty world → dark
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
REQUIRE_EQ(world->getVisibleRectCount(), 0);
Color empty = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(luma(testSceneLinearColor(empty)) < 0.2f);
for (int z = 0; z < 8; ++z)
for (int y = 0; y < 8; ++y)
for (int x = 0; x < 8; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
Color filled = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(luma(filled) > luma(empty) + 0.03f);
}
TEST_CASE("voxel.render.emptyWorldDrawVisible") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 200, 150);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
Texture *atlas = makeSolid(gfx, 255, 255, 255);
const glm::vec3 eye(0.f, 0.f, 5.f);
const glm::vec3 target(0.f, 0.f, 0.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
REQUIRE_EQ(world->getVisibleBatchCount(), 0);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(luma(testSceneLinearColor(mid)) < 0.25f);
}
TEST_CASE("voxel.render.manyChunksBatched") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int cx = 0; cx < 3; ++cx)
for (int cz = 0; cz < 3; ++cz) {
for (int y = 0; y < 2; ++y)
for (int x = 0; x < 2; ++x)
for (int z = 0; z < 2; ++z) world->setVoxel(cx * 32 + x + 8, y, cz * 32 + z + 8, 1);
}
world->remeshDirty();
CHECK_EQ(world->getChunkCount(), 9);
Texture *atlas = makeSolid(gfx, 120, 160, 200);
const glm::vec3 eye(48.f, 20.f, 120.f);
const glm::vec3 target(48.f, 1.f, 48.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 300.f, true);
CHECK(world->getVisibleChunkCount() >= 3);
CHECK(world->getVisibleBatchCount() >= 3);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.instanceCountGrowth") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeSolid(gfx, 200, 200, 210);
const float aspect = float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(8.f, 8.f, 40.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(8.f, 0.f, 8.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
// Grow instance buffer across draws in one frame: 1, then many.
std::vector<uint32_t> many;
many.reserve(64);
for (int z = 0; z < 8; ++z)
for (int x = 0; x < 8; ++x) many.push_back(PackedRect::pack(x, 0, z, 1, 1, 1).bits);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
uint32_t one = many[0];
gfx->drawVoxelFaceInstances(&one, 1, 0.f, 0.f, 0.f, "posY", atlas, 1);
gfx->drawVoxelFaceInstances(many.data(), int(many.size()), 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.twoOriginsSameFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t a = PackedRect::pack(0, 0, 0, 2, 2, 1).bits;
uint32_t b = PackedRect::pack(0, 0, 0, 2, 2, 1).bits;
Texture *atlas = makeSolid(gfx, 90, 200, 120);
const float aspect = float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(4.f, 4.f, 20.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(4.f, 1.f, 4.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&a, 1, 0.f, 0.f, 0.f, "posY", atlas, 1);
gfx->drawVoxelFaceInstances(&b, 1, 6.f, 0.f, 6.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.removeVoxelsDarkens") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 6; ++z)
for (int y = 0; y < 6; ++y)
for (int x = 0; x < 6; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 240, 240, 250);
const glm::vec3 eye(3.f, 3.f, 16.f);
const glm::vec3 target(3.f, 3.f, 3.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color filled = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(luma(filled) > 0.08f);
world->clear();
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color empty = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
REQUIRE(luma(empty) < luma(filled));
REQUIRE(luma(testSceneLinearColor(empty)) < 0.22f);
}
TEST_CASE("voxel.render.stressHundredsOfInstances") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::vector<uint32_t> many;
many.reserve(256);
for (int z = 0; z < 16; ++z)
for (int x = 0; x < 16; ++x) many.push_back(PackedRect::pack(x, 0, z, 1, 1, 1).bits);
CHECK_EQ(int(many.size()), 256);
Texture *atlas = makeSolid(gfx, 180, 190, 200);
const float aspect = float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(8.f, 12.f, 30.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(8.f, 0.f, 8.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(many.data(), int(many.size()), 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.manualAllSixDirsOneFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 2, 2, 1).bits;
Texture *atlas = makeSolid(gfx, 220, 220, 40);
const float aspect = float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
// Orbit-ish view so several faces of a unit cube-ish placement are on screen.
const glm::vec3 eye(8.f, 8.f, 8.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(1.f, 1.f, 1.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(55.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
const char *dirs[6] = {"posX", "negX", "posY", "negY", "posZ", "negZ"};
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
for (const char *d : dirs) gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, d, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.03f);
}
TEST_CASE("voxel.render.remeshIncreasesThenDecreasesRects") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->setVoxel(2, 2, 2, 1);
world->remeshDirty();
const int one = world->getChunk(0, 0, 0)->totalRectCount();
CHECK_EQ(one, 6);
fillCube(world.get(), 0, 0, 0, 5, 1);
world->remeshDirty();
const int many = world->getChunk(0, 0, 0)->totalRectCount();
CHECK(many < one * 5); // greedy merges
CHECK(many >= 6);
Texture *atlas = makeSolid(gfx, 200, 200, 210);
renderVoxelFrame(gfx, world.get(), atlas, 1, glm::vec3(2.5f, 2.5f, 14.f), glm::vec3(2.5f, 2.5f, 2.5f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
world->clear();
world->setVoxel(2, 2, 2, 1);
world->remeshDirty();
CHECK_EQ(world->getChunk(0, 0, 0)->totalRectCount(), 6);
}
// ---------------------------------------------------------------------------
// Large visual scenes (PNG under test/out/voxel_scenes/)
// ---------------------------------------------------------------------------