// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #ifndef vtkWebGPUComputeOcclusionCuller_h #define vtkWebGPUComputeOcclusionCuller_h #include "vtkCallbackCommand.h" // for the bounds recomputed callback #include "vtkCuller.h" #include "vtkNew.h" // for new macro #include "vtkRenderingWebGPUModule.h" // For export macro #include "vtkSmartPointer.h" // for the pipeline smart pointer #include "vtkWebGPUComputePass.h" // for compute passes #include "vtkWebGPUComputePipeline.h" // for the member compute pipeline #include "vtkWebGPURenderWindow.h" // for the render window weak pointer member #include "vtkWrappingHints.h" // For VTK_MARSHALAUTO VTK_ABI_NAMESPACE_BEGIN /** * This culler does both frustum culling and occlusion culling. * * Occlusion culling culls props that are occluded (behind) other props and that are not visible to * the camera because of that. * * This implementation uses the two-pass hierarchical z-buffer approach. * * This approach projects the bound of the actors onto the viewport and compares the depth of the * projected region with a prepass depth buffer. This "prepass" depth buffer is built from the * objects that were rendered last frame. These objects offer a good approximation of what objects * will be visible this frame (assuming no brutal camera movements). To make the depth comparison * between the quad of the actor (projection of its bounding box on the viewport) more efficient, a * mipmap chain of the depth buffer is used. Without this mipmap chain, we would have to compare the * depth of all the pixels (there could be dozens to hundreds of thousands depending on the * screen-space size of the actor) of the projected bounding box of the actor against the depth * buffer which would be way too expensive. Using a mipmap chain allows us to choose the right * mipmap so that we only have to check a few (~4 +/- 2) pixels for the depth. * * Resource for a general overview of the algorithm: * https://medium.com/@mil_kru/two-pass-occlusion-culling-4100edcad501 * * Resource for non-power of two mipmap calculation: * https://miketuritzin.com/post/hierarchical-depth-buffers/ * * To use this culler, simply instantiate it and set its RenderWindow (after Initialize() has been * called on the RenderWindow()): * * vtkNew webgpuOcclusionCuller; * * renWin->Initialize(); * webgpuOcclusionCuller->SetRenderWindow(renWin) * * Then add it to the cullers of your renderer: * * renderer->GetCullers()->AddItem(webgpuOcclusionCuller); * * You may also want to remove the default CPU coverage culler of your renderer by calling : * * renderer->GetCullers()->RemoveAllItems(); * * before adding the webgpuOcclusionCuller. * * @warning: In its current state, the compute occlusion culler is expected to fail if the WebGPU * backend used is OpenGL. This is because OpenGL has its texture coordinate origin (0, 0) at the * bottom left corner of the texture whereas the shader of the occlusion culler expects the (0, 0) * of the texture to be in the top left corner. With OpenGL, this will cause reads into the depth * buffer to be reversed along the Y axis and incorrect depth values will be read --> invalid * culling. */ class VTKRENDERINGWEBGPU_EXPORT VTK_MARSHALAUTO vtkWebGPUComputeOcclusionCuller : public vtkCuller { public: vtkTypeMacro(vtkWebGPUComputeOcclusionCuller, vtkCuller); static vtkWebGPUComputeOcclusionCuller* New(); void PrintSelf(ostream& os, vtkIndent indent) override; /** * Sets which render window this occlusion culler is going to work on */ void SetRenderWindow(vtkWebGPURenderWindow* webGpuRenderWindow); /** * Culls props and returns the number of props that still need to be rendered after the culling */ double Cull( vtkRenderer* renderer, vtkProp** propList, int& listLength, int& initialized) override; protected: vtkWebGPUComputeOcclusionCuller(); ~vtkWebGPUComputeOcclusionCuller() override; private: vtkWebGPUComputeOcclusionCuller(const vtkWebGPUComputeOcclusionCuller&) = delete; void operator=(const vtkWebGPUComputeOcclusionCuller&) = delete; /** * Sets up the first compute pass for copying the depth buffer of the render window to the first * mip level (level 0) of the hierarchical Z-buffer */ void SetupDepthBufferCopyPass(); /** * Sets up the compute pass for compute the max-mipmaps of the depth buffer */ void SetupMipmapsPass(); /** * Sets up the buffer used in the culling pass */ void SetupCullingPass(); /** * First render pass that renders the props that were visible last frame and that passed the * previous culling tests (if any). This pass is needed to fill the z-buffer. * * Return a list of the props that were not rendered for the first (filling the depth buffer) but * that need to be tested for culling (they are in the propList given to the Cull() call). */ void FirstPassRender(vtkRenderer* renderer, vtkProp** propList, int listLength); /** * Copies the depth buffer filled by the rendering of the props of last frame into the mipmap * level 0 of the hierarchical z-buffer */ void CopyDepthBuffer(); /** * Computes the depth buffer max-mipmaps */ void DepthMipmaps(); /** * Culls the actors using the depth buffer mipmaps computed in the previous pass */ void PropCulling(vtkRenderer* renderer, vtkProp** propList, int& listLength); /** * Reuploads the camera MVP matrix to its GPU buffer */ void UpdateCameraMVPBuffer(vtkRenderer* renderer); /** * Resizes the various bounds buffers (inputBounds, outputBoundsIndices) and updates their data */ void UpdateBoundsBuffers(vtkProp** propList, int listLength); /** * Adds the occlusion culling pipeline to the passed renderer so that the pipeline can reuse the * textures from the render window of the renderer */ void AddOcclusionCullingPipelineToRenderer(vtkRenderer* renderer); /** * Sets-up the hierarchical z-buffer mipmapped texture */ void CreateHierarchicalZBuffer(); /** * Computes the number of mip levels for the given width and height and returns that number. * * The widths and heights of all the mip levels are also stored in MipmapWidths and MipmapHeights */ int ComputeMipLevelsSizes(int width, int height); /** * Resizes the hi zbuffer texture to the given new width and height. The level 0 of the new * texture isn't initialized and the mipmaps are not immediately recomputed. */ void ResizeHierarchicalZBuffer(uint32_t newWidth, uint32_t newHeight); /** * Recomputes the number of mipmaps of the hi-z buffer for the given newWidth and newHeight and * recreates all the texture views on the mipmap levels of the hi-z buffer */ void ResizeHierarchicalZBufferMipmapsChain(); /** * Creates the texture view of the hierarchical z buffer for copying the depth buffer of the * render window into it */ void FinishSetupDepthCopyPass(); /** * Creates the texture views for all the mipmap levels that are going to be needed to downsample * the depth buffer */ void FinishSetupMipmapsPass(); /** * Adds the texture view of the hierarchical z buffer to the culling pass */ void FinishSetupCullingPass(); /** * Callback to read the number of props that passed the culling test */ static void ReadIndicesCountCallback(const void* mappedData, void* indicesCount); /** * Callback for reading the props that passed the culling test and store them in the propList of * the renderer (passed through the userdata of the callback). * * For a prop to be written to the propList that will be rendered, it needs to have passed the * culling test but also not having been rendered in the first pass (because rendering it twice is * useless so we're not adding a prop that was already rendered to the list of props that need to * be rendered). */ static void FillObjectsToDrawCallback(const void* mappedData, void* data); /** * Callback that reads the indices of the props that were culled by the occlusion culling. These * props are then removed from the list of "props rendered last frame" of the wgpu renderer */ static void OutputIndicesCulledCallback(const void* mappedData, void* data); /** * Callback called when the render window of this occlusion culler is resized. This callback * resizes the hierarchical z-buffer */ static void WindowResizedCallback( vtkObject* caller, unsigned long eid, void* clientdata, void* calldata); // Occlusion culling pipeline vtkSmartPointer OcclusionCullingPipeline; // Pass that copies the depth buffer of the render window into the mip level 0 of the hierarchical // z-buffer vtkSmartPointer DepthBufferCopyPass; // Index of the hierarchical z buffer in the depth buffer copy compute pass int HierarchicalZBufferTextureIndexCopyPass = -1; // Index of the hierarchical z buffer in the depth buffer mipmaps pass int HierarchicalZBufferTextureIndexMipmapsPass = -1; // Index of the hierarchical z buffer in the culling pass int HierarchicalZBufferTextureIndexCullingPass = -1; // All the views necessary for the computation of the depth buffer mipmaps std::vector> HierarchicalZBufferMipmapViews; // Texture view indices within the DepthMipmapsPass compute pass std::vector HierarchicalZBufferMipmapViewsIndices; // Total number of mipmaps of the hierarchical z buffer int HierarchicalZBufferMipmapCount = -1; // Widths of the successive mipmaps of the hierarchical z buffer std::vector MipmapWidths; // Heights of the successive mipmaps of the hierarchical z buffer std::vector MipmapHeights; // We need to keep the uniform buffer here because we can only create it once the RenderWindow has // set its Device to the pipeline of this occlusion culler. We will add the buffer to the pipeline // on the first frame, when we're sure that the Device has been set // Pass that downsamples the mipmap level 0 of the depth buffer into as many mipmap levels as // possible vtkSmartPointer DepthMipmapsPass; // Pass that does the culling of the actors against the hierarchial z-buffer vtkSmartPointer CullingPass; // Index of the hierarchical z buffer texture view in the culling pass int CullingPassHierarchicalZBufferView = -1; // Index of the bounds buffer in the culling pass int CullingPassBoundsBufferIndex = -1; // Index of the buffer that contains the indices of the props that passed the culling test in the // culling pass int CullingPassOutputIndicesBufferIndex = -1; // How many props that were sent to the culling shader passed the culling test int CullingPassOutputIndicesCountBufferIndex = -1; // Index of the buffer that contains the indices of the props that were culled. Needed to update // the visibility of the props in the PropsRendered array of the WGPURenderer int CullingPassOutputIndicesCulledBufferIndex = -1; // How many props were culled by the culling pass int CullingPassOutputIndicesCulledCountBufferIndex = -1; // Index of the buffer that contains the number of bounds to cull in the culling pass int CullingPassBoundsCountBufferIndex = -1; // Index of the buffer that contains the view projection matrix in the culling pass int CullingPassMVPMatrixBufferIndex = -1; // Structure passed to the callbacks for reading the results of the culling pass struct FillObjectsToDrawCallbackMapData { // How many props passed the culling test. // This is a pointer to the 'listLength' parameter of the Cull() method int* listLength = nullptr; // Prop list of the renderer that needs to be updated vtkProp** propList = nullptr; // WebGPU renderer // Used for accessing the 'rendered last frame' list vtkWebGPURenderer* renderer = nullptr; }; // Structure passed to the callbacks for reading the results of the culling pass struct OutputIndicesCulledMapData { // Point to the renderer for removing the props that were culled from the list of "props // rendered last frame" vtkWebGPURenderer* renderer = nullptr; // Prop list of the renderer vtkProp** propList = nullptr; // How many props were culled int culledCount = -1; }; // If this is the first frame, every object is going to be rendered b the FirstPassRender to fill // the z-buffer bool FirstFrame = true; // Whether or not we're done initializing the compute passes of this culler bool Initialized = false; // Render window whose depth buffer we're going to use for the culling vtkWeakPointer WebGPURenderWindow = nullptr; // Callback command for when the render window that this occlusion culler is attached to is // resized vtkSmartPointer WindowResizedCallbackCommand; }; VTK_ABI_NAMESPACE_END #endif