Skip to content

Commit

Permalink
Traktor: Fixed GPU occlusion culling after last refactor.
Browse files Browse the repository at this point in the history
  • Loading branch information
apistol78 committed May 16, 2024
1 parent 9468e5a commit 6620b87
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 155 deletions.
7 changes: 5 additions & 2 deletions code/Mesh/Instance/InstanceMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ void InstanceMesh::getTechniques(SmallSet< render::handle_t >& outHandles) const
outHandles.insert(part.first);
}

void InstanceMesh::cullableBuild(
void InstanceMesh::build(
const world::WorldBuildContext& context,
const world::WorldRenderView& worldRenderView,
const world::IWorldRenderPass& worldRenderPass,
Expand All @@ -84,6 +84,7 @@ void InstanceMesh::cullableBuild(
const auto& meshParts = m_renderMesh->getParts();

// Lazy create the buffers.
const uint32_t bufferItemCount = (uint32_t)alignUp(count, 16);
if (count > m_allocatedCount)
{
m_drawBuffers.resize(0);
Expand All @@ -95,11 +96,13 @@ void InstanceMesh::cullableBuild(
for (uint32_t i = dbSize; i < (peakCascade + 1) * parts.size(); ++i)
m_drawBuffers.push_back(m_renderSystem->createBuffer(
render::BufferUsage::BuStructured | render::BufferUsage::BuIndirect,
count * sizeof(render::IndexedIndirectDraw),
bufferItemCount * sizeof(render::IndexedIndirectDraw),
false
));

// Create draw buffers from visibility buffer.
// Compute blocks are executed before render pass, so draws for shadow map rendering all cascades
// are dispatched at the same time.
for (uint32_t i = 0; i < parts.size(); ++i)
{
const auto& part = parts[i];
Expand Down
31 changes: 12 additions & 19 deletions code/Mesh/Instance/InstanceMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
#include "Mesh/IMesh.h"
#include "Render/Shader.h"
#include "Resource/Proxy.h"
#include "World/Entity/CullingComponent.h"

// import/export mechanism.
#undef T_DLLCLASS
Expand All @@ -30,22 +29,22 @@
namespace traktor::render
{

//class Buffer;
class Buffer;
class IRenderSystem;
class ProgramParameters;
//class RenderContext;
class Mesh;
class Shader;

}

//namespace traktor::world
//{
//
//class IWorldRenderPass;
//class WorldRenderView;
//
//}
namespace traktor::world
{

class IWorldRenderPass;
class WorldBuildContext;
class WorldRenderView;

}

namespace traktor::mesh
{
Expand All @@ -57,9 +56,7 @@ namespace traktor::mesh
* automatically by the GPU in any number of instances
* using hardware instancing in a single draw call.
*/
class T_DLLCLASS InstanceMesh
: public IMesh
, public world::CullingComponent::ICullable
class T_DLLCLASS InstanceMesh : public IMesh
{
T_RTTI_CLASS;

Expand All @@ -81,19 +78,15 @@ class T_DLLCLASS InstanceMesh

void getTechniques(SmallSet< render::handle_t >& outHandles) const;

/* world::CullingComponent::ICullable */

virtual const Aabb3& cullableGetBoundingBox() const override final { return getBoundingBox(); }

virtual void cullableBuild(
void build(
const world::WorldBuildContext& context,
const world::WorldRenderView& worldRenderView,
const world::IWorldRenderPass& worldRenderPass,
render::Buffer* instanceBuffer,
render::Buffer* visibilityBuffer,
uint32_t start,
uint32_t count
) override final;
);

private:
friend class InstanceMeshResource;
Expand Down
76 changes: 41 additions & 35 deletions code/Mesh/Instance/InstanceMeshComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ T_IMPLEMENT_RTTI_CLASS(L"traktor.mesh.InstanceMeshComponent", InstanceMeshCompon
InstanceMeshComponent::InstanceMeshComponent(const resource::Proxy< InstanceMesh >& mesh)
: m_mesh(mesh)
{
//m_cullingInstance = m_mesh->allocateInstance();
m_mesh.consume();
}

Expand All @@ -35,44 +34,36 @@ InstanceMeshComponent::~InstanceMeshComponent()

void InstanceMeshComponent::destroy()
{
//// Only release instance if resource hasn't been replaced.
//if (!m_mesh.changed())
//{
// if (m_mesh && m_meshInstance != nullptr)
// m_mesh->releaseInstance(m_meshInstance);
//}

if (m_owner != nullptr && m_cullingInstance != nullptr)
{
world::CullingComponent* culling = m_owner->getWorld()->getComponent< world::CullingComponent >();
culling->releaseInstance(m_cullingInstance);
}

T_FATAL_ASSERT(m_cullingInstance == nullptr);
m_mesh.clear();
MeshComponent::destroy();
}

void InstanceMeshComponent::setWorld(world::World* world)
{
T_FATAL_ASSERT(m_cullingInstance == nullptr);
// Remove from last world.
if (m_world != nullptr)
{
T_FATAL_ASSERT(m_cullingInstance != nullptr);
world::CullingComponent* culling = m_world->getComponent< world::CullingComponent >();
culling->releaseInstance(m_cullingInstance);
}

// Add to new world.
if (world != nullptr)
{
T_FATAL_ASSERT(m_cullingInstance == nullptr);
world::CullingComponent* culling = world->getComponent< world::CullingComponent >();
m_cullingInstance = culling->allocateInstance(m_mesh);
m_cullingInstance = culling->allocateInstance(this, (intptr_t)m_mesh.getResource());
}

m_world = world;
}

void InstanceMeshComponent::setTransform(const Transform& transform)
{
MeshComponent::setTransform(transform);

// Re-allocate instance if mesh resource has been replaced.
//if (m_mesh.changed())
//{
// m_meshInstance = m_mesh->allocateInstance();
// m_mesh.consume();
//}

if (m_cullingInstance)
m_cullingInstance->setTransform(transform);
}
Expand All @@ -82,22 +73,37 @@ Aabb3 InstanceMeshComponent::getBoundingBox() const
return m_mesh->getBoundingBox();
}

void InstanceMeshComponent::update(const world::UpdateParams& update)
void InstanceMeshComponent::build(
const world::WorldBuildContext& context,
const world::WorldRenderView& worldRenderView,
const world::IWorldRenderPass& worldRenderPass
)
{
MeshComponent::update(update);

// Re-allocate instance if mesh resource has been replaced.
//if (m_mesh.changed())
//{
// m_meshInstance = m_mesh->allocateInstance();
// m_meshInstance->setTransform(getTransform().get0());
// m_mesh.consume();
//}
// Not used since we're getting called by the culling component when we should build.
}

void InstanceMeshComponent::build(const world::WorldBuildContext& context, const world::WorldRenderView& worldRenderView, const world::IWorldRenderPass& worldRenderPass)
void InstanceMeshComponent::cullableBuild(
const world::WorldBuildContext& context,
const world::WorldRenderView& worldRenderView,
const world::IWorldRenderPass& worldRenderPass,
render::Buffer* instanceBuffer,
render::Buffer* visibilityBuffer,
uint32_t start,
uint32_t count
)
{
//T_ASSERT_M(0, L"Forgot to register InstanceMeshComponentRenderer?");
// Draw mesh instances; this method is called for the "first" InstanceMeshComponent using the same ordinal number
// assuming all other components reference the same mesh.
if (m_mesh)
m_mesh->build(
context,
worldRenderView,
worldRenderPass,
instanceBuffer,
visibilityBuffer,
start,
count
);
}

}
27 changes: 23 additions & 4 deletions code/Mesh/Instance/InstanceMeshComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class InstanceMesh;
/*! Instancing mesh component.
* \ingroup Mesh
*/
class T_DLLCLASS InstanceMeshComponent : public MeshComponent
class T_DLLCLASS InstanceMeshComponent
: public MeshComponent
, public world::CullingComponent::ICullable
{
T_RTTI_CLASS;

Expand All @@ -45,14 +47,31 @@ class T_DLLCLASS InstanceMeshComponent : public MeshComponent

virtual Aabb3 getBoundingBox() const override final;

virtual void update(const world::UpdateParams& update) override final;

virtual void build(const world::WorldBuildContext& context, const world::WorldRenderView& worldRenderView, const world::IWorldRenderPass& worldRenderPass) override final;
virtual void build(
const world::WorldBuildContext& context,
const world::WorldRenderView& worldRenderView,
const world::IWorldRenderPass& worldRenderPass
) override final;

inline resource::Proxy< InstanceMesh >& getMesh() { return m_mesh; }

/* world::CullingComponent::ICullable */

virtual Aabb3 cullableGetBoundingBox() const override final { return getBoundingBox(); }

virtual void cullableBuild(
const world::WorldBuildContext& context,
const world::WorldRenderView& worldRenderView,
const world::IWorldRenderPass& worldRenderPass,
render::Buffer* instanceBuffer,
render::Buffer* visibilityBuffer,
uint32_t start,
uint32_t count
) override final;

private:
resource::Proxy< InstanceMesh > m_mesh;
world::World* m_world = nullptr;
world::CullingComponent::Instance* m_cullingInstance = nullptr;
};

Expand Down
26 changes: 16 additions & 10 deletions code/World/Entity/CullingComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ void CullingComponent::destroy()
T_FATAL_ASSERT_M(m_instances.empty(), L"Culling instances not empty.");
safeDestroy(m_instanceBuffer);
for (auto visibilityBuffer : m_visibilityBuffers)
safeDestroy(visibilityBuffer);
{
if (visibilityBuffer)
visibilityBuffer->destroy();
}
m_visibilityBuffers.resize(0);
m_renderSystem = nullptr;
}

void CullingComponent::update(World* world, const UpdateParams& update)
Expand Down Expand Up @@ -76,6 +80,7 @@ void CullingComponent::build(
m_instanceBufferDirty = true;
}

// Ensure we have visibility buffers for all cascades.
const uint32_t peakCascade = worldRenderView.getCascade();
const uint32_t vbSize = (uint32_t)m_visibilityBuffers.size();
for (uint32_t i = vbSize; i < peakCascade + 1; ++i)
Expand All @@ -100,7 +105,7 @@ void CullingComponent::build(
render::Buffer* visibilityBuffer = m_visibilityBuffers[worldRenderView.getCascade()];

// Cull instances, output are visibility buffer.
// #todo Compute blocks are executed before render pass, so for shadow map rendering all cascades
// Compute blocks are executed before render pass, so for shadow map rendering all cascades
// are culled before being rendered.
{
Vector4 cullFrustum[12];
Expand All @@ -115,19 +120,19 @@ void CullingComponent::build(
const Vector2 viewSize = worldRenderView.getViewSize();

auto renderBlock = renderContext->allocNamed< render::ComputeRenderBlock >(
str(L"Mesh cull %d", worldRenderView.getCascade())
str(L"Cull %d", worldRenderView.getCascade())
);

render::Shader::Permutation perm;
if (worldRenderPass.getTechnique() == s_techniqueDeferredGBufferWrite)
{
// Deferred g-buffer pass has access to HiZ texture.
m_shaderCull->setCombination(render::getParameterHandle(L"InstanceMesh_HiZ"), true, perm);
m_shaderCull->setCombination(s_handleCullingHiZ, true, perm);
}
else
{
// All other paths use simple frustum culling only.
m_shaderCull->setCombination(render::getParameterHandle(L"InstanceMesh_HiZ"), false, perm);
m_shaderCull->setCombination(s_handleCullingHiZ, false, perm);
}

renderBlock->program = m_shaderCull->getProgram(perm).program;
Expand All @@ -150,13 +155,13 @@ void CullingComponent::build(
renderContext->compute< render::BarrierRenderBlock >(render::Stage::Compute, render::Stage::Compute, nullptr, 0);
}

// Batch draw instances; assumes m_instances are sorted by "cullable".
// Batch draw instances; assumes m_instances are sorted by "ordinal" so we can scan for run length.
for (uint32_t i = 0; i < (uint32_t)m_instances.size(); )
{
uint32_t j = i + 1;
for (; j < (uint32_t)m_instances.size(); ++j)
{
if (m_instances[i]->cullable != m_instances[j]->cullable)
if (m_instances[i]->ordinal != m_instances[j]->ordinal)
break;
}

Expand All @@ -174,17 +179,18 @@ void CullingComponent::build(
}
}

CullingComponent::Instance* CullingComponent::allocateInstance(ICullable* cullable)
CullingComponent::Instance* CullingComponent::allocateInstance(ICullable* cullable, intptr_t ordinal)
{
Instance* instance = new Instance();
instance->owner = this;
instance->cullable = cullable;
instance->ordinal = ordinal;
instance->transform = Transform::identity();
instance->boundingBox = cullable->cullableGetBoundingBox();

// Insert instance sorted by cullable so we can calculate run length when building.
// Insert instance sorted by ordinal so we can calculate run length when building.
auto it = std::upper_bound(m_instances.begin(), m_instances.end(), instance, [=](Instance* lh, Instance* rh) {
return lh->cullable < rh->cullable;
return lh->ordinal < rh->ordinal;
});
m_instances.insert(it, instance);
return instance;
Expand Down
5 changes: 3 additions & 2 deletions code/World/Entity/CullingComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class T_DLLCLASS CullingComponent : public IWorldComponent

struct T_DLLCLASS ICullable
{
virtual const Aabb3& cullableGetBoundingBox() const = 0;
virtual Aabb3 cullableGetBoundingBox() const = 0;

virtual void cullableBuild(
const WorldBuildContext& context,
Expand All @@ -82,6 +82,7 @@ class T_DLLCLASS CullingComponent : public IWorldComponent
{
CullingComponent* owner;
ICullable* cullable;
intptr_t ordinal;
Transform transform;
Aabb3 boundingBox;

Expand All @@ -100,7 +101,7 @@ class T_DLLCLASS CullingComponent : public IWorldComponent
const IWorldRenderPass& worldRenderPass
);

Instance* allocateInstance(ICullable* cullable);
Instance* allocateInstance(ICullable* cullable, intptr_t ordinal);

void releaseInstance(Instance*& instance);

Expand Down
Loading

0 comments on commit 6620b87

Please sign in to comment.