Remove octree-level fragment query occlusion culling. This will be replaced with a method involving hi-z buffers and mesh shaders.

This commit is contained in:
2025-11-30 15:47:27 -08:00
parent b6179a2496
commit 19020e3c01
8 changed files with 54 additions and 287 deletions

View File

@@ -42,10 +42,6 @@ KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds) : m_bounds(
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
m_children[i] = NULL; m_children[i] = NULL;
} }
m_occlusionQuery = 0;
m_occlusionTested = false;
m_activeQuery = false;
} }
KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds, int iChild, KROctreeNode* pChild) : m_bounds(bounds) KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds, int iChild, KROctreeNode* pChild) : m_bounds(bounds)
@@ -58,10 +54,6 @@ KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds, int iChild,
} }
m_children[iChild] = pChild; m_children[iChild] = pChild;
pChild->m_parent = this; pChild->m_parent = this;
m_occlusionQuery = 0;
m_occlusionTested = false;
m_activeQuery = false;
} }
KROctreeNode::~KROctreeNode() KROctreeNode::~KROctreeNode()
@@ -71,37 +63,6 @@ KROctreeNode::~KROctreeNode()
delete m_children[i]; delete m_children[i];
} }
} }
if (m_occlusionTested) {
GLDEBUG(glDeleteQueriesEXT(1, &m_occlusionQuery));
}
}
void KROctreeNode::beginOcclusionQuery()
{
if (!m_occlusionTested) {
GLDEBUG(glGenQueriesEXT(1, &m_occlusionQuery));
#if TARGET_OS_IPHONE || defined(ANDROID)
GLDEBUG(glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, m_occlusionQuery));
#else
GLDEBUG(glBeginQuery(GL_SAMPLES_PASSED, m_occlusionQuery));
#endif
m_occlusionTested = true;
m_activeQuery = true;
}
}
void KROctreeNode::endOcclusionQuery()
{
if (m_activeQuery) {
// Only end a query if we started one
#if TARGET_OS_IPHONE || defined(ANDROID)
GLDEBUG(glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT));
#else
GLDEBUG(glEndQuery(GL_SAMPLES_PASSED));
#endif
}
} }

View File

@@ -62,14 +62,6 @@ public:
bool canShrinkRoot() const; bool canShrinkRoot() const;
KROctreeNode* stripChild(); KROctreeNode* stripChild();
void beginOcclusionQuery();
void endOcclusionQuery();
int m_occlusionQuery;
bool m_occlusionTested;
bool m_activeQuery;
bool lineCast(const hydra::Vector3& v0, const hydra::Vector3& v1, hydra::HitInfo& hitinfo, unsigned int layer_mask); bool lineCast(const hydra::Vector3& v0, const hydra::Vector3& v1, hydra::HitInfo& hitinfo, unsigned int layer_mask);
bool rayCast(const hydra::Vector3& v0, const hydra::Vector3& dir, hydra::HitInfo& hitinfo, unsigned int layer_mask); bool rayCast(const hydra::Vector3& v0, const hydra::Vector3& dir, hydra::HitInfo& hitinfo, unsigned int layer_mask);
bool sphereCast(const hydra::Vector3& v0, const hydra::Vector3& v1, float radius, hydra::HitInfo& hitinfo, unsigned int layer_mask); bool sphereCast(const hydra::Vector3& v0, const hydra::Vector3& v1, float radius, hydra::HitInfo& hitinfo, unsigned int layer_mask);

View File

@@ -210,12 +210,6 @@ void KRViewport::calculateDerivedValues()
} }
} }
unordered_map<AABB, int>& KRViewport::getVisibleBounds()
{
return m_visibleBounds;
}
float KRViewport::getLODBias() const float KRViewport::getLODBias() const
{ {
return m_lodBias; return m_lodBias;
@@ -314,20 +308,3 @@ bool KRViewport::visible(const AABB& b) const
return is_visible; return is_visible;
} }
void KRViewport::expireOcclusionResults(long frame)
{
// Expire cached occlusion test results.
// Cached "failed" results are expired on the next frame (marked with .second of -1)
// Cached "success" results are expired after KRENGINE_OCCLUSION_TEST_EXPIRY frames (marked with .second of the last frame
std::set<AABB> expired_visible_bounds;
for (unordered_map<AABB, int>::iterator visible_bounds_itr = m_visibleBounds.begin(); visible_bounds_itr != m_visibleBounds.end(); visible_bounds_itr++) {
if ((*visible_bounds_itr).second == -1 || (*visible_bounds_itr).second + KRENGINE_OCCLUSION_TEST_EXPIRY < frame) {
expired_visible_bounds.insert((*visible_bounds_itr).first);
}
}
for (std::set<AABB>::iterator expired_visible_bounds_itr = expired_visible_bounds.begin(); expired_visible_bounds_itr != expired_visible_bounds.end(); expired_visible_bounds_itr++) {
m_visibleBounds.erase(*expired_visible_bounds_itr);
}
}

View File

@@ -69,14 +69,11 @@ public:
// Overload assignment operator // Overload assignment operator
KRViewport& operator=(const KRViewport& v); KRViewport& operator=(const KRViewport& v);
unordered_map<hydra::AABB, int>& getVisibleBounds();
const std::set<KRLight*>& getVisibleLights(); const std::set<KRLight*>& getVisibleLights();
void setVisibleLights(const std::set<KRLight*> visibleLights); void setVisibleLights(const std::set<KRLight*> visibleLights);
bool visible(const hydra::AABB& b) const; bool visible(const hydra::AABB& b) const;
float coverage(const hydra::AABB& b) const; float coverage(const hydra::AABB& b) const;
void expireOcclusionResults(long frame);
private: private:
hydra::Vector2 m_size; hydra::Vector2 m_size;
@@ -96,6 +93,4 @@ private:
int m_backToFrontOrder[8]; int m_backToFrontOrder[8];
void calculateDerivedValues(); void calculateDerivedValues();
unordered_map<hydra::AABB, int> m_visibleBounds; // AABB's that output fragments in the last frame
}; };

View File

@@ -181,6 +181,8 @@ void KRCamera::render(KRNode::RenderInfo& ri)
{ {
// ----====---- Debug Overlays ----====---- // ----====---- Debug Overlays ----====----
/*
* // TODO: Implement octree visualization that is not dependent on the no longer present fragment query based occlusion testing
if (settings.debug_display == KRRenderSettings::KRENGINE_DEBUG_DISPLAY_OCTREE) { if (settings.debug_display == KRRenderSettings::KRENGINE_DEBUG_DISPLAY_OCTREE) {
KRMeshManager::KRVBOData& vertices = getContext().getMeshManager()->KRENGINE_VBO_DATA_3D_CUBE_VERTICES; KRMeshManager::KRVBOData& vertices = getContext().getMeshManager()->KRENGINE_VBO_DATA_3D_CUBE_VERTICES;
@@ -193,17 +195,8 @@ void KRCamera::render(KRNode::RenderInfo& ri)
info.vertexAttributes = vertices.getVertexAttributes(); info.vertexAttributes = vertices.getVertexAttributes();
info.modelFormat = ModelFormat::KRENGINE_MODEL_FORMAT_STRIP; info.modelFormat = ModelFormat::KRENGINE_MODEL_FORMAT_STRIP;
KRPipeline* pVisShader = getContext().getPipelineManager()->getPipeline(*ri.surface, info); KRPipeline* pVisShader = getContext().getPipelineManager()->getPipeline(*ri.surface, info);
m_pContext->getMeshManager()->bindVBO(ri.commandBuffer, &vertices, 1.0f);
for (unordered_map<AABB, int>::iterator itr = m_viewport.getVisibleBounds().begin(); itr != m_viewport.getVisibleBounds().end(); itr++) {
Matrix4 matModel = Matrix4();
matModel.scale((*itr).first.size() * 0.5f);
matModel.translate((*itr).first.center());
pVisShader->bind(ri, matModel);
vkCmdDraw(ri.commandBuffer, 14, 1, 0, 0);
}
} }
*/
renderDebug(ri); renderDebug(ri);
} }
@@ -260,8 +253,6 @@ void KRCamera::renderFrame(VkCommandBuffer& commandBuffer, KRSurface& compositeS
scene.updateOctree(m_viewport); scene.updateOctree(m_viewport);
m_viewport.expireOcclusionResults(getContext().getCurrentFrame());
renderGraph.render(commandBuffer, compositeSurface, this); renderGraph.render(commandBuffer, compositeSurface, this);
} }

View File

@@ -477,7 +477,6 @@ void KRLight::renderShadowBuffers(RenderInfo& ri)
ri.viewport = &m_shadowViewports[iShadow]; ri.viewport = &m_shadowViewports[iShadow];
shadowShader->bind(ri, Matrix4()); shadowShader->bind(ri, Matrix4());
m_shadowViewports[iShadow].expireOcclusionResults(m_pContext->getCurrentFrame());
getScene().render(ri); getScene().render(ri);
} }
} }

View File

@@ -146,31 +146,15 @@ void KRScene::render(KRNode::RenderInfo& ri)
} }
std::vector<KROctreeNode*> remainingOctrees; std::vector<KROctreeNode*> remainingOctrees;
std::vector<KROctreeNode*> remainingOctreesTestResults;
std::vector<KROctreeNode*> remainingOctreesTestResultsOnly;
if (m_nodeTree.getRootNode() != NULL) { if (m_nodeTree.getRootNode() != NULL) {
remainingOctrees.push_back(m_nodeTree.getRootNode()); remainingOctrees.push_back(m_nodeTree.getRootNode());
} }
std::vector<KROctreeNode*> newRemainingOctrees; while ((!remainingOctrees.empty())) {
std::vector<KROctreeNode*> newRemainingOctreesTestResults;
while ((!remainingOctrees.empty() || !remainingOctreesTestResults.empty())) {
newRemainingOctrees.clear();
newRemainingOctreesTestResults.clear();
for (std::vector<KROctreeNode*>::iterator octree_itr = remainingOctrees.begin(); octree_itr != remainingOctrees.end(); octree_itr++) { for (std::vector<KROctreeNode*>::iterator octree_itr = remainingOctrees.begin(); octree_itr != remainingOctrees.end(); octree_itr++) {
render(ri, resourceRequests, *octree_itr, newRemainingOctrees, newRemainingOctreesTestResults, remainingOctreesTestResultsOnly); render(ri, resourceRequests, *octree_itr);
} }
for (std::vector<KROctreeNode*>::iterator octree_itr = remainingOctreesTestResults.begin(); octree_itr != remainingOctreesTestResults.end(); octree_itr++) { remainingOctrees.clear();
render_occlusionResultsPass(ri, *octree_itr, newRemainingOctrees, false);
}
remainingOctrees = newRemainingOctrees;
remainingOctreesTestResults = newRemainingOctreesTestResults;
}
newRemainingOctrees.clear();
newRemainingOctreesTestResults.clear();
for (std::vector<KROctreeNode*>::iterator octree_itr = remainingOctreesTestResultsOnly.begin(); octree_itr != remainingOctreesTestResultsOnly.end(); octree_itr++) {
render_occlusionResultsPass(ri, *octree_itr, newRemainingOctrees, true);
} }
// TODO: WIP Refactoring, this will be moved to the streaming system // TODO: WIP Refactoring, this will be moved to the streaming system
@@ -179,9 +163,8 @@ void KRScene::render(KRNode::RenderInfo& ri)
} }
} }
void KRScene::render(KRNode::RenderInfo& ri, std::list<KRResourceRequest>& resourceRequests, KROctreeNode* pOctreeNode, std::vector<KROctreeNode*>& remainingOctrees, std::vector<KROctreeNode*>& remainingOctreesTestResults, std::vector<KROctreeNode*>& remainingOctreesTestResultsOnly) void KRScene::render(KRNode::RenderInfo& ri, std::list<KRResourceRequest>& resourceRequests, KROctreeNode* pOctreeNode)
{ {
unordered_map<AABB, int>& visibleBounds = ri.viewport->getVisibleBounds();
if (pOctreeNode) { if (pOctreeNode) {
AABB octreeBounds = pOctreeNode->getBounds(); AABB octreeBounds = pOctreeNode->getBounds();
@@ -196,198 +179,68 @@ void KRScene::render(KRNode::RenderInfo& ri, std::list<KRResourceRequest>& resou
} }
if (in_viewport) { if (in_viewport) {
// ----====---- Rendering and occlusion test pass ----====---- // Add lights that influence this octree level and its children to the stack
bool bVisible = false; int directional_light_count = 0;
bool bNeedOcclusionTest = true; int spot_light_count = 0;
int point_light_count = 0;
if (!ri.camera->settings.getEnableRealtimeOcclusion()) { for (std::set<KRNode*>::iterator itr = pOctreeNode->getSceneNodes().begin(); itr != pOctreeNode->getSceneNodes().end(); itr++) {
bVisible = true; KRNode* node = (*itr);
bNeedOcclusionTest = false; KRDirectionalLight* directional_light = dynamic_cast<KRDirectionalLight*>(node);
} if (directional_light) {
ri.directional_lights.push_back(directional_light);
if (!bVisible) { directional_light_count++;
// Assume bounding boxes are visible without occlusion test queries if the camera is inside the box. }
// The near clipping plane of the camera is taken into consideration by expanding the match area KRSpotLight* spot_light = dynamic_cast<KRSpotLight*>(node);
AABB cameraExtents = AABB::Create(ri.viewport->getCameraPosition() - Vector3::Create(ri.camera->settings.getPerspectiveNearZ()), ri.viewport->getCameraPosition() + Vector3::Create(ri.camera->settings.getPerspectiveNearZ())); if (spot_light) {
bVisible = octreeBounds.intersects(cameraExtents); ri.spot_lights.push_back(spot_light);
if (bVisible) { spot_light_count++;
// Record the frame number in which the camera was within the bounds }
visibleBounds[octreeBounds] = getContext().getCurrentFrame(); KRPointLight* point_light = dynamic_cast<KRPointLight*>(node);
bNeedOcclusionTest = false; if (point_light) {
ri.point_lights.push_back(point_light);
point_light_count++;
} }
} }
// Render objects that are at this octree level
if (!bVisible) { for (std::set<KRNode*>::iterator itr = pOctreeNode->getSceneNodes().begin(); itr != pOctreeNode->getSceneNodes().end(); itr++) {
// Check if a previous occlusion query has returned true, taking advantage of temporal consistency of visible elements from frame to frame //assert(pOctreeNode->getBounds().contains((*itr)->getBounds())); // Sanity check
// If the previous frame rendered this octree, then attempt to render it in this frame without performing a pre-occlusion test if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_PRESTREAM) {
unordered_map<AABB, int>::iterator match_itr = visibleBounds.find(octreeBounds); if ((*itr)->getLODVisibility() >= KRNode::LOD_VISIBILITY_PRESTREAM) {
if (match_itr != visibleBounds.end()) { (*itr)->preStream(*ri.viewport, resourceRequests);
if ((*match_itr).second == -1) {
// We have already tested these bounds with a negative result
bNeedOcclusionTest = false;
} else {
bVisible = true;
// We set bNeedOcclusionTest to false only when the previous occlusion test is old and we need to perform an occlusion test to record if this octree node was visible for the next frame
bNeedOcclusionTest = false;
} }
}
}
if (!bVisible && bNeedOcclusionTest) {
// Optimization: If this is an empty octree node with only a single child node, then immediately try to render the child node without an occlusion test for this higher level, as it would be more expensive than the occlusion test for the child
if (pOctreeNode->getSceneNodes().empty()) {
int child_count = 0;
for (int i = 0; i < 8; i++) {
if (pOctreeNode->getChildren()[i] != NULL) child_count++;
}
if (child_count == 1) {
bVisible = true;
bNeedOcclusionTest = false;
}
}
}
if (bNeedOcclusionTest) {
pOctreeNode->beginOcclusionQuery();
Matrix4 matModel = Matrix4();
matModel.scale(octreeBounds.size() * 0.5f);
matModel.translate(octreeBounds.center());
Matrix4 mvpmatrix = matModel * ri.viewport->getViewProjectionMatrix();
KRMeshManager::KRVBOData& vertices = getContext().getMeshManager()->KRENGINE_VBO_DATA_3D_CUBE_VERTICES;
getContext().getMeshManager()->bindVBO(ri.commandBuffer, &vertices, 1.0f);
PipelineInfo info{};
std::string shader_name("occlusion_test");
info.shader_name = &shader_name;
info.pCamera = ri.camera;
info.point_lights = &ri.point_lights;
info.directional_lights = &ri.directional_lights;
info.spot_lights = &ri.spot_lights;
info.renderPass = ri.renderPass;
info.rasterMode = RasterMode::kAdditive;
info.vertexAttributes = vertices.getVertexAttributes();
info.modelFormat = ModelFormat::KRENGINE_MODEL_FORMAT_STRIP;
KRPipeline* pPipeline = getContext().getPipelineManager()->getPipeline(*ri.surface, info);
pPipeline->bind(ri, matModel);
vkCmdDraw(ri.commandBuffer, 14, 1, 0, 0);
m_pContext->getMeshManager()->log_draw_call(ri.renderPass->getType(), "octree", "occlusion_test", 14);
pOctreeNode->endOcclusionQuery();
if (bVisible) {
// Schedule a pass to get the result of the occlusion test only for future frames and passes, without rendering the model or recurring further
remainingOctreesTestResultsOnly.push_back(pOctreeNode);
} else { } else {
// Schedule a pass to get the result of the occlusion test and continue recursion and rendering if test is true if ((*itr)->getLODVisibility() > KRNode::LOD_VISIBILITY_PRESTREAM)
remainingOctreesTestResults.push_back(pOctreeNode); {
ri.reflectedObjects.push_back(*itr);
(*itr)->render(ri);
ri.reflectedObjects.pop_back();
}
} }
} }
if (bVisible) { // Render child octrees
const int* childOctreeOrder = ri.renderPass->getType() == RenderPassType::RENDER_PASS_FORWARD_TRANSPARENT || ri.renderPass->getType() == RenderPassType::RENDER_PASS_ADDITIVE_PARTICLES || ri.renderPass->getType() == RenderPassType::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE ? ri.viewport->getBackToFrontOrder() : ri.viewport->getFrontToBackOrder();
// Add lights that influence this octree level and its children to the stack for (int i = 0; i < 8; i++) {
int directional_light_count = 0; render(ri, resourceRequests, pOctreeNode->getChildren()[childOctreeOrder[i]]);
int spot_light_count = 0; }
int point_light_count = 0;
for (std::set<KRNode*>::iterator itr = pOctreeNode->getSceneNodes().begin(); itr != pOctreeNode->getSceneNodes().end(); itr++) {
KRNode* node = (*itr);
KRDirectionalLight* directional_light = dynamic_cast<KRDirectionalLight*>(node);
if (directional_light) {
ri.directional_lights.push_back(directional_light);
directional_light_count++;
}
KRSpotLight* spot_light = dynamic_cast<KRSpotLight*>(node);
if (spot_light) {
ri.spot_lights.push_back(spot_light);
spot_light_count++;
}
KRPointLight* point_light = dynamic_cast<KRPointLight*>(node);
if (point_light) {
ri.point_lights.push_back(point_light);
point_light_count++;
}
}
// Render objects that are at this octree level // Remove lights added at this octree level from the stack
for (std::set<KRNode*>::iterator itr = pOctreeNode->getSceneNodes().begin(); itr != pOctreeNode->getSceneNodes().end(); itr++) { while (directional_light_count--) {
//assert(pOctreeNode->getBounds().contains((*itr)->getBounds())); // Sanity check ri.directional_lights.pop_back();
if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_PRESTREAM) { }
if ((*itr)->getLODVisibility() >= KRNode::LOD_VISIBILITY_PRESTREAM) { while (spot_light_count--) {
(*itr)->preStream(*ri.viewport, resourceRequests); ri.spot_lights.pop_back();
} }
} else { while (point_light_count--) {
if ((*itr)->getLODVisibility() > KRNode::LOD_VISIBILITY_PRESTREAM) ri.point_lights.pop_back();
{
ri.reflectedObjects.push_back(*itr);
(*itr)->render(ri);
ri.reflectedObjects.pop_back();
}
}
}
// Render child octrees
const int* childOctreeOrder = ri.renderPass->getType() == RenderPassType::RENDER_PASS_FORWARD_TRANSPARENT || ri.renderPass->getType() == RenderPassType::RENDER_PASS_ADDITIVE_PARTICLES || ri.renderPass->getType() == RenderPassType::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE ? ri.viewport->getBackToFrontOrder() : ri.viewport->getFrontToBackOrder();
for (int i = 0; i < 8; i++) {
render(ri, resourceRequests, pOctreeNode->getChildren()[childOctreeOrder[i]], remainingOctrees, remainingOctreesTestResults, remainingOctreesTestResultsOnly);
}
// Remove lights added at this octree level from the stack
while (directional_light_count--) {
ri.directional_lights.pop_back();
}
while (spot_light_count--) {
ri.spot_lights.pop_back();
}
while (point_light_count--) {
ri.point_lights.pop_back();
}
} }
} }
} }
} }
void KRScene::render_occlusionResultsPass(KRNode::RenderInfo& ri, KROctreeNode* pOctreeNode, std::vector<KROctreeNode*>& remainingOctrees, bool bOcclusionTestResultsOnly)
{
unordered_map<AABB, int>& visibleBounds = ri.viewport->getVisibleBounds();
if (pOctreeNode) {
AABB octreeBounds = pOctreeNode->getBounds();
// ----====---- Occlusion results pass ----====----
if (pOctreeNode->m_occlusionTested) {
int params = 0;
GLDEBUG(glGetQueryObjectuivEXT(pOctreeNode->m_occlusionQuery, GL_QUERY_RESULT_EXT, &params));
if (params) {
// Record the frame number that the test has passed on
visibleBounds[octreeBounds] = getContext().getCurrentFrame();
if (!bOcclusionTestResultsOnly) {
// Schedule a pass to perform the rendering
remainingOctrees.push_back(pOctreeNode);
}
} else {
// Record -1 to indicate that the visibility test had failed
visibleBounds[octreeBounds] = -1;
}
GLDEBUG(glDeleteQueriesEXT(1, &pOctreeNode->m_occlusionQuery));
pOctreeNode->m_occlusionTested = false;
pOctreeNode->m_occlusionQuery = 0;
}
}
// fprintf(stderr, "Octree culled: (%f, %f, %f) - (%f, %f, %f)\n", pOctreeNode->getBounds().min.x, pOctreeNode->getBounds().min.y, pOctreeNode->getBounds().min.z, pOctreeNode->getBounds().max.x, pOctreeNode->getBounds().max.y, pOctreeNode->getBounds().max.z);
}
std::string KRScene::getExtension() std::string KRScene::getExtension()
{ {
return "krscene"; return "krscene";

View File

@@ -92,8 +92,7 @@ public:
std::set<KRLight*>& getLights(); std::set<KRLight*>& getLights();
private: private:
void render(KRNode::RenderInfo& ri, std::list<KRResourceRequest>& resourceRequests, KROctreeNode* pOctreeNode, std::vector<KROctreeNode*>& remainingOctrees, std::vector<KROctreeNode*>& remainingOctreesTestResults, std::vector<KROctreeNode*>& remainingOctreesTestResultsOnly); void render(KRNode::RenderInfo& ri, std::list<KRResourceRequest>& resourceRequests, KROctreeNode* pOctreeNodey);
void render_occlusionResultsPass(KRNode::RenderInfo& ri, KROctreeNode* pOctreeNode, std::vector<KROctreeNode*>& remainingOctrees, bool bOcclusionTestResultsOnly);
KRNode* m_pRootNode; KRNode* m_pRootNode;