From 3200a5f929ad05be8328894e07f316f8e08f6c4c Mon Sep 17 00:00:00 2001 From: David Rose Date: Sat, 2 Mar 2002 00:11:24 +0000 Subject: [PATCH] pgraph view-frustum cull --- panda/src/display/config_display.cxx | 10 ++ panda/src/display/config_display.h | 2 + panda/src/display/graphicsEngine.cxx | 221 +++++++++++++++------------ panda/src/display/graphicsEngine.h | 7 + panda/src/pgraph/config_pgraph.cxx | 5 + panda/src/pgraph/config_pgraph.h | 2 + panda/src/pgraph/qpcullTraverser.cxx | 139 ++++++++++++++++- panda/src/pgraph/qpcullTraverser.h | 9 +- 8 files changed, 292 insertions(+), 103 deletions(-) diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 38dd731b8e..b4c3459613 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -58,6 +58,16 @@ const bool pipe_spec_is_remote = config_display.Defined("pipe-machine") const bool compare_state_by_pointer = config_display.GetBool("compare-state-by-pointer", true); +// This is normally true to enable the cull traversal to perform +// state-sorting and alpha-sorting. Turn this false to disable these +// features and likely improve cull performance at the expense of draw +// (and at the expense of correct alpha). +const bool cull_sorting = config_display.GetBool("cull-sorting", true); + +// This is normally true; set it false to disable view-frustum culling +// (primarily useful for debugging). +const bool qpview_frustum_cull = config_display.GetBool("view-frustum-cull", true); + const float gsg_clear_r = config_display.GetFloat("gsg-clear-r", 0.0); const float gsg_clear_g = config_display.GetFloat("gsg-clear-g", 0.0); const float gsg_clear_b = config_display.GetFloat("gsg-clear-b", 0.0); diff --git a/panda/src/display/config_display.h b/panda/src/display/config_display.h index 2ae0091acc..69f583b190 100644 --- a/panda/src/display/config_display.h +++ b/panda/src/display/config_display.h @@ -36,6 +36,8 @@ extern const bool pipe_spec_is_file; extern const bool pipe_spec_is_remote; extern const bool compare_state_by_pointer; +extern const bool cull_sorting; +extern const bool qpview_frustum_cull; extern const float gsg_clear_r; extern const float gsg_clear_g; diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index 7f3dbf9fb0..6d502dc14c 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////// #include "graphicsEngine.h" +#include "config_display.h" #include "pipeline.h" #include "drawCullHandler.h" #include "binCullHandler.h" @@ -79,8 +80,11 @@ remove_window(GraphicsWindow *window) { //////////////////////////////////////////////////////////////////// void GraphicsEngine:: render_frame() { - // cull_and_draw_together(); - cull_bin_draw(); + if (cull_sorting) { + cull_bin_draw(); + } else { + cull_and_draw_together(); + } // **** This doesn't belong here; it really belongs in the Pipeline, // but here it is for now. @@ -120,68 +124,18 @@ cull_and_draw_together() { //////////////////////////////////////////////////////////////////// void GraphicsEngine:: cull_and_draw_together(GraphicsWindow *win, DisplayRegion *dr) { - const NodeChain &camera = dr->get_qpcamera(); - if (camera.is_empty()) { - // No camera, no draw. - return; - } - - qpCamera *camera_node; - DCAST_INTO_V(camera_node, camera.node()); - - if (!camera_node->is_active()) { - // Camera inactive, no draw. - return; - } - - Lens *lens = camera_node->get_lens(); - if (lens == (Lens *)NULL) { - // No lens, no draw. - return; - } - - NodeChain scene = camera_node->get_scene(); - if (scene.is_empty()) { - // No scene, no draw. - return; - } - GraphicsStateGuardian *gsg = win->get_gsg(); nassertv(gsg != (GraphicsStateGuardian *)NULL); - if (!gsg->set_lens(lens)) { - // The lens is inappropriate somehow. - display_cat.error() - << gsg->get_type() << " cannot render with " << lens->get_type() - << "\n"; - return; + if (set_gsg_lens(gsg, dr)) { + DisplayRegionStack old_dr = gsg->push_display_region(dr); + gsg->prepare_display_region(); + + DrawCullHandler cull_handler(gsg); + do_cull(&cull_handler, dr->get_qpcamera(), gsg); + + gsg->pop_display_region(old_dr); } - - DrawCullHandler cull_handler(gsg); - qpCullTraverser trav; - trav.set_cull_handler(&cull_handler); - - // The world transform is computed from the camera's position; we - // then might need to adjust it into the GSG's internal coordinate - // system. - trav.set_camera_transform(scene.get_rel_transform(camera)); - - CPT(TransformState) render_transform = camera.get_rel_transform(scene); - CoordinateSystem external_cs = gsg->get_coordinate_system(); - CoordinateSystem internal_cs = gsg->get_internal_coordinate_system(); - if (internal_cs != CS_default && internal_cs != external_cs) { - CPT(TransformState) cs_transform = - TransformState::make_mat(LMatrix4f::convert_mat(external_cs, internal_cs)); - render_transform = cs_transform->compose(render_transform); - } - trav.set_render_transform(render_transform); - - DisplayRegionStack old_dr = gsg->push_display_region(dr); - gsg->prepare_display_region(); - - trav.traverse(scene.node()); - - gsg->pop_display_region(old_dr); } //////////////////////////////////////////////////////////////////// @@ -217,7 +171,34 @@ cull_bin_draw() { //////////////////////////////////////////////////////////////////// void GraphicsEngine:: cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) { - const NodeChain &camera = dr->get_qpcamera(); + GraphicsStateGuardian *gsg = win->get_gsg(); + nassertv(gsg != (GraphicsStateGuardian *)NULL); + + PT(CullResult) cull_result = dr->_cull_result; + if (cull_result == (CullResult *)NULL) { + cull_result = new CullResult(gsg); + } + + BinCullHandler cull_handler(cull_result); + do_cull(&cull_handler, dr->get_qpcamera(), gsg); + + cull_result->finish_cull(); + + // Save the results for next frame. + dr->_cull_result = cull_result->make_next(); + + // Now draw. + do_draw(cull_result, gsg, dr); +} + +//////////////////////////////////////////////////////////////////// +// Function: GraphicsEngine::do_cull +// Access: Private +// Description: Fires off a cull traversal using the indicated camera. +//////////////////////////////////////////////////////////////////// +void GraphicsEngine:: +do_cull(CullHandler *cull_handler, const NodeChain &camera, + GraphicsStateGuardian *gsg) { if (camera.is_empty()) { // No camera, no draw. return; @@ -243,32 +224,21 @@ cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) { return; } - GraphicsStateGuardian *gsg = win->get_gsg(); - nassertv(gsg != (GraphicsStateGuardian *)NULL); - - if (!gsg->set_lens(lens)) { - // The lens is inappropriate somehow. - display_cat.error() - << gsg->get_type() << " cannot render with " << lens->get_type() - << "\n"; - return; - } - - PT(CullResult) cull_result = dr->_cull_result; - if (cull_result == (CullResult *)NULL) { - cull_result = new CullResult(gsg); - } - - BinCullHandler cull_handler(cull_result); qpCullTraverser trav; - trav.set_cull_handler(&cull_handler); + trav.set_cull_handler(cull_handler); - // The world transform is computed from the camera's position; we - // then might need to adjust it into the GSG's internal coordinate - // system. - trav.set_camera_transform(scene.get_rel_transform(camera)); + // We will need both the camera transform (the net transform from + // the scene to the camera) and the world transform (the camera + // transform inverse, or the net transform from the camera to the + // scene). + CPT(TransformState) camera_transform = scene.get_rel_transform(camera); + CPT(TransformState) world_transform = camera.get_rel_transform(scene); + + // The render transform is the same as the world transform, except + // it is converted into the GSG's internal coordinate system. This + // is the transform that the GSG will apply to all of its vertices. + CPT(TransformState) render_transform = world_transform; - CPT(TransformState) render_transform = camera.get_rel_transform(scene); CoordinateSystem external_cs = gsg->get_coordinate_system(); CoordinateSystem internal_cs = gsg->get_internal_coordinate_system(); if (internal_cs != CS_default && internal_cs != external_cs) { @@ -276,17 +246,80 @@ cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) { TransformState::make_mat(LMatrix4f::convert_mat(external_cs, internal_cs)); render_transform = cs_transform->compose(render_transform); } + + trav.set_camera_transform(scene.get_rel_transform(camera)); trav.set_render_transform(render_transform); + + if (qpview_frustum_cull) { + // If we're to be performing view-frustum culling, determine the + // bounding volume associated with the current viewing frustum. + + // First, we have to get the current viewing frustum, which comes + // from the lens. + PT(BoundingVolume) bv = lens->make_bounds(); + + if (bv != (BoundingVolume *)NULL && + bv->is_of_type(GeometricBoundingVolume::get_class_type())) { + // Transform it into the appropriate coordinate space. + PT(GeometricBoundingVolume) local_frustum; + local_frustum = DCAST(GeometricBoundingVolume, bv->make_copy()); + local_frustum->xform(camera_transform->get_mat()); + + trav.set_view_frustum(local_frustum); + } + } + trav.traverse(scene.node()); - cull_result->finish_cull(); - - // Save the results for next frame. - dr->_cull_result = cull_result->make_next(); - - // Now draw. - DisplayRegionStack old_dr = gsg->push_display_region(dr); - gsg->prepare_display_region(); - cull_result->draw(); - gsg->pop_display_region(old_dr); +} + +//////////////////////////////////////////////////////////////////// +// Function: GraphicsEngine::do_draw +// Access: Private +// Description: Draws the previously-culled scene. +//////////////////////////////////////////////////////////////////// +void GraphicsEngine:: +do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg, + DisplayRegion *dr) { + if (set_gsg_lens(gsg, dr)) { + DisplayRegionStack old_dr = gsg->push_display_region(dr); + gsg->prepare_display_region(); + cull_result->draw(); + gsg->pop_display_region(old_dr); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GraphicsEngine::set_gsg_lens +// Access: Private +// Description: Sets up the GSG to draw with the lens from the +// indicated DisplayRegion. Returns true if the lens is +// acceptable, false otherwise. +//////////////////////////////////////////////////////////////////// +bool GraphicsEngine:: +set_gsg_lens(GraphicsStateGuardian *gsg, DisplayRegion *dr) { + const NodeChain &camera = dr->get_qpcamera(); + if (camera.is_empty()) { + // No camera, no draw. + return false; + } + + qpCamera *camera_node; + DCAST_INTO_R(camera_node, camera.node(), false); + + Lens *lens = camera_node->get_lens(); + if (lens == (Lens *)NULL) { + // No lens, no draw. + return false; + } + + if (!gsg->set_lens(lens)) { + // The lens is inappropriate somehow. + display_cat.error() + << gsg->get_type() << " cannot render with " << lens->get_type() + << "\n"; + return false; + } + + return true; } diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index 86e4e97617..e5b86f5a41 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -57,6 +57,13 @@ private: void cull_bin_draw(); void cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr); + void do_cull(CullHandler *cull_handler, const NodeChain &camera, + GraphicsStateGuardian *gsg); + void do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg, + DisplayRegion *dr); + + bool set_gsg_lens(GraphicsStateGuardian *gsg, DisplayRegion *dr); + Pipeline *_pipeline; typedef pset Windows; diff --git a/panda/src/pgraph/config_pgraph.cxx b/panda/src/pgraph/config_pgraph.cxx index 2c9b331861..ae3408bf59 100644 --- a/panda/src/pgraph/config_pgraph.cxx +++ b/panda/src/pgraph/config_pgraph.cxx @@ -47,6 +47,11 @@ ConfigureFn(config_pgraph) { init_libpgraph(); } +// Set this true to cause culling to be performed by rendering the +// object in red wireframe, rather than actually culling it. This +// helps make culling errors obvious. +const bool qpfake_view_frustum_cull = config_pgraph.GetBool("fake-view-frustum-cull", false); + //////////////////////////////////////////////////////////////////// // Function: init_libpgraph diff --git a/panda/src/pgraph/config_pgraph.h b/panda/src/pgraph/config_pgraph.h index 4226777c01..4f235538b9 100644 --- a/panda/src/pgraph/config_pgraph.h +++ b/panda/src/pgraph/config_pgraph.h @@ -26,6 +26,8 @@ ConfigureDecl(config_pgraph, EXPCL_PANDA, EXPTP_PANDA); NotifyCategoryDecl(pgraph, EXPCL_PANDA, EXPTP_PANDA); +extern const bool qpfake_view_frustum_cull; + extern EXPCL_PANDA void init_libpgraph(); #endif diff --git a/panda/src/pgraph/qpcullTraverser.cxx b/panda/src/pgraph/qpcullTraverser.cxx index 973bbe509a..6b4b237363 100644 --- a/panda/src/pgraph/qpcullTraverser.cxx +++ b/panda/src/pgraph/qpcullTraverser.cxx @@ -23,6 +23,9 @@ #include "cullHandler.h" #include "dcast.h" #include "qpgeomNode.h" +#include "colorAttrib.h" +#include "textureAttrib.h" +#include "config_pgraph.h" //////////////////////////////////////////////////////////////////// // Function: qpCullTraverser::Constructor @@ -74,6 +77,39 @@ set_render_transform(const TransformState *render_transform) { _render_transform = render_transform; } +//////////////////////////////////////////////////////////////////// +// Function: qpCullTraverser::set_view_frustum +// Access: Public +// Description: Specifies the bounding volume that corresponds to the +// viewing frustum. Any primitives that fall entirely +// outside of this volume are not drawn. +//////////////////////////////////////////////////////////////////// +void qpCullTraverser:: +set_view_frustum(GeometricBoundingVolume *view_frustum) { + _view_frustum = view_frustum; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpCullTraverser::set_guard_band +// Access: Public +// Description: Specifies the bounding volume to use for detecting +// guard band clipping. This is a render optimization +// for certain cards that support this feature; the +// guard band is a 2-d area than the frame buffer. +// If a primitive will appear entirely within the guard +// band after perspective transform, it may be drawn +// correctly with clipping disabled, for a small +// performance gain. +// +// This is the bounding volume that corresponds to the +// 2-d guard band. If a primitive is entirely within +// this area, clipping will be disabled on the GSG. +//////////////////////////////////////////////////////////////////// +void qpCullTraverser:: +set_guard_band(GeometricBoundingVolume *guard_band) { + _guard_band = guard_band; +} + //////////////////////////////////////////////////////////////////// // Function: qpCullTraverser::set_cull_handler // Access: Public @@ -95,7 +131,7 @@ traverse(PandaNode *root) { nassertv(_cull_handler != (CullHandler *)NULL); r_traverse(root, _render_transform, TransformState::make_identity(), - _initial_state, 0); + _initial_state, _view_frustum, _guard_band); } //////////////////////////////////////////////////////////////////// @@ -107,12 +143,93 @@ void qpCullTraverser:: r_traverse(PandaNode *node, const TransformState *render_transform, const TransformState *net_transform, - const RenderState *state, int flags) { - CPT(TransformState) next_render_transform = - render_transform->compose(node->get_transform()); - CPT(TransformState) next_net_transform = - net_transform->compose(node->get_transform()); - CPT(RenderState) next_state = state->compose(node->get_state()); + const RenderState *state, + GeometricBoundingVolume *view_frustum, + GeometricBoundingVolume *guard_band) { + CPT(RenderState) next_state = state; + + if (view_frustum != (GeometricBoundingVolume *)NULL) { + // If we have a viewing frustum, check to see if the node's + // bounding volume falls within it. + const BoundingVolume &node_volume = node->get_bound(); + nassertv(node_volume.is_of_type(GeometricBoundingVolume::get_class_type())); + const GeometricBoundingVolume *node_gbv = + DCAST(GeometricBoundingVolume, &node_volume); + + int result = view_frustum->contains(node_gbv); + if (result == BoundingVolume::IF_no_intersection) { + // No intersection at all. Cull. + if (!qpfake_view_frustum_cull) { + return; + } + + // If we have fake view-frustum culling enabled, instead of + // actually culling an object we simply force it to be drawn in + // red wireframe. + view_frustum = (GeometricBoundingVolume *)NULL; + CPT(RenderState) fake_effect = RenderState::make + (ColorAttrib::make_flat(Colorf(1.0f, 0.0f, 0.0f, 1.0f)), + TextureAttrib::make_off(), + 1000); + next_state = next_state->compose(fake_effect); + + } else if ((result & BoundingVolume::IF_all) != 0) { + // The node and its descendants are completely enclosed within + // the frustum. No need to cull further. + view_frustum = (GeometricBoundingVolume *)NULL; + + } else { + if (node->is_final()) { + // The bounding volume is partially, but not completely, + // within the viewing frustum. Normally we'd keep testing + // child bounded volumes as we continue down. But this node + // has the "final" flag, so the user is claiming that there is + // some important reason we should consider everything visible + // at this point. So be it. + view_frustum = (GeometricBoundingVolume *)NULL; + } + } + } + + CPT(TransformState) next_render_transform = render_transform; + CPT(TransformState) next_net_transform = net_transform; + PT(GeometricBoundingVolume) next_view_frustum = view_frustum; + PT(GeometricBoundingVolume) next_guard_band = guard_band; + + const TransformState *transform = node->get_transform(); + if (!transform->is_identity()) { + next_render_transform = render_transform->compose(transform); + next_net_transform = net_transform->compose(transform); + + if ((view_frustum != (GeometricBoundingVolume *)NULL) || + (guard_band != (GeometricBoundingVolume *)NULL)) { + // We need to move the viewing frustums into the node's + // coordinate space by applying the node's inverse transform. + if (transform->is_singular()) { + // But we can't invert a singular transform! Instead of + // trying, we'll just give up on frustum culling from this + // point down. + view_frustum = (GeometricBoundingVolume *)NULL; + guard_band = (GeometricBoundingVolume *)NULL; + + } else { + CPT(TransformState) inv_transform = + transform->invert_compose(TransformState::make_identity()); + + if (view_frustum != (GeometricBoundingVolume *)NULL) { + next_view_frustum = DCAST(GeometricBoundingVolume, view_frustum->make_copy()); + next_view_frustum->xform(inv_transform->get_mat()); + } + + if (guard_band != (GeometricBoundingVolume *)NULL) { + next_guard_band = DCAST(GeometricBoundingVolume, guard_band->make_copy()); + next_guard_band->xform(inv_transform->get_mat()); + } + } + } + } + + next_state = next_state->compose(node->get_state()); const BillboardAttrib *billboard = state->get_billboard(); if (billboard != (const BillboardAttrib *)NULL) { @@ -121,6 +238,11 @@ r_traverse(PandaNode *node, billboard->do_billboard(net_transform, _camera_transform); next_render_transform = next_render_transform->compose(billboard_transform); next_net_transform = next_net_transform->compose(billboard_transform); + + // We can't reliably cull within a billboard, because the geometry + // might get rotated out of its bounding volume. So once we get + // within a billboard, we consider it all visible. + next_view_frustum = (GeometricBoundingVolume *)NULL; } if (node->is_geom_node()) { @@ -140,6 +262,7 @@ r_traverse(PandaNode *node, PandaNode::Children cr = node->get_children(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { - r_traverse(cr.get_child(i), next_render_transform, next_net_transform, next_state, flags); + r_traverse(cr.get_child(i), next_render_transform, next_net_transform, + next_state, next_view_frustum, next_guard_band); } } diff --git a/panda/src/pgraph/qpcullTraverser.h b/panda/src/pgraph/qpcullTraverser.h index e5b5595912..8b7c494748 100644 --- a/panda/src/pgraph/qpcullTraverser.h +++ b/panda/src/pgraph/qpcullTraverser.h @@ -23,6 +23,7 @@ #include "renderState.h" #include "transformState.h" +#include "geometricBoundingVolume.h" #include "pointerTo.h" class PandaNode; @@ -43,6 +44,8 @@ public: void set_initial_state(const RenderState *initial_state); void set_camera_transform(const TransformState *camera_transform); void set_render_transform(const TransformState *render_transform); + void set_view_frustum(GeometricBoundingVolume *view_frustum); + void set_guard_band(GeometricBoundingVolume *guard_band); void set_cull_handler(CullHandler *cull_handler); void traverse(PandaNode *root); @@ -50,11 +53,15 @@ public: private: void r_traverse(PandaNode *node, const TransformState *render_transform, const TransformState *net_transform, - const RenderState *state, int flags); + const RenderState *state, + GeometricBoundingVolume *view_frustum, + GeometricBoundingVolume *guard_band); CPT(RenderState) _initial_state; CPT(TransformState) _camera_transform; CPT(TransformState) _render_transform; + PT(GeometricBoundingVolume) _view_frustum; + PT(GeometricBoundingVolume) _guard_band; CullHandler *_cull_handler; };