223 lines
7.9 KiB
C++
223 lines
7.9 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
#include "Fixtures.h"
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <cmath>
|
|
#include <vector>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#include "graphics/AmbientOcclusion.h"
|
|
#include "graphics/AntiAliasing.h"
|
|
#include "graphics/Canvas.h"
|
|
#include "graphics/ClusteredLight.h"
|
|
#include "graphics/DrawItem2D.h"
|
|
#include "graphics/Font.h"
|
|
#include "graphics/GBuffer.h"
|
|
#include "graphics/GlobalIllumination.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "graphics/Grass.h"
|
|
#include "graphics/Light.h"
|
|
#include "graphics/Material.h"
|
|
#include "graphics/Mesh.h"
|
|
#include "graphics/Outline.h"
|
|
#include "graphics/Quad.h"
|
|
#include "graphics/RenderControl.h"
|
|
#include "graphics/RenderSystem.h"
|
|
#include "graphics/RenderSystem3D.h"
|
|
#include "graphics/ScreenSpaceReflection.h"
|
|
#include "graphics/Shader.h"
|
|
#include "graphics/Texture.h"
|
|
#include "graphics/Volumetric.h"
|
|
#include "graphics/Water.h"
|
|
#include "graphics/Waterfall.h"
|
|
#include "window/Window.h"
|
|
// Color lives in eve::graphics (see graphics/Canvas.h); keep the unqualified form.
|
|
using eve::graphics::Color;
|
|
|
|
using namespace eve::graphics;
|
|
|
|
static float luma(const Color &c) { return (c.r + c.g + c.b) / 3.f; }
|
|
|
|
|
|
static void resetScene3D() {
|
|
if (ecs::current()->getManager<Renderable3D>() != nullptr) {
|
|
auto view = ecs::View<Renderable3D, Renderable3D::MeshRenderer>();
|
|
for (auto it = view.begin(); it != view.end(); ++it) {
|
|
auto [mr] = *it;
|
|
mr->visible = false;
|
|
}
|
|
}
|
|
if (ecs::current()->getManager<Camera3D>() != nullptr) {
|
|
auto camView = ecs::View<Camera3D, Camera3D::Data>();
|
|
for (auto it = camView.begin(); it != camView.end(); ++it) {
|
|
auto [data] = *it;
|
|
data->active = false;
|
|
}
|
|
}
|
|
if (ecs::current()->getManager<Light3D>() != nullptr) {
|
|
auto lightView = ecs::View<Light3D, Light3D::Data>();
|
|
for (auto it = lightView.begin(); it != lightView.end(); ++it) {
|
|
auto [d] = *it;
|
|
d->enabled = false;
|
|
}
|
|
}
|
|
if (ecs::current()->getManager<Renderable2D>() != nullptr) {
|
|
auto view = ecs::View<Renderable2D, Renderable2D::Sprite>();
|
|
for (auto it = view.begin(); it != view.end(); ++it) {
|
|
auto [sp] = *it;
|
|
sp->visible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static Texture *makeSolidGray(Graphics *gfx, uint8_t g) {
|
|
uint8_t px[4] = {g, g, g, 255};
|
|
return gfx->newTexture(1, 1, px);
|
|
}
|
|
|
|
TEST_CASE("ClusteredLighting.buildProducesNonEmptyTables") {
|
|
std::vector<ClusteredLightGpu> points;
|
|
for (int i = 0; i < 32; ++i) {
|
|
ClusteredLightGpu L{};
|
|
const float a = float(i) * 0.2f;
|
|
L.posRadius = glm::vec4(std::cos(a) * 1.5f, 0.f, std::sin(a) * 1.5f, 2.f);
|
|
L.color = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
|
points.push_back(L);
|
|
}
|
|
std::vector<ClusteredLightGpu> dirs;
|
|
ClusteredLightGpu d{};
|
|
d.posRadius = glm::vec4(0.f, 1.f, 0.f, 0.f);
|
|
d.color = glm::vec4(0.2f, 0.2f, 0.2f, 1.f);
|
|
dirs.push_back(d);
|
|
|
|
glm::mat4 view = glm::lookAtRH(glm::vec3(0, 0, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
|
|
auto upload = buildClusteredLighting(points, dirs, view, 0.1f, 100.f, 320, 240, 1.0f,
|
|
glm::vec4(0.1f, 0.1f, 0.1f, 0.f));
|
|
REQUIRE(upload.active);
|
|
CHECK_EQ(upload.lights.size(), 32u);
|
|
CHECK_EQ(upload.clusterTable.size(), size_t(ClusteredLightConfig::kClusterCount));
|
|
CHECK(!upload.lightIndices.empty());
|
|
uint32_t total = 0;
|
|
for (const auto &e : upload.clusterTable) total += e.count;
|
|
CHECK_GT(total, 0u);
|
|
}
|
|
|
|
TEST_CASE("ClusteredLighting.reportsFrameAndClusterBudgetOverflow") {
|
|
std::vector<ClusteredLightGpu> points;
|
|
points.reserve(300);
|
|
for (int i = 0; i < 300; ++i) {
|
|
ClusteredLightGpu light{};
|
|
// Deliberately overlap every candidate so at least one cluster exceeds 32 lights.
|
|
light.posRadius = glm::vec4(0.f, 0.f, 0.f, 4.f);
|
|
light.color = glm::vec4(1.f + float(i) * 0.001f, 1.f, 1.f, 1.f);
|
|
points.push_back(light);
|
|
}
|
|
const glm::mat4 view =
|
|
glm::lookAtRH(glm::vec3(0, 0, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
|
|
const auto upload = buildClusteredLighting(points, {}, view, 0.1f, 100.f, 1920, 1080, 1.f,
|
|
glm::vec4(0.f));
|
|
REQUIRE(upload.active);
|
|
CHECK_EQ(upload.lights.size(), size_t(ClusteredLightConfig::kMaxLights));
|
|
CHECK_EQ(upload.truncatedLightCount, 44u);
|
|
CHECK_GT(upload.overflowClusterCount, 0u);
|
|
CHECK_GT(upload.droppedLightReferenceCount, 0u);
|
|
for (const auto &cluster : upload.clusterTable)
|
|
CHECK_LE(cluster.count, uint32_t(ClusteredLightConfig::kMaxLightsPerCluster));
|
|
}
|
|
|
|
TEST_CASE("ClusteredLighting.acceptsDistributed256LightBudget") {
|
|
std::vector<ClusteredLightGpu> points;
|
|
points.reserve(ClusteredLightConfig::kMaxLights);
|
|
for (int i = 0; i < ClusteredLightConfig::kMaxLights; ++i) {
|
|
ClusteredLightGpu light{};
|
|
const int column = i % 16;
|
|
const int row = (i / 16) % 4;
|
|
const int depth = i / 64;
|
|
light.posRadius = glm::vec4((float(column) - 7.5f) * 1.5f,
|
|
(float(row) - 1.5f) * 1.5f,
|
|
-4.f - float(depth) * 12.f, 0.45f);
|
|
light.color = glm::vec4(1.f);
|
|
points.push_back(light);
|
|
}
|
|
const auto upload = buildClusteredLighting(points, {}, glm::mat4(1.f), 0.1f, 100.f, 1920,
|
|
1080, 1.f, glm::vec4(0.f));
|
|
REQUIRE(upload.active);
|
|
CHECK_EQ(upload.lights.size(), size_t(ClusteredLightConfig::kMaxLights));
|
|
CHECK_EQ(upload.truncatedLightCount, 0u);
|
|
CHECK_EQ(upload.overflowClusterCount, 0u);
|
|
CHECK_EQ(upload.droppedLightReferenceCount, 0u);
|
|
}
|
|
|
|
TEST_CASE("ClusteredLighting.manyPointLightsBrightenCenter") {
|
|
eve::window::Window *win = nullptr;
|
|
Graphics *gfx = nullptr;
|
|
openGfxWindow(win, gfx);
|
|
resetScene3D();
|
|
|
|
Mesh *mesh = gfx->newMeshSphere(24, 12);
|
|
REQUIRE(mesh != nullptr);
|
|
|
|
auto *cam = Camera3D::createCamera();
|
|
cam->data()->eyeZ = 3.f;
|
|
cam->setAmbient(0.04f, 0.04f, 0.04f);
|
|
|
|
auto *ent = Renderable3D::create();
|
|
ent->meshRenderer()->mesh = mesh;
|
|
ent->meshRenderer()->texture = makeSolidGray(gfx, 220);
|
|
ent->setMetallic(0.05f);
|
|
ent->setRoughness(0.55f);
|
|
|
|
auto *hud = Renderable2D::create();
|
|
hud->transform()->x = 0;
|
|
hud->transform()->y = 0;
|
|
hud->sprite()->width = 2;
|
|
hud->sprite()->height = 2;
|
|
|
|
// >8 lights triggers clustered path.
|
|
std::vector<Light3D *> lights;
|
|
for (int i = 0; i < 24; ++i) {
|
|
auto *L = Light3D::createLight("point");
|
|
const float a = float(i) * (6.2831853f / 24.f);
|
|
L->setPosition(std::cos(a) * 1.2f, 0.3f, std::sin(a) * 1.2f);
|
|
L->setColor(1.f, 1.f, 1.f, 1.8f);
|
|
L->setRadius(3.5f);
|
|
lights.push_back(L);
|
|
}
|
|
auto *dir = Light3D::createLight("dir");
|
|
dir->setDirection(0.f, 0.4f, 1.f);
|
|
dir->setColor(0.15f, 0.15f, 0.15f, 1.f);
|
|
|
|
gfx->setScreenReadbackEnabled(true);
|
|
for (int i = 0; i < 3; ++i) {
|
|
RenderSystem3D::render(*gfx);
|
|
RenderSystem::render(*gfx);
|
|
}
|
|
const float L_on = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2));
|
|
|
|
for (auto *L : lights) L->setEnabled(false);
|
|
for (int i = 0; i < 3; ++i) {
|
|
RenderSystem3D::render(*gfx);
|
|
RenderSystem::render(*gfx);
|
|
}
|
|
const float L_off = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2));
|
|
|
|
REQUIRE(L_on > L_off + 0.04f);
|
|
|
|
// Re-enable the ring of lights and spin so clustered lighting is visible.
|
|
for (auto *L : lights) L->setEnabled(true);
|
|
for (int i = 0; i < 60; ++i) {
|
|
ent->transform()->yaw = float(i) * 0.04f;
|
|
RenderSystem3D::render(*gfx);
|
|
RenderSystem::render(*gfx);
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e)) {
|
|
if (e.type == SDL_QUIT) break;
|
|
}
|
|
SDL_Delay(16);
|
|
}
|
|
|
|
dir->setEnabled(false);
|
|
win->close();
|
|
}
|