You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
816 lines
30 KiB
816 lines
30 KiB
#include "cocos/renderer/pipeline/Define.h"
|
|
#include "cocos/renderer/pipeline/custom/LayoutGraphUtils.h"
|
|
#include "cocos/renderer/pipeline/custom/NativeBuiltinUtils.h"
|
|
#include "cocos/renderer/pipeline/custom/NativePipelineTypes.h"
|
|
#include "cocos/renderer/pipeline/custom/NativeRenderGraphUtils.h"
|
|
#include "cocos/renderer/pipeline/custom/details/GslUtils.h"
|
|
#include "cocos/renderer/pipeline/custom/details/Range.h"
|
|
#include "cocos/scene/Octree.h"
|
|
#include "cocos/scene/ReflectionProbe.h"
|
|
#include "cocos/scene/RenderScene.h"
|
|
#include "cocos/scene/Skybox.h"
|
|
#include "cocos/scene/SpotLight.h"
|
|
|
|
#include <boost/align/align_up.hpp>
|
|
|
|
namespace cc {
|
|
|
|
namespace render {
|
|
|
|
const static uint32_t REFLECTION_PROBE_DEFAULT_MASK = ~static_cast<uint32_t>(pipeline::LayerList::UI_2D) & ~static_cast<uint32_t>(pipeline::LayerList::PROFILER) & ~static_cast<uint32_t>(pipeline::LayerList::UI_3D) & ~static_cast<uint32_t>(pipeline::LayerList::GIZMOS) & ~static_cast<uint32_t>(pipeline::LayerList::SCENE_GIZMO) & ~static_cast<uint32_t>(pipeline::LayerList::EDITOR);
|
|
|
|
void NativeRenderQueue::clear() noexcept {
|
|
probeQueue.clear();
|
|
opaqueQueue.instances.clear();
|
|
transparentQueue.instances.clear();
|
|
opaqueInstancingQueue.clear();
|
|
transparentInstancingQueue.clear();
|
|
sceneFlags = SceneFlags::NONE;
|
|
subpassOrPassLayoutID = 0xFFFFFFFF;
|
|
}
|
|
|
|
bool NativeRenderQueue::empty() const noexcept {
|
|
return opaqueQueue.instances.empty() &&
|
|
transparentQueue.instances.empty() &&
|
|
opaqueInstancingQueue.empty() &&
|
|
transparentInstancingQueue.empty();
|
|
}
|
|
|
|
FrustumCullingID SceneCulling::getOrCreateFrustumCulling(const SceneData& sceneData) {
|
|
const auto* const scene = sceneData.scene;
|
|
// get or add scene to queries
|
|
auto& queries = frustumCullings[scene];
|
|
|
|
// check cast shadow
|
|
const bool bCastShadow = any(sceneData.flags & SceneFlags::SHADOW_CASTER);
|
|
|
|
// get or create query source
|
|
// make query key
|
|
const auto key = FrustumCullingKey{
|
|
sceneData.camera,
|
|
sceneData.light.probe,
|
|
sceneData.light.light,
|
|
sceneData.light.level,
|
|
bCastShadow,
|
|
};
|
|
|
|
// find query source
|
|
auto iter = queries.resultIndex.find(key);
|
|
if (iter == queries.resultIndex.end()) {
|
|
// create query source
|
|
// make query source id
|
|
const FrustumCullingID frustomCulledResultID{numFrustumCulling++};
|
|
if (numFrustumCulling > frustumCullingResults.size()) {
|
|
// space is not enough, create query source
|
|
CC_EXPECTS(numFrustumCulling == frustumCullingResults.size() + 1);
|
|
frustumCullingResults.emplace_back();
|
|
}
|
|
// add query source to query index
|
|
bool added = false;
|
|
std::tie(iter, added) = queries.resultIndex.emplace(key, frustomCulledResultID);
|
|
CC_ENSURES(added);
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
LightBoundsCullingID SceneCulling::getOrCreateLightBoundsCulling(
|
|
const SceneData& sceneData, FrustumCullingID frustumCullingID) {
|
|
if (!any(sceneData.cullingFlags & CullingFlags::LIGHT_BOUNDS)) {
|
|
return {};
|
|
}
|
|
if (sceneData.shadingLight->getType() == scene::LightType::DIRECTIONAL) {
|
|
return {};
|
|
}
|
|
if (!enableLightCulling) {
|
|
return {};
|
|
}
|
|
|
|
CC_EXPECTS(sceneData.shadingLight);
|
|
const auto* const scene = sceneData.scene;
|
|
CC_EXPECTS(scene);
|
|
|
|
auto& queries = lightBoundsCullings[scene];
|
|
|
|
// get or create query source
|
|
// make query key
|
|
const auto key = LightBoundsCullingKey{
|
|
frustumCullingID,
|
|
sceneData.camera,
|
|
sceneData.light.probe,
|
|
sceneData.shadingLight,
|
|
};
|
|
|
|
// find query source
|
|
auto iter = queries.resultIndex.find(key);
|
|
if (iter == queries.resultIndex.end()) {
|
|
// create query source
|
|
// make query source id
|
|
const LightBoundsCullingID lightBoundsCullingID{numLightBoundsCulling++};
|
|
if (numLightBoundsCulling > lightBoundsCullingResults.size()) {
|
|
// space is not enough, create query source
|
|
CC_EXPECTS(numLightBoundsCulling == lightBoundsCullingResults.size() + 1);
|
|
lightBoundsCullingResults.emplace_back();
|
|
}
|
|
// add query source to query index
|
|
bool added = false;
|
|
std::tie(iter, added) = queries.resultIndex.emplace(key, lightBoundsCullingID);
|
|
CC_ENSURES(added);
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
NativeRenderQueueID SceneCulling::createRenderQueue(
|
|
SceneFlags sceneFlags, LayoutGraphData::vertex_descriptor subpassOrPassLayoutID) {
|
|
const auto targetID = numRenderQueues++;
|
|
if (numRenderQueues > renderQueues.size()) {
|
|
CC_EXPECTS(numRenderQueues == renderQueues.size() + 1);
|
|
renderQueues.emplace_back();
|
|
}
|
|
CC_ENSURES(targetID < renderQueues.size());
|
|
auto& rq = renderQueues[targetID];
|
|
CC_EXPECTS(rq.empty());
|
|
CC_EXPECTS(rq.sceneFlags == SceneFlags::NONE);
|
|
CC_EXPECTS(rq.subpassOrPassLayoutID == 0xFFFFFFFF);
|
|
rq.sceneFlags = sceneFlags;
|
|
rq.subpassOrPassLayoutID = subpassOrPassLayoutID;
|
|
return NativeRenderQueueID{targetID};
|
|
}
|
|
|
|
void SceneCulling::collectCullingQueries(
|
|
const RenderGraph& rg, const LayoutGraphData& lg) {
|
|
for (const auto vertID : makeRange(vertices(rg))) {
|
|
if (!holds<SceneTag>(vertID, rg)) {
|
|
continue;
|
|
}
|
|
const auto& sceneData = get(SceneTag{}, vertID, rg);
|
|
if (!sceneData.scene) {
|
|
CC_EXPECTS(false);
|
|
continue;
|
|
}
|
|
const auto frustomCulledResultID = getOrCreateFrustumCulling(sceneData);
|
|
const auto lightBoundsCullingID = getOrCreateLightBoundsCulling(sceneData, frustomCulledResultID);
|
|
const auto layoutID = getSubpassOrPassID(vertID, rg, lg);
|
|
const auto targetID = createRenderQueue(sceneData.flags, layoutID);
|
|
const auto lightType = sceneData.light.light
|
|
? sceneData.light.light->getType()
|
|
: scene::LightType::UNKNOWN;
|
|
|
|
// add render queue to query source
|
|
renderQueueIndex.emplace(
|
|
vertID,
|
|
NativeRenderQueueDesc{
|
|
frustomCulledResultID,
|
|
lightBoundsCullingID,
|
|
targetID,
|
|
lightType,
|
|
});
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pipeline::PipelineSceneData* kPipelineSceneData = nullptr;
|
|
|
|
const LayoutGraphData* kLayoutGraph = nullptr;
|
|
|
|
bool isNodeVisible(const Node* node, uint32_t visibility) {
|
|
return node && ((visibility & node->getLayer()) == node->getLayer());
|
|
}
|
|
|
|
uint32_t isModelVisible(const scene::Model& model, uint32_t visibility) {
|
|
return visibility & static_cast<uint32_t>(model.getVisFlags());
|
|
}
|
|
|
|
bool isReflectProbeMask(const scene::Model& model) {
|
|
return ((model.getNode()->getLayer() & REFLECTION_PROBE_DEFAULT_MASK) == model.getNode()->getLayer()) || (REFLECTION_PROBE_DEFAULT_MASK & static_cast<uint32_t>(model.getVisFlags()));
|
|
}
|
|
|
|
bool isIntersectAABB(const geometry::AABB& lAABB, const geometry::AABB& rAABB) {
|
|
return !lAABB.aabbAabb(rAABB);
|
|
}
|
|
|
|
bool isFrustumVisible(const scene::Model& model, const geometry::Frustum& frustum, bool castShadow) {
|
|
const auto* const modelWorldBounds = model.getWorldBounds();
|
|
if (!modelWorldBounds) {
|
|
return false;
|
|
}
|
|
geometry::AABB transWorldBounds{};
|
|
transWorldBounds.set(modelWorldBounds->getCenter(), modelWorldBounds->getHalfExtents());
|
|
const scene::Shadows& shadows = *kPipelineSceneData->getShadows();
|
|
if (shadows.getType() == scene::ShadowType::PLANAR && castShadow) {
|
|
modelWorldBounds->transform(shadows.getMatLight(), &transWorldBounds);
|
|
}
|
|
return !transWorldBounds.aabbFrustum(frustum);
|
|
}
|
|
|
|
void octreeCulling(
|
|
const scene::Octree& octree,
|
|
const scene::Model* skyboxModel,
|
|
const scene::RenderScene& scene,
|
|
const scene::Camera& camera,
|
|
const geometry::Frustum& cameraOrLightFrustum,
|
|
bool bCastShadow,
|
|
ccstd::vector<const scene::Model*>& models) {
|
|
const auto visibility = camera.getVisibility();
|
|
const auto camSkyboxFlag = (static_cast<int32_t>(camera.getClearFlag()) & scene::Camera::SKYBOX_FLAG);
|
|
if (!bCastShadow && skyboxModel && camSkyboxFlag) {
|
|
models.emplace_back(skyboxModel);
|
|
}
|
|
// add instances without world bounds
|
|
for (const auto& pModel : scene.getModels()) {
|
|
CC_EXPECTS(pModel);
|
|
const auto& model = *pModel;
|
|
if (!model.isEnabled() || !model.getNode() || model.getWorldBounds() || (bCastShadow && !model.isCastShadow())) {
|
|
continue;
|
|
}
|
|
// filter model by view visibility
|
|
if (isNodeVisible(model.getNode(), visibility) || isModelVisible(model, visibility)) {
|
|
models.emplace_back(&model);
|
|
}
|
|
}
|
|
// add instances with world bounds
|
|
octree.queryVisibility(&camera, cameraOrLightFrustum, bCastShadow, models);
|
|
|
|
// TODO(zhouzhenglong): move lod culling into octree query
|
|
auto iter = std::remove_if(
|
|
models.begin(), models.end(),
|
|
[&](const scene::Model* model) {
|
|
return scene.isCulledByLod(&camera, model);
|
|
});
|
|
models.erase(iter, models.end());
|
|
}
|
|
|
|
void bruteForceCulling(
|
|
const scene::Model* skyboxModel,
|
|
const scene::RenderScene& scene,
|
|
const scene::Camera& camera,
|
|
const geometry::Frustum& cameraOrLightFrustum,
|
|
bool bCastShadow,
|
|
const scene::ReflectionProbe* probe,
|
|
ccstd::vector<const scene::Model*>& models) {
|
|
const auto visibility = camera.getVisibility();
|
|
const auto camSkyboxFlag = (static_cast<int32_t>(camera.getClearFlag()) & scene::Camera::SKYBOX_FLAG);
|
|
if (!bCastShadow && skyboxModel && camSkyboxFlag) {
|
|
models.emplace_back(skyboxModel);
|
|
}
|
|
for (const auto& pModel : scene.getModels()) {
|
|
CC_EXPECTS(pModel);
|
|
const auto& model = *pModel;
|
|
if (!model.isEnabled() || !model.getNode() || (bCastShadow && !model.isCastShadow())) {
|
|
continue;
|
|
}
|
|
// lod culling
|
|
if (scene.isCulledByLod(&camera, &model)) {
|
|
continue;
|
|
}
|
|
if (!probe || (probe && probe->getProbeType() == cc::scene::ReflectionProbe::ProbeType::CUBE)) {
|
|
// filter model by view visibility
|
|
if (isNodeVisible(model.getNode(), visibility) || isModelVisible(model, visibility)) {
|
|
const auto* const wBounds = model.getWorldBounds();
|
|
// frustum culling
|
|
if (wBounds && ((!probe && isFrustumVisible(model, cameraOrLightFrustum, bCastShadow)) ||
|
|
(probe && isIntersectAABB(*wBounds, *probe->getBoundingBox())))) {
|
|
continue;
|
|
}
|
|
|
|
models.emplace_back(&model);
|
|
}
|
|
} else if (isReflectProbeMask(model)) {
|
|
models.emplace_back(&model);
|
|
}
|
|
}
|
|
}
|
|
|
|
void sceneCulling(
|
|
const scene::Model* skyboxModel,
|
|
const scene::RenderScene& scene,
|
|
const scene::Camera& camera,
|
|
const geometry::Frustum& cameraOrLightFrustum,
|
|
bool bCastShadow,
|
|
const scene::ReflectionProbe* probe,
|
|
ccstd::vector<const scene::Model*>& models) {
|
|
const auto* const octree = scene.getOctree();
|
|
if (octree && octree->isEnabled() && !probe) {
|
|
octreeCulling(
|
|
*octree, skyboxModel,
|
|
scene, camera, cameraOrLightFrustum, bCastShadow, models);
|
|
} else {
|
|
bruteForceCulling(
|
|
skyboxModel,
|
|
scene, camera, cameraOrLightFrustum, bCastShadow, probe, models);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SceneCulling::batchFrustumCulling(const NativePipeline& ppl) {
|
|
const auto& pplSceneData = *ppl.getPipelineSceneData();
|
|
const auto* const skybox = pplSceneData.getSkybox();
|
|
const auto* const skyboxModel = skybox && skybox->isEnabled() ? skybox->getModel() : nullptr;
|
|
|
|
for (const auto& [scene, queries] : frustumCullings) {
|
|
CC_ENSURES(scene);
|
|
for (const auto& [key, frustomCulledResultID] : queries.resultIndex) {
|
|
CC_EXPECTS(key.camera);
|
|
CC_EXPECTS(key.camera->getScene() == scene);
|
|
const auto* light = key.light;
|
|
const auto level = key.lightLevel;
|
|
const auto bCastShadow = key.castShadow;
|
|
const auto* probe = key.probe;
|
|
const auto& camera = probe ? *probe->getCamera() : *key.camera;
|
|
CC_EXPECTS(frustomCulledResultID.value < frustumCullingResults.size());
|
|
auto& models = frustumCullingResults[frustomCulledResultID.value];
|
|
|
|
if (probe) {
|
|
sceneCulling(
|
|
skyboxModel,
|
|
*scene, camera,
|
|
camera.getFrustum(),
|
|
bCastShadow,
|
|
probe,
|
|
models);
|
|
continue;
|
|
}
|
|
|
|
if (light) {
|
|
switch (light->getType()) {
|
|
case scene::LightType::SPOT:
|
|
sceneCulling(
|
|
skyboxModel,
|
|
*scene, camera,
|
|
dynamic_cast<const scene::SpotLight*>(light)->getFrustum(),
|
|
bCastShadow,
|
|
nullptr,
|
|
models);
|
|
break;
|
|
case scene::LightType::DIRECTIONAL: {
|
|
const auto* mainLight = dynamic_cast<const scene::DirectionalLight*>(light);
|
|
const auto& frustum = getBuiltinShadowFrustum(ppl, camera, mainLight, level);
|
|
sceneCulling(
|
|
skyboxModel,
|
|
*scene, camera,
|
|
frustum,
|
|
bCastShadow,
|
|
nullptr,
|
|
models);
|
|
} break;
|
|
default:
|
|
// noop
|
|
break;
|
|
}
|
|
} else {
|
|
sceneCulling(
|
|
skyboxModel,
|
|
*scene, camera,
|
|
camera.getFrustum(),
|
|
bCastShadow,
|
|
nullptr,
|
|
models);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
void executeSphereLightCulling(
|
|
const scene::SphereLight& light,
|
|
const ccstd::vector<const scene::Model*>& frustumCullingResult,
|
|
ccstd::vector<const scene::Model*>& lightBoundsCullingResult) {
|
|
const auto& lightAABB = light.getAABB();
|
|
for (const auto* const model : frustumCullingResult) {
|
|
CC_EXPECTS(model);
|
|
const auto* const modelBounds = model->getWorldBounds();
|
|
if (!modelBounds || modelBounds->aabbAabb(lightAABB)) {
|
|
lightBoundsCullingResult.emplace_back(model);
|
|
}
|
|
}
|
|
}
|
|
|
|
void executeSpotLightCulling(
|
|
const scene::SpotLight& light,
|
|
const ccstd::vector<const scene::Model*>& frustumCullingResult,
|
|
ccstd::vector<const scene::Model*>& lightBoundsCullingResult) {
|
|
const auto& lightAABB = light.getAABB();
|
|
const auto& lightFrustum = light.getFrustum();
|
|
for (const auto* const model : frustumCullingResult) {
|
|
CC_EXPECTS(model);
|
|
const auto* const modelBounds = model->getWorldBounds();
|
|
if (!modelBounds || (modelBounds->aabbAabb(lightAABB) && modelBounds->aabbFrustum(lightFrustum))) {
|
|
lightBoundsCullingResult.emplace_back(model);
|
|
}
|
|
}
|
|
}
|
|
|
|
void executePointLightCulling(
|
|
const scene::PointLight& light,
|
|
const ccstd::vector<const scene::Model*>& frustumCullingResult,
|
|
ccstd::vector<const scene::Model*>& lightBoundsCullingResult) {
|
|
const auto& lightAABB = light.getAABB();
|
|
for (const auto* const model : frustumCullingResult) {
|
|
CC_EXPECTS(model);
|
|
const auto* const modelBounds = model->getWorldBounds();
|
|
if (!modelBounds || modelBounds->aabbAabb(lightAABB)) {
|
|
lightBoundsCullingResult.emplace_back(model);
|
|
}
|
|
}
|
|
}
|
|
|
|
void executeRangedDirectionalLightCulling(
|
|
const scene::RangedDirectionalLight& light,
|
|
const ccstd::vector<const scene::Model*>& frustumCullingResult,
|
|
ccstd::vector<const scene::Model*>& lightBoundsCullingResult) {
|
|
const geometry::AABB rangedDirLightBoundingBox(0.0F, 0.0F, 0.0F, 0.5F, 0.5F, 0.5F);
|
|
// when execute render graph, we should never update world matrix
|
|
// light->getNode()->updateWorldTransform();
|
|
geometry::AABB lightAABB{};
|
|
rangedDirLightBoundingBox.transform(light.getNode()->getWorldMatrix(), &lightAABB);
|
|
for (const auto* const model : frustumCullingResult) {
|
|
CC_EXPECTS(model);
|
|
const auto* const modelBounds = model->getWorldBounds();
|
|
if (!modelBounds || modelBounds->aabbAabb(lightAABB)) {
|
|
lightBoundsCullingResult.emplace_back(model);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SceneCulling::batchLightBoundsCulling() {
|
|
for (const auto& [scene, queries] : lightBoundsCullings) {
|
|
CC_ENSURES(scene);
|
|
for (const auto& [key, cullingID] : queries.resultIndex) {
|
|
CC_EXPECTS(key.camera);
|
|
CC_EXPECTS(key.camera->getScene() == scene);
|
|
const auto& frustumCullingResult = frustumCullingResults.at(key.frustumCullingID.value);
|
|
auto& lightBoundsCullingResult = lightBoundsCullingResults.at(cullingID.value);
|
|
CC_EXPECTS(lightBoundsCullingResult.instances.empty());
|
|
switch (key.cullingLight->getType()) {
|
|
case scene::LightType::SPHERE: {
|
|
const auto* light = dynamic_cast<const scene::SphereLight*>(key.cullingLight);
|
|
CC_ENSURES(light);
|
|
executeSphereLightCulling(*light, frustumCullingResult, lightBoundsCullingResult.instances);
|
|
} break;
|
|
case scene::LightType::SPOT: {
|
|
const auto* light = dynamic_cast<const scene::SpotLight*>(key.cullingLight);
|
|
CC_ENSURES(light);
|
|
executeSpotLightCulling(*light, frustumCullingResult, lightBoundsCullingResult.instances);
|
|
} break;
|
|
case scene::LightType::POINT: {
|
|
const auto* light = dynamic_cast<const scene::PointLight*>(key.cullingLight);
|
|
CC_ENSURES(light);
|
|
executePointLightCulling(*light, frustumCullingResult, lightBoundsCullingResult.instances);
|
|
} break;
|
|
case scene::LightType::RANGED_DIRECTIONAL: {
|
|
const auto* light = dynamic_cast<const scene::RangedDirectionalLight*>(key.cullingLight);
|
|
CC_ENSURES(light);
|
|
executeRangedDirectionalLightCulling(*light, frustumCullingResult, lightBoundsCullingResult.instances);
|
|
} break;
|
|
case scene::LightType::DIRECTIONAL:
|
|
case scene::LightType::UNKNOWN:
|
|
default:
|
|
// noop
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool isBlend(const scene::Pass& pass) {
|
|
bool bBlend = false;
|
|
for (const auto& target : pass.getBlendState()->targets) {
|
|
if (target.blend) {
|
|
bBlend = true;
|
|
}
|
|
}
|
|
return bBlend;
|
|
}
|
|
|
|
float computeSortingDepth(const scene::Camera& camera, const scene::Model& model) {
|
|
float depth = 0;
|
|
if (model.getNode()) {
|
|
const auto* node = model.getTransform();
|
|
Vec3 position;
|
|
const auto* bounds = model.getWorldBounds();
|
|
Vec3::subtract(bounds ? bounds->center : node->getWorldPosition(), camera.getPosition(), &position);
|
|
depth = position.dot(camera.getForward());
|
|
}
|
|
return depth;
|
|
}
|
|
|
|
void addRenderObject(
|
|
LayoutGraphData::vertex_descriptor phaseLayoutID,
|
|
const bool bDrawOpaqueOrMask,
|
|
const bool bDrawBlend,
|
|
const bool bDrawProbe,
|
|
const scene::Camera& camera,
|
|
const scene::Model& model,
|
|
NativeRenderQueue& queue) {
|
|
if (bDrawProbe) {
|
|
queue.probeQueue.applyMacro(*kLayoutGraph, model, phaseLayoutID);
|
|
}
|
|
const auto& subModels = model.getSubModels();
|
|
const auto subModelCount = subModels.size();
|
|
for (uint32_t subModelIdx = 0; subModelIdx < subModelCount; ++subModelIdx) {
|
|
const auto& subModel = subModels[subModelIdx];
|
|
const auto& passes = *(subModel->getPasses());
|
|
const auto passCount = passes.size();
|
|
auto probeIt = std::find(queue.probeQueue.probeMap.begin(), queue.probeQueue.probeMap.end(), subModel.get());
|
|
if (probeIt != queue.probeQueue.probeMap.end()) {
|
|
phaseLayoutID = ProbeHelperQueue::getDefaultId(*kLayoutGraph);
|
|
}
|
|
for (uint32_t passIdx = 0; passIdx < passCount; ++passIdx) {
|
|
auto& pass = *passes[passIdx];
|
|
// check phase
|
|
const bool bRenderPhaseAllowed = phaseLayoutID == pass.getPhaseID();
|
|
if (!bRenderPhaseAllowed) {
|
|
continue;
|
|
}
|
|
|
|
// check scene flags
|
|
const bool bBlend = isBlend(pass);
|
|
const bool bOpaqueOrMask = !bBlend;
|
|
if (!bDrawBlend && bBlend) {
|
|
// skip transparent object
|
|
continue;
|
|
}
|
|
if (!bDrawOpaqueOrMask && bOpaqueOrMask) {
|
|
// skip opaque object
|
|
continue;
|
|
}
|
|
|
|
// add object to queue
|
|
if (pass.getBatchingScheme() == scene::BatchingSchemes::INSTANCING) {
|
|
if (bBlend) {
|
|
queue.transparentInstancingQueue.add(pass, *subModel, passIdx);
|
|
} else {
|
|
queue.opaqueInstancingQueue.add(pass, *subModel, passIdx);
|
|
}
|
|
} else {
|
|
// TODO(zhouzhenglong): change camera to frustum
|
|
const float depth = computeSortingDepth(camera, model);
|
|
if (bBlend) {
|
|
queue.transparentQueue.add(model, depth, subModelIdx, passIdx);
|
|
} else {
|
|
queue.opaqueQueue.add(model, depth, subModelIdx, passIdx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SceneCulling::fillRenderQueues(
|
|
const RenderGraph& rg, const pipeline::PipelineSceneData& pplSceneData) {
|
|
const auto* const skybox = pplSceneData.getSkybox();
|
|
for (auto&& [sceneID, desc] : renderQueueIndex) {
|
|
CC_EXPECTS(holds<SceneTag>(sceneID, rg));
|
|
const auto frustomCulledResultID = desc.frustumCulledResultID;
|
|
const auto lightBoundsCullingID = desc.lightBoundsCulledResultID;
|
|
const auto targetID = desc.renderQueueTarget;
|
|
const auto& sceneData = get(SceneTag{}, sceneID, rg);
|
|
|
|
// check scene flags
|
|
const bool bDrawBlend = any(sceneData.flags & SceneFlags::TRANSPARENT_OBJECT);
|
|
const bool bDrawOpaqueOrMask = any(sceneData.flags & (SceneFlags::OPAQUE_OBJECT | SceneFlags::CUTOUT_OBJECT));
|
|
const bool bDrawShadowCaster = any(sceneData.flags & SceneFlags::SHADOW_CASTER);
|
|
const bool bDrawProbe = any(sceneData.flags & SceneFlags::REFLECTION_PROBE);
|
|
if (!bDrawShadowCaster && !bDrawBlend && !bDrawOpaqueOrMask && !bDrawProbe) {
|
|
// nothing to draw
|
|
continue;
|
|
}
|
|
|
|
// render queue info
|
|
const auto renderQueueID = parent(sceneID, rg);
|
|
CC_EXPECTS(holds<QueueTag>(renderQueueID, rg));
|
|
const auto& renderQueue = get(QueueTag{}, renderQueueID, rg);
|
|
const auto phaseLayoutID = renderQueue.phaseID;
|
|
CC_EXPECTS(phaseLayoutID != LayoutGraphData::null_vertex());
|
|
|
|
// culling source
|
|
CC_EXPECTS(frustomCulledResultID.value < frustumCullingResults.size());
|
|
const auto& sourceModels = [&]() -> const auto& {
|
|
// is culled by light bounds
|
|
if (lightBoundsCullingID.value != 0xFFFFFFFF) {
|
|
CC_EXPECTS(lightBoundsCullingID.value < lightBoundsCullingResults.size());
|
|
return lightBoundsCullingResults.at(lightBoundsCullingID.value).instances;
|
|
}
|
|
// not culled by light bounds
|
|
return frustumCullingResults.at(frustomCulledResultID.value);
|
|
}
|
|
();
|
|
|
|
// native queue target
|
|
CC_EXPECTS(targetID.value < renderQueues.size());
|
|
auto& nativeQueue = renderQueues[targetID.value];
|
|
CC_EXPECTS(nativeQueue.empty());
|
|
|
|
// skybox
|
|
const auto* camera = sceneData.camera;
|
|
CC_EXPECTS(camera);
|
|
|
|
// fill native queue
|
|
for (const auto* const model : sourceModels) {
|
|
addRenderObject(
|
|
phaseLayoutID, bDrawOpaqueOrMask, bDrawBlend,
|
|
bDrawProbe, *sceneData.camera, *model, nativeQueue);
|
|
}
|
|
|
|
// post-processing
|
|
nativeQueue.sort();
|
|
}
|
|
}
|
|
|
|
void SceneCulling::buildRenderQueues(
|
|
const RenderGraph& rg, const LayoutGraphData& lg,
|
|
const NativePipeline& ppl) {
|
|
kPipelineSceneData = ppl.pipelineSceneData;
|
|
kLayoutGraph = ≶
|
|
collectCullingQueries(rg, lg);
|
|
batchFrustumCulling(ppl);
|
|
batchLightBoundsCulling(); // cull frustum-culling's results by light bounds
|
|
fillRenderQueues(rg, *ppl.pipelineSceneData);
|
|
}
|
|
|
|
void SceneCulling::clear() noexcept {
|
|
// frustum culling
|
|
frustumCullings.clear();
|
|
for (auto& c : frustumCullingResults) {
|
|
c.clear();
|
|
}
|
|
// light bounds culling
|
|
lightBoundsCullings.clear();
|
|
for (auto& c : lightBoundsCullingResults) {
|
|
c.instances.clear();
|
|
c.lightByteOffset = 0xFFFFFFFF;
|
|
}
|
|
// native render queues
|
|
for (auto& q : renderQueues) {
|
|
q.clear();
|
|
}
|
|
|
|
// clear render graph scene vertex query index
|
|
renderQueueIndex.clear();
|
|
// do not clear this->renderQueues, it is reused to avoid memory allocation
|
|
|
|
// reset all counters
|
|
numFrustumCulling = 0;
|
|
numLightBoundsCulling = 0;
|
|
numRenderQueues = 0;
|
|
}
|
|
|
|
void LightResource::init(const NativeProgramLibrary& programLib, gfx::Device* deviceIn, uint32_t maxNumLightsIn) {
|
|
CC_EXPECTS(!device);
|
|
device = deviceIn;
|
|
programLibrary = &programLib;
|
|
|
|
const auto& instanceLayout = programLibrary->localLayoutData;
|
|
const auto attrID = at(programLib.layoutGraph.attributeIndex, std::string_view{"CCForwardLight"});
|
|
const auto& uniformBlock = instanceLayout.uniformBlocks.at(attrID);
|
|
|
|
elementSize = boost::alignment::align_up(
|
|
getUniformBlockSize(uniformBlock.members),
|
|
device->getCapabilities().uboOffsetAlignment);
|
|
maxNumLights = maxNumLightsIn;
|
|
binding = programLib.localLayoutData.bindingMap.at(attrID);
|
|
|
|
const auto bufferSize = elementSize * maxNumLights;
|
|
|
|
lightBuffer = device->createBuffer(gfx::BufferInfo{
|
|
gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST,
|
|
gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE,
|
|
bufferSize,
|
|
elementSize,
|
|
});
|
|
firstLightBufferView = device->createBuffer({lightBuffer, 0, elementSize});
|
|
|
|
cpuBuffer.resize(bufferSize);
|
|
lights.reserve(maxNumLights);
|
|
lightIndex.reserve(maxNumLights);
|
|
|
|
CC_ENSURES(elementSize);
|
|
CC_ENSURES(maxNumLights);
|
|
|
|
resized = true;
|
|
}
|
|
|
|
uint32_t LightResource::addLight(
|
|
const scene::Light* light,
|
|
bool bHDR,
|
|
float exposure,
|
|
const scene::Shadows* shadowInfo) {
|
|
// already added
|
|
auto iter = lightIndex.find(light);
|
|
if (iter != lightIndex.end()) {
|
|
return iter->second;
|
|
}
|
|
|
|
// resize buffer
|
|
if (lights.size() == maxNumLights) {
|
|
resized = true;
|
|
maxNumLights *= 2;
|
|
const auto bufferSize = elementSize * maxNumLights;
|
|
lightBuffer->resize(bufferSize);
|
|
firstLightBufferView = device->createBuffer({lightBuffer, 0, elementSize});
|
|
cpuBuffer.resize(bufferSize);
|
|
lights.reserve(maxNumLights);
|
|
lightIndex.reserve(maxNumLights);
|
|
}
|
|
CC_ENSURES(lights.size() < maxNumLights);
|
|
|
|
// add light
|
|
const auto lightID = static_cast<uint32_t>(lights.size());
|
|
lights.emplace_back(light);
|
|
auto res = lightIndex.emplace(light, lightID);
|
|
CC_ENSURES(res.second);
|
|
|
|
// update buffer
|
|
const auto offset = elementSize * lightID;
|
|
setLightUBO(light, bHDR, exposure, shadowInfo, cpuBuffer.data() + offset, elementSize);
|
|
|
|
return lightID * elementSize;
|
|
}
|
|
|
|
void LightResource::buildLights(
|
|
SceneCulling& sceneCulling,
|
|
bool bHDR,
|
|
const scene::Shadows* shadowInfo) {
|
|
// build light buffer
|
|
for (const auto& [scene, lightBoundsCullings] : sceneCulling.lightBoundsCullings) {
|
|
for (const auto& [key, lightBoundsCullingID] : lightBoundsCullings.resultIndex) {
|
|
float exposure = 1.0F;
|
|
if (key.camera) {
|
|
exposure = key.camera->getExposure();
|
|
} else if (key.probe && key.probe->getCamera()) {
|
|
exposure = key.probe->getCamera()->getExposure();
|
|
} else {
|
|
CC_EXPECTS(false);
|
|
}
|
|
const auto lightByteOffset = addLight(
|
|
key.cullingLight,
|
|
bHDR,
|
|
exposure,
|
|
shadowInfo);
|
|
|
|
// save light byte offset for each light bounds culling
|
|
auto& result = sceneCulling.lightBoundsCullingResults.at(lightBoundsCullingID.value);
|
|
result.lightByteOffset = lightByteOffset;
|
|
}
|
|
}
|
|
|
|
// assign light byte offset to each queue
|
|
for (const auto& [sceneID, desc] : sceneCulling.renderQueueIndex) {
|
|
if (desc.lightBoundsCulledResultID.value == 0xFFFFFFFF) {
|
|
continue;
|
|
}
|
|
const auto lightByteOffset = sceneCulling.lightBoundsCullingResults.at(
|
|
desc.lightBoundsCulledResultID.value)
|
|
.lightByteOffset;
|
|
|
|
sceneCulling.renderQueues.at(desc.renderQueueTarget.value).lightByteOffset = lightByteOffset;
|
|
}
|
|
}
|
|
|
|
void LightResource::clear() {
|
|
std::fill(cpuBuffer.begin(), cpuBuffer.end(), 0);
|
|
lights.clear();
|
|
lightIndex.clear();
|
|
}
|
|
|
|
void LightResource::buildLightBuffer(gfx::CommandBuffer* cmdBuffer) const {
|
|
if (lights.empty()) {
|
|
return;
|
|
}
|
|
cmdBuffer->updateBuffer(
|
|
lightBuffer,
|
|
cpuBuffer.data(),
|
|
static_cast<uint32_t>(lights.size()) * elementSize);
|
|
}
|
|
|
|
void LightResource::tryUpdateRenderSceneLocalDescriptorSet(const SceneCulling& sceneCulling) {
|
|
if (sceneCulling.lightBoundsCullingResults.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& [scene, culling] : sceneCulling.frustumCullings) {
|
|
for (const auto& model : scene->getModels()) {
|
|
CC_EXPECTS(model);
|
|
for (const auto& submodel : model->getSubModels()) {
|
|
auto* set = submodel->getDescriptorSet();
|
|
const auto& prev = set->getBuffer(binding);
|
|
if (resized || prev != firstLightBufferView) {
|
|
set->bindBuffer(binding, firstLightBufferView);
|
|
set->update();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resized = false;
|
|
}
|
|
|
|
} // namespace render
|
|
|
|
} // namespace cc
|
|
|