Skip to content

Commit

Permalink
Merge pull request #5943 from Web-eWorks/render-instanced
Browse files Browse the repository at this point in the history
Optimize CityOnPlanet CPU-side rendering cost
  • Loading branch information
Webster Sheets authored Nov 8, 2024
2 parents 21c84cf + 18fd318 commit 2ac32b7
Show file tree
Hide file tree
Showing 32 changed files with 234 additions and 261 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ list(APPEND SRC_FOLDERS
src/graphics
src/graphics/dummy
src/graphics/opengl
src/gui
src/math
src/pigui
src/scenegraph
src/ship
Expand Down
16 changes: 10 additions & 6 deletions src/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ CameraContext::CameraContext(float width, float height, float fovAng, float zNea
m_frame(FrameId::Invalid),
m_pos(0.0),
m_orient(matrix3x3d::Identity()),
m_camFrame(FrameId::Invalid)
m_camFrame(FrameId::Invalid),
m_projMatrix(matrix4x4f::InfinitePerspectiveMatrix(DEG2RAD(m_fovAng), m_width / m_height, m_zNear))
{
}

Expand All @@ -49,6 +50,7 @@ void CameraContext::SetFovAng(float newAng)
{
m_fovAng = newAng;
m_frustum = Frustum(m_width, m_height, m_fovAng, m_zNear, m_zFar);
m_projMatrix = matrix4x4f::InfinitePerspectiveMatrix(DEG2RAD(m_fovAng), m_width / m_height, m_zNear);
}

void CameraContext::BeginFrame()
Expand Down Expand Up @@ -82,7 +84,7 @@ void CameraContext::EndFrame()
void CameraContext::ApplyDrawTransforms(Graphics::Renderer *r)
{
Graphics::SetFov(m_fovAng);
r->SetProjection(matrix4x4f::InfinitePerspectiveMatrix(DEG2RAD(m_fovAng), m_width / m_height, m_zNear));
r->SetProjection(GetProjectionMatrix());
r->SetTransform(matrix4x4f::Identity());
}

Expand Down Expand Up @@ -187,10 +189,12 @@ void Camera::Update()
if (pixSize < BILLBOARD_PIXEL_THRESHOLD) {
attrs.billboard = true;

// project the position
vector3d pos;
m_context->GetFrustum().TranslatePoint(attrs.viewCoords, pos);
attrs.billboardPos = vector3f(pos);
// scale the position of the terrain body until it fits within the far plane for its billboard to be rendered
// Note that with an infinite projection matrix there is no far plane and this is somewhat unnecessary
double zFar = m_context->GetZFar();
double scaleFactor = zFar / attrs.viewCoords.Length() - 0.000001; // tiny nudge closer from the far plane

attrs.billboardPos = vector3f(attrs.viewCoords * std::min(scaleFactor, 1.0));

// limit the minimum billboard size for planets so they're always a little visible
attrs.billboardSize = std::max(1.0f, pixSize);
Expand Down
3 changes: 3 additions & 0 deletions src/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class CameraContext : public RefCounted {
// get the frustum. use for projection
const Graphics::Frustum &GetFrustum() const { return m_frustum; }

const matrix4x4f &GetProjectionMatrix() const { return m_projMatrix; }

// generate and destroy the camere frame, used mostly to transform things to camera space
void BeginFrame();
void EndFrame();
Expand All @@ -74,6 +76,7 @@ class CameraContext : public RefCounted {
float m_zFar;

Graphics::Frustum m_frustum;
matrix4x4f m_projMatrix;

FrameId m_frame;
vector3d m_pos;
Expand Down
98 changes: 65 additions & 33 deletions src/CityOnPlanet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "CityOnPlanet.h"

#include "Camera.h"
#include "FileSystem.h"
#include "Frame.h"
#include "Game.h"
Expand All @@ -11,19 +12,22 @@
#include "Pi.h"
#include "Planet.h"
#include "SpaceStation.h"

#include "collider/Geom.h"
#include "core/Log.h"

#include "graphics/Frustum.h"
#include "graphics/Graphics.h"
#include "graphics/Material.h"
#include "graphics/RenderState.h"
#include "graphics/Renderer.h"
#include "graphics/Types.h"

#include "scenegraph/Animation.h"
#include "scenegraph/Model.h"
#include "scenegraph/ModelSkin.h"
#include "scenegraph/SceneGraph.h"
#include "utils.h"

#include "matrix3x3.h"
#include "matrix4x4.h"

#include "utils.h"

Expand Down Expand Up @@ -87,6 +91,26 @@ void CityOnPlanet::RemoveStaticGeomsFromCollisionSpace()
}
}

void CityOnPlanet::PrecalcInstanceTransforms(const matrix4x4d &stationOrient)
{
PROFILE_SCOPED();

m_instanceTransforms.clear();

// Precalculate building orientation matrices
matrix3x3f rot[4] = { matrix3x3f(stationOrient.GetOrient()) };
for (int i = 1; i < 4; i++) {
rot[i] = rot[0] * matrix3x3f::RotateY(M_PI * 0.5 * double(i));
}

// Cache building instance transforms
// TODO: this could easily be represented as a 4x3 matrix or dual quaternion for significantly less bandwidth
// That requires an "instance buffer" that isn't a 4x4 matrix per instance though... :(
for (const auto &building : m_enabledBuildings) {
m_instanceTransforms.emplace_back(rot[building.rotation], building.pos);
}
}

void CityOnPlanet::GetModelSize(const Aabb &aabb, uint8_t size[2])
{
vector3d aabbSize = aabb.max - aabb.min;
Expand Down Expand Up @@ -326,6 +350,8 @@ CityOnPlanet::CityOnPlanet(Planet *planet, SpaceStation *station, const Uint32 s
Generate(station);

AddStaticGeomsToCollisionSpace();

PrecalcInstanceTransforms(station->GetOrient());
}

void CityOnPlanet::Generate(SpaceStation *station)
Expand Down Expand Up @@ -374,7 +400,8 @@ void CityOnPlanet::Generate(SpaceStation *station)

// top-left corner of the grid is -X, -Z relative to station position at center
// offset origin by half-grid to ensure grid centers are aligned with the station model
m_gridOrigin = station->GetPosition() - incX * (cityExtents + 0.5) - incZ * (cityExtents + 0.5);
const vector3d stationOrigin = station->GetPosition();
m_gridOrigin = stationOrigin - incX * (cityExtents + 0.5) - incZ * (cityExtents + 0.5);

// Setup the station somewhere in the city (defaults to center for now)
const uint32_t stationPos[2] = {
Expand Down Expand Up @@ -536,7 +563,7 @@ void CityOnPlanet::Generate(SpaceStation *station)
Geom *geom = new Geom(cmesh->GetGeomTree(), orientcalc[orient], pos, GetPlanet());

// add it to the list of buildings to render
m_buildings.push_back({ typeIndex, float(cmesh->GetRadius()), orient, pos, geom });
m_buildings.push_back({ vector3f(pos - stationOrigin), float(cmesh->GetRadius()), typeIndex, orient, geom });

}
}
Expand All @@ -550,10 +577,9 @@ void CityOnPlanet::Generate(SpaceStation *station)

// Compute the total AABB of this city
Aabb totalAABB;
const vector3d &stationOrigin = station->GetPosition();

for (const auto &buildingInst : m_buildings) {
totalAABB.Update(buildingInst.pos - stationOrigin);
totalAABB.Update(vector3d(buildingInst.pos));
}

m_realCentre = totalAABB.min + ((totalAABB.max - totalAABB.min) * 0.5);
Expand Down Expand Up @@ -640,7 +666,7 @@ bool CityOnPlanet::TestGridOccupancy(uint32_t x, uint32_t y, const uint8_t size[
return test != 0;
}

void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustum, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform)
void CityOnPlanet::Render(Graphics::Renderer *r, const CameraContext *camera, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform)
{
PROFILE_SCOPED()

Expand All @@ -649,31 +675,25 @@ void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustu
return;

// Early frustum test of whole city.
const vector3d stationOrigin = station->GetPosition();
const vector3d stationPos = viewTransform * (station->GetPosition() + m_realCentre);
//modelview seems to be always identity
if (!frustum.TestPoint(stationPos, m_clipRadius))

if (!camera->GetFrustum().TestPoint(stationPos, m_clipRadius))
return;

// Precalculate building orientation matrices
matrix4x4d rot[4];
matrix4x4f rotf[4];
rot[0] = station->GetOrient();
// Use the station-centered frame-oriented coordinate system we generated building positions in
matrix4x4f modelView = matrix4x4f(viewTransform * matrix4x4d::Translation(station->GetInterpPosition()));

// Combine all transformations to do as few computations as possible inside of the frustum-test loop
// with as much precision as possible
Graphics::FrustumF frustum = Graphics::FrustumF(modelView, camera->GetProjectionMatrix());

// change detail level if necessary
const bool bDetailChanged = m_detailLevel != Pi::detail.cities;
if (bDetailChanged) {
RemoveStaticGeomsFromCollisionSpace();
AddStaticGeomsToCollisionSpace();
}

rot[0] = viewTransform * rot[0];
for (int i = 1; i < 4; i++) {
rot[i] = rot[0] * matrix4x4d::RotateYMatrix(M_PI * 0.5 * double(i));
}
for (int i = 0; i < 4; i++) {
for (int e = 0; e < 16; e++) {
rotf[i][e] = float(rot[i][e]);
}
PrecalcInstanceTransforms(station->GetOrient());
}

// update any idle animations
Expand All @@ -696,27 +716,39 @@ void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustu

transform.resize(numBuildings);

// TODO: we could store all instance transforms in a single array by
// sorting the list of buildings according to instance index...
// Of course, we can't do that because Model::RenderInstanced wants a full-
// owning std::vector of instance data
for (uint32_t i = 0; i < numBuildings; i++) {
transform[i].reserve(m_buildingCounts[i]);
}

for (const auto &building : m_enabledBuildings) {
const vector3d pos = viewTransform * building.pos;
{
// Because we cull each individual instance for performance reasons, we
// have to repack instance transform data into an instance buffer to be
// uploaded to the GPU
// TODO: this could potentially be improved by clustering buildings
// according to a spatial distance metric and then frustum culling those
// clusters instead. It would result in more instanced draws however.

if (!frustum.TestPoint(pos, building.clipRadius))
continue;
PROFILE_SCOPED_DESC("CityOnPlanet::Render() Frustum Culling")

matrix4x4f instanceRot = matrix4x4f(rotf[building.rotation]);
instanceRot.SetTranslate(vector3f(pos));
for (size_t i = 0; i < m_enabledBuildings.size(); i++) {
const auto &building = m_enabledBuildings[i];

transform[building.instIndex].push_back(instanceRot);
++uCount;
if (!frustum.TestPoint(building.pos, building.clipRadius))
continue;

transform[building.instIndex].emplace_back(m_instanceTransforms[i]);
++uCount;
}
}

// render the building models using instancing
for (Uint32 i = 0; i < numBuildings; i++) {
if (!transform[i].empty())
m_cityType->buildingTypes[i].model->Render(transform[i]);
m_cityType->buildingTypes[i].model->RenderInstanced(modelView, transform[i]);
}

// Draw debug extents
Expand Down
21 changes: 14 additions & 7 deletions src/CityOnPlanet.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@
#ifndef _CITYONPLANET_H
#define _CITYONPLANET_H

#include "CollMesh.h"
#include "FrameId.h"
#include "Random.h"
#include "JsonFwd.h"
#include "matrix4x4.h"

#include <set>

class CameraContext;
class Geom;
class Planet;
class SpaceStation;
class Frame;
class SystemPath;
class SystemBody;

struct Aabb;

namespace Graphics {
class Renderer;
class Frustum;
class Material;
} // namespace Graphics

Expand All @@ -40,7 +39,7 @@ class CityOnPlanet {
CityOnPlanet(Planet *planet, SpaceStation *station, const Uint32 seed);
virtual ~CityOnPlanet();

void Render(Graphics::Renderer *r, const Graphics::Frustum &camera, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform);
void Render(Graphics::Renderer *r, const CameraContext *camera, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform);
inline Planet *GetPlanet() const { return m_planet; }
float GetClipRadius() const { return m_clipRadius; }

Expand Down Expand Up @@ -115,11 +114,15 @@ class CityOnPlanet {
void AddStaticGeomsToCollisionSpace();
void RemoveStaticGeomsFromCollisionSpace();

void PrecalcInstanceTransforms(const matrix4x4d &stationOrient);

// NOTE: position here is stored as a 32-bit float offset from the station
// This should retain decent precision within the size of our cities (5-10km radius).
struct BuildingInstance {
Uint32 instIndex;
vector3f pos;
float clipRadius;
Uint32 instIndex;
int rotation; // 0-3
vector3d pos;
Geom *geom;
};

Expand All @@ -133,10 +136,14 @@ class CityOnPlanet {
FrameId m_frame;
Random m_rand;

// Both of these vectors are sorted by building instance index
std::vector<BuildingInstance> m_buildings;
std::vector<BuildingInstance> m_enabledBuildings;

std::vector<Uint32> m_buildingCounts;

std::vector<matrix4x4f> m_instanceTransforms;

// bitmask occupancy grid for quick population of the city
std::unique_ptr<uint8_t[]> m_gridBitset;
// width of a single grid row in bytes
Expand Down
2 changes: 1 addition & 1 deletion src/GeoPatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include "MathUtil.h"
#include "Pi.h"
#include "RefCounted.h"
#include "Sphere.h"
#include "math/Sphere.h"
#include "galaxy/SystemBody.h"
#include "graphics/Frustum.h"
#include "graphics/Graphics.h"
Expand Down
2 changes: 1 addition & 1 deletion src/GeoPatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "RefCounted.h"
#include "matrix4x4.h"
#include "vector3.h"
#include "graphics/Frustum.h"
#include <deque>
#include <memory>

Expand All @@ -26,7 +27,6 @@ namespace Graphics {

namespace Graphics {
class Renderer;
class Frustum;
class MeshObject;
} // namespace Graphics

Expand Down
18 changes: 0 additions & 18 deletions src/Plane.cpp

This file was deleted.

Loading

0 comments on commit 2ac32b7

Please sign in to comment.