| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941 |
- // Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
- // All rights reserved.
- // Code licensed under the BSD License.
- // http://www.anki3d.org/LICENSE
- #include <AnKi/Renderer/Utils/GpuVisibility.h>
- #include <AnKi/Renderer/Renderer.h>
- #include <AnKi/Scene/RenderStateBucket.h>
- #include <AnKi/Scene/GpuSceneArray.h>
- #include <AnKi/Core/GpuMemory/GpuVisibleTransientMemoryPool.h>
- #include <AnKi/Core/GpuMemory/RebarTransientMemoryPool.h>
- #include <AnKi/Core/GpuMemory/GpuSceneBuffer.h>
- #include <AnKi/Collision/Functions.h>
- #include <AnKi/Shaders/Include/GpuVisibilityTypes.h>
- #include <AnKi/Core/GpuMemory/UnifiedGeometryBuffer.h>
- #include <AnKi/Core/StatsSet.h>
- #include <AnKi/Core/CVarSet.h>
- namespace anki {
- constexpr U32 kMaxVisibleObjects = 30 * 1024;
- constexpr U32 kMaxVisiblePrimitives = 40'000'000;
- constexpr U32 kMaxVisibleMeshlets = kMaxVisiblePrimitives / kMaxPrimitivesPerMeshlet;
- constexpr PtrSize kMaxMeshletMemory = kMaxVisibleMeshlets * sizeof(GpuSceneMeshletInstance);
- constexpr U32 kVisibleMaxMeshletGroups = max(kMaxVisibleObjects, (kMaxVisibleMeshlets + kMeshletGroupSize - 1) / kMeshletGroupSize);
- constexpr PtrSize kMaxMeshletGroupMemory = kVisibleMaxMeshletGroups * sizeof(GpuSceneMeshletGroupInstance);
- static NumericCVar<PtrSize> g_maxMeshletMemoryPerTest(CVarSubsystem::kRenderer, "MaxMeshletMemoryPerTest", kMaxMeshletMemory, 1_KB, 100_MB,
- "Max memory that will be allocated per GPU occlusion test for storing meshlets");
- static NumericCVar<PtrSize> g_maxMeshletGroupMemoryPerTest(CVarSubsystem::kRenderer, "MaxMeshletGroupMemoryPerTest", kMaxMeshletGroupMemory, 1_KB,
- 100_MB,
- "Max memory that will be allocated per GPU occlusion test for storing meshlet groups");
- static StatCounter g_gpuVisMemoryAllocatedStatVar(StatCategory::kRenderer, "GPU visibility mem",
- StatFlag::kBytes | StatFlag::kMainThreadUpdates | StatFlag::kZeroEveryFrame);
- static BufferView allocateTransientGpuMem(PtrSize size)
- {
- BufferView out = {};
- if(size)
- {
- g_gpuVisMemoryAllocatedStatVar.increment(size);
- out = GpuVisibleTransientMemoryPool::getSingleton().allocate(size);
- }
- return out;
- }
- Error GpuVisibility::init()
- {
- for(MutatorValue hzb = 0; hzb < 2; ++hzb)
- {
- for(MutatorValue gatherAabbs = 0; gatherAabbs < 2; ++gatherAabbs)
- {
- for(MutatorValue genHash = 0; genHash < 2; ++genHash)
- {
- for(MutatorValue gatherType = 0; gatherType < 3; ++gatherType)
- {
- ANKI_CHECK(loadShaderProgram("ShaderBinaries/GpuVisibility.ankiprogbin",
- {{"HZB_TEST", hzb},
- {"DISTANCE_TEST", 0},
- {"GATHER_AABBS", gatherAabbs},
- {"HASH_VISIBLES", genHash},
- {"GATHER_TYPE", gatherType + 1}},
- m_prog, m_frustumGrProgs[hzb][gatherAabbs][genHash][gatherType]));
- }
- }
- }
- }
- for(MutatorValue gatherAabbs = 0; gatherAabbs < 2; ++gatherAabbs)
- {
- for(MutatorValue genHash = 0; genHash < 2; ++genHash)
- {
- for(MutatorValue gatherType = 0; gatherType < 3; ++gatherType)
- {
- ANKI_CHECK(loadShaderProgram("ShaderBinaries/GpuVisibility.ankiprogbin",
- {{"HZB_TEST", 0},
- {"DISTANCE_TEST", 1},
- {"GATHER_AABBS", gatherAabbs},
- {"HASH_VISIBLES", genHash},
- {"GATHER_TYPE", gatherType + 1}},
- m_prog, m_distGrProgs[gatherAabbs][genHash][gatherType]));
- }
- }
- }
- for(MutatorValue hzb = 0; hzb < 2; ++hzb)
- {
- for(MutatorValue passthrough = 0; passthrough < 2; ++passthrough)
- {
- ANKI_CHECK(loadShaderProgram("ShaderBinaries/GpuVisibilityMeshlet.ankiprogbin", {{"HZB_TEST", hzb}, {"PASSTHROUGH", passthrough}},
- m_meshletCullingProg, m_meshletCullingGrProgs[hzb][passthrough]));
- }
- }
- return Error::kNone;
- }
- void GpuVisibility::computeGpuVisibilityMemoryRequirements(RenderingTechnique t, MemoryRequirements& total, WeakArray<MemoryRequirements> perBucket)
- {
- ANKI_ASSERT(perBucket.getSize() == RenderStateBucketContainer::getSingleton().getBucketCount(t));
- U32 totalMeshletCount = 0;
- U32 totalMeshletGroupCount = 0;
- U32 totalRenderableCount = 0;
- RenderStateBucketContainer::getSingleton().iterateBuckets(t, [&](const RenderStateInfo&, U32 userCount, U32 meshletGroupCount, U32 meshletCount) {
- if(meshletCount)
- {
- totalMeshletCount += meshletCount;
- totalMeshletGroupCount += meshletGroupCount;
- }
- else
- {
- totalRenderableCount += userCount;
- }
- });
- const U32 maxVisibleMeshlets = min(U32(g_maxMeshletMemoryPerTest.get() / sizeof(GpuSceneMeshletInstance)), totalMeshletCount);
- const U32 maxVisibleMeshletGroups = min(U32(g_maxMeshletGroupMemoryPerTest.get() / sizeof(GpuSceneMeshletGroupInstance)), totalMeshletGroupCount);
- const U32 maxVisibleRenderables = min(kMaxVisibleObjects, totalRenderableCount);
- total = {};
- U32 bucketCount = 0;
- RenderStateBucketContainer::getSingleton().iterateBuckets(t, [&](const RenderStateInfo&, U32 userCount, U32 meshletGroupCount, U32 meshletCount) {
- MemoryRequirements& bucket = perBucket[bucketCount++];
- // Use U64 cause some expressions are overflowing
- if(meshletCount)
- {
- ANKI_ASSERT(meshletGroupCount > 0);
- ANKI_ASSERT(totalMeshletCount > 0);
- bucket.m_meshletInstanceCount = max(1u, U32(U64(meshletCount) * maxVisibleMeshlets / totalMeshletCount));
- ANKI_ASSERT(totalMeshletGroupCount > 0);
- bucket.m_meshletGroupInstanceCount = max(1u, U32(U64(meshletGroupCount) * maxVisibleMeshletGroups / totalMeshletGroupCount));
- }
- else if(userCount > 0)
- {
- ANKI_ASSERT(totalRenderableCount > 0);
- bucket.m_renderableInstanceCount = max(1u, U32(U64(userCount) * maxVisibleRenderables / totalRenderableCount));
- }
- total.m_meshletInstanceCount += bucket.m_meshletInstanceCount;
- total.m_meshletGroupInstanceCount += bucket.m_meshletGroupInstanceCount;
- total.m_renderableInstanceCount += bucket.m_renderableInstanceCount;
- });
- }
- void GpuVisibility::populateRenderGraphInternal(Bool distanceBased, BaseGpuVisibilityInput& in, GpuVisibilityOutput& out)
- {
- ANKI_ASSERT(in.m_lodReferencePoint.x() != kMaxF32);
- if(RenderStateBucketContainer::getSingleton().getBucketsActiveUserCount(in.m_technique) == 0) [[unlikely]]
- {
- // Early exit
- in = {};
- return;
- }
- RenderGraphDescription& rgraph = *in.m_rgraph;
- class DistanceTestData
- {
- public:
- Vec3 m_pointOfTest;
- F32 m_testRadius;
- };
- class FrustumTestData
- {
- public:
- RenderTargetHandle m_hzbRt;
- Mat4 m_viewProjMat;
- UVec2 m_finalRenderTargetSize;
- };
- FrustumTestData* frustumTestData = nullptr;
- DistanceTestData* distTestData = nullptr;
- if(distanceBased)
- {
- distTestData = newInstance<DistanceTestData>(getRenderer().getFrameMemoryPool());
- const DistanceGpuVisibilityInput& din = static_cast<DistanceGpuVisibilityInput&>(in);
- distTestData->m_pointOfTest = din.m_pointOfTest;
- distTestData->m_testRadius = din.m_testRadius;
- }
- else
- {
- frustumTestData = newInstance<FrustumTestData>(getRenderer().getFrameMemoryPool());
- const FrustumGpuVisibilityInput& fin = static_cast<FrustumGpuVisibilityInput&>(in);
- frustumTestData->m_viewProjMat = fin.m_viewProjectionMatrix;
- frustumTestData->m_finalRenderTargetSize = fin.m_viewportSize;
- }
- // Allocate memory
- const Bool firstCallInFrame = m_runCtx.m_frameIdx != getRenderer().getFrameCount();
- if(firstCallInFrame)
- {
- // First call in frame. Init stuff
- m_runCtx.m_frameIdx = getRenderer().getFrameCount();
- m_runCtx.m_populateRenderGraphCallCount = 0;
- m_runCtx.m_populateRenderGraphMeshletRenderingCallCount = 0;
- // Calc memory requirements
- MemoryRequirements maxTotalMemReq;
- WeakArray<MemoryRequirements> bucketsMemReqs;
- for(RenderingTechnique t : EnumBitsIterable<RenderingTechnique, RenderingTechniqueBit>(RenderingTechniqueBit::kAllRaster))
- {
- const U32 tBucketCount = RenderStateBucketContainer::getSingleton().getBucketCount(t);
- if(tBucketCount == 0)
- {
- continue;
- }
- newArray<MemoryRequirements>(getRenderer().getFrameMemoryPool(), tBucketCount, bucketsMemReqs);
- computeGpuVisibilityMemoryRequirements(t, m_runCtx.m_totalMemRequirements[t], bucketsMemReqs);
- maxTotalMemReq = maxTotalMemReq.max(m_runCtx.m_totalMemRequirements[t]);
- newArray<InstanceRange>(getRenderer().getFrameMemoryPool(), tBucketCount, m_runCtx.m_renderableInstanceRanges[t]);
- newArray<InstanceRange>(getRenderer().getFrameMemoryPool(), tBucketCount, m_runCtx.m_meshletGroupInstanceRanges[t]);
- newArray<InstanceRange>(getRenderer().getFrameMemoryPool(), tBucketCount, m_runCtx.m_meshletInstanceRanges[t]);
- U32 renderablesFirstInstance = 0, groupsFirstInstance = 0, meshletsFirstInstance = 0;
- for(U32 i = 0; i < tBucketCount; ++i)
- {
- m_runCtx.m_renderableInstanceRanges[t][i].m_firstInstance = renderablesFirstInstance;
- m_runCtx.m_renderableInstanceRanges[t][i].m_instanceCount = bucketsMemReqs[i].m_renderableInstanceCount;
- m_runCtx.m_meshletGroupInstanceRanges[t][i].m_firstInstance = groupsFirstInstance;
- m_runCtx.m_meshletGroupInstanceRanges[t][i].m_instanceCount = bucketsMemReqs[i].m_meshletGroupInstanceCount;
- m_runCtx.m_meshletInstanceRanges[t][i].m_firstInstance = meshletsFirstInstance;
- m_runCtx.m_meshletInstanceRanges[t][i].m_instanceCount = bucketsMemReqs[i].m_meshletInstanceCount;
- renderablesFirstInstance += bucketsMemReqs[i].m_renderableInstanceCount;
- groupsFirstInstance += bucketsMemReqs[i].m_meshletGroupInstanceCount;
- meshletsFirstInstance += bucketsMemReqs[i].m_meshletInstanceCount;
- }
- }
- // Allocate persistent memory
- for(PersistentMemory& mem : m_runCtx.m_persistentMem)
- {
- mem = {};
- mem.m_drawIndexedIndirectArgsBuffer = allocateTransientGpuMem(maxTotalMemReq.m_renderableInstanceCount * sizeof(DrawIndexedIndirectArgs));
- mem.m_renderableInstancesBuffer = allocateTransientGpuMem(maxTotalMemReq.m_renderableInstanceCount * sizeof(GpuSceneRenderableInstance));
- mem.m_meshletGroupsInstancesBuffer =
- allocateTransientGpuMem(maxTotalMemReq.m_meshletGroupInstanceCount * sizeof(GpuSceneMeshletGroupInstance));
- mem.m_bufferDepedency = rgraph.importBuffer((mem.m_drawIndexedIndirectArgsBuffer.isValid()) ? mem.m_drawIndexedIndirectArgsBuffer
- : mem.m_meshletGroupsInstancesBuffer,
- BufferUsageBit::kNone);
- }
- if(getRenderer().runSoftwareMeshletRendering())
- {
- // Because someone will need it later
- for(PersistentMemoryMeshletRendering& mem : m_runCtx.m_persistentMeshletRenderingMem)
- {
- mem = {};
- mem.m_meshletInstancesBuffer = allocateTransientGpuMem(maxTotalMemReq.m_meshletInstanceCount * sizeof(GpuSceneMeshletInstance));
- mem.m_bufferDepedency = rgraph.importBuffer(mem.m_meshletInstancesBuffer, BufferUsageBit::kNone);
- }
- }
- }
- const U32 bucketCount = RenderStateBucketContainer::getSingleton().getBucketCount(in.m_technique);
- const MemoryRequirements& req = m_runCtx.m_totalMemRequirements[in.m_technique];
- const PersistentMemory& mem = m_runCtx.m_persistentMem[m_runCtx.m_populateRenderGraphCallCount++ % m_runCtx.m_persistentMem.getSize()];
- out.m_legacy.m_drawIndexedIndirectArgsBuffer =
- (req.m_renderableInstanceCount)
- ? BufferView(mem.m_drawIndexedIndirectArgsBuffer).setRange(req.m_renderableInstanceCount * sizeof(DrawIndexedIndirectArgs))
- : BufferView();
- out.m_legacy.m_renderableInstancesBuffer =
- (req.m_renderableInstanceCount)
- ? BufferView(mem.m_renderableInstancesBuffer).setRange(req.m_renderableInstanceCount * sizeof(GpuSceneRenderableInstance))
- : BufferView();
- out.m_legacy.m_mdiDrawCountsBuffer = allocateTransientGpuMem(sizeof(U32) * bucketCount);
- out.m_mesh.m_meshletGroupInstancesBuffer =
- (req.m_meshletGroupInstanceCount)
- ? BufferView(mem.m_meshletGroupsInstancesBuffer).setRange(req.m_meshletGroupInstanceCount * sizeof(GpuSceneMeshletGroupInstance))
- : BufferView();
- out.m_mesh.m_taskShaderIndirectArgsBuffer = allocateTransientGpuMem(bucketCount * sizeof(DispatchIndirectArgs));
- if(in.m_hashVisibles)
- {
- out.m_visiblesHashBuffer = allocateTransientGpuMem(sizeof(GpuVisibilityHash));
- }
- if(in.m_gatherAabbIndices)
- {
- out.m_visibleAaabbIndicesBuffer =
- allocateTransientGpuMem((RenderStateBucketContainer::getSingleton().getBucketsActiveUserCount(in.m_technique) + 1) * sizeof(U32));
- }
- // Set instance sub-ranges
- out.m_legacy.m_bucketRenderableInstanceRanges = m_runCtx.m_renderableInstanceRanges[in.m_technique];
- out.m_mesh.m_bucketMeshletGroupInstanceRanges = m_runCtx.m_meshletGroupInstanceRanges[in.m_technique];
- // Zero some stuff
- const BufferHandle zeroStuffDependency = rgraph.importBuffer(out.m_legacy.m_mdiDrawCountsBuffer, BufferUsageBit::kNone);
- {
- Array<Char, 128> passName;
- snprintf(passName.getBegin(), passName.getSizeInBytes(), "GPU vis zero: %s", in.m_passesName.cstr());
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(passName.getBegin());
- pass.newBufferDependency(zeroStuffDependency, BufferUsageBit::kTransferDestination);
- pass.setWork([out](RenderPassWorkContext& rpass) {
- CommandBuffer& cmdb = *rpass.m_commandBuffer;
- cmdb.pushDebugMarker("MDI counts", Vec3(1.0f, 1.0f, 1.0f));
- cmdb.fillBuffer(out.m_legacy.m_mdiDrawCountsBuffer, 0);
- cmdb.popDebugMarker();
- if(out.m_mesh.m_taskShaderIndirectArgsBuffer.isValid())
- {
- cmdb.pushDebugMarker("Task shader indirect args", Vec3(1.0f, 1.0f, 1.0f));
- cmdb.fillBuffer(out.m_mesh.m_taskShaderIndirectArgsBuffer, 0);
- cmdb.popDebugMarker();
- }
- if(out.m_visiblesHashBuffer.isValid())
- {
- cmdb.pushDebugMarker("Visibles hash", Vec3(1.0f, 1.0f, 1.0f));
- cmdb.fillBuffer(out.m_visiblesHashBuffer, 0);
- cmdb.popDebugMarker();
- }
- if(out.m_visibleAaabbIndicesBuffer.isValid())
- {
- cmdb.pushDebugMarker("Visible AABB indices", Vec3(1.0f, 1.0f, 1.0f));
- cmdb.fillBuffer(BufferView(out.m_visibleAaabbIndicesBuffer).setRange(sizeof(U32)), 0);
- cmdb.popDebugMarker();
- }
- });
- }
- // Set the out dependency. Use one of the big buffers.
- out.m_dependency = mem.m_bufferDepedency;
- // Create the renderpass
- Array<Char, 128> passName;
- snprintf(passName.getBegin(), passName.getSizeInBytes(), "GPU vis: %s", in.m_passesName.cstr());
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(passName.getBegin());
- pass.newBufferDependency(getRenderer().getGpuSceneBufferHandle(), BufferUsageBit::kStorageComputeRead);
- pass.newBufferDependency(zeroStuffDependency, BufferUsageBit::kStorageComputeWrite);
- pass.newBufferDependency(out.m_dependency, BufferUsageBit::kStorageComputeWrite);
- if(!distanceBased && static_cast<FrustumGpuVisibilityInput&>(in).m_hzbRt)
- {
- frustumTestData->m_hzbRt = *static_cast<FrustumGpuVisibilityInput&>(in).m_hzbRt;
- pass.newTextureDependency(frustumTestData->m_hzbRt, TextureUsageBit::kSampledCompute);
- }
- pass.setWork([this, frustumTestData, distTestData, lodReferencePoint = in.m_lodReferencePoint, lodDistances = in.m_lodDistances,
- technique = in.m_technique, out](RenderPassWorkContext& rpass) {
- CommandBuffer& cmdb = *rpass.m_commandBuffer;
- const Bool gatherAabbIndices = out.m_visibleAaabbIndicesBuffer.isValid();
- const Bool genHash = out.m_visiblesHashBuffer.isValid();
- U32 gatherType = 0;
- if(out.m_mesh.m_meshletGroupInstancesBuffer.isValid())
- {
- gatherType |= 2u;
- }
- if(out.m_legacy.m_renderableInstancesBuffer.isValid())
- {
- gatherType |= 1u;
- }
- ANKI_ASSERT(gatherType != 0);
- if(frustumTestData)
- {
- cmdb.bindShaderProgram(m_frustumGrProgs[frustumTestData->m_hzbRt.isValid()][gatherAabbIndices][genHash][gatherType - 1u].get());
- }
- else
- {
- cmdb.bindShaderProgram(m_distGrProgs[gatherAabbIndices][genHash][gatherType - 1u].get());
- }
- BufferView aabbsBuffer;
- U32 aabbCount = 0;
- switch(technique)
- {
- case RenderingTechnique::kGBuffer:
- aabbsBuffer = GpuSceneArrays::RenderableBoundingVolumeGBuffer::getSingleton().getBufferView();
- aabbCount = GpuSceneArrays::RenderableBoundingVolumeGBuffer::getSingleton().getElementCount();
- break;
- case RenderingTechnique::kDepth:
- aabbsBuffer = GpuSceneArrays::RenderableBoundingVolumeDepth::getSingleton().getBufferView();
- aabbCount = GpuSceneArrays::RenderableBoundingVolumeDepth::getSingleton().getElementCount();
- break;
- case RenderingTechnique::kForward:
- aabbsBuffer = GpuSceneArrays::RenderableBoundingVolumeForward::getSingleton().getBufferView();
- aabbCount = GpuSceneArrays::RenderableBoundingVolumeForward::getSingleton().getElementCount();
- break;
- default:
- ANKI_ASSERT(0);
- }
- cmdb.bindStorageBuffer(0, 0, aabbsBuffer);
- cmdb.bindStorageBuffer(0, 1, GpuSceneArrays::Renderable::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 2, GpuSceneArrays::MeshLod::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 3, GpuSceneArrays::Transform::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 4, GpuSceneBuffer::getSingleton().getBufferView());
- if(gatherType & 1u)
- {
- cmdb.bindStorageBuffer(0, 5, out.m_legacy.m_renderableInstancesBuffer);
- cmdb.bindStorageBuffer(0, 6, out.m_legacy.m_drawIndexedIndirectArgsBuffer);
- cmdb.bindStorageBuffer(0, 7, out.m_legacy.m_mdiDrawCountsBuffer);
- }
- if(gatherType & 2u)
- {
- cmdb.bindStorageBuffer(0, 8, out.m_mesh.m_taskShaderIndirectArgsBuffer);
- cmdb.bindStorageBuffer(0, 9, out.m_mesh.m_meshletGroupInstancesBuffer);
- }
- const U32 bucketCount = RenderStateBucketContainer::getSingleton().getBucketCount(technique);
- UVec2* instanceRanges = allocateAndBindStorageBuffer<UVec2>(cmdb, 0, 10, bucketCount);
- for(U32 i = 0; i < bucketCount; ++i)
- {
- const Bool legacyBucket = m_runCtx.m_renderableInstanceRanges[technique][i].m_instanceCount > 0;
- if(legacyBucket)
- {
- instanceRanges[i].x() = m_runCtx.m_renderableInstanceRanges[technique][i].m_firstInstance;
- instanceRanges[i].y() = m_runCtx.m_renderableInstanceRanges[technique][i].m_instanceCount;
- }
- else
- {
- instanceRanges[i].x() = m_runCtx.m_meshletGroupInstanceRanges[technique][i].m_firstInstance;
- instanceRanges[i].y() = m_runCtx.m_meshletGroupInstanceRanges[technique][i].m_instanceCount;
- }
- }
- if(frustumTestData)
- {
- FrustumGpuVisibilityUniforms* unis = allocateAndBindConstants<FrustumGpuVisibilityUniforms>(cmdb, 0, 11);
- Array<Plane, 6> planes;
- extractClipPlanes(frustumTestData->m_viewProjMat, planes);
- for(U32 i = 0; i < 6; ++i)
- {
- unis->m_clipPlanes[i] = Vec4(planes[i].getNormal().xyz(), planes[i].getOffset());
- }
- ANKI_ASSERT(kMaxLodCount == 3);
- unis->m_maxLodDistances[0] = lodDistances[0];
- unis->m_maxLodDistances[1] = lodDistances[1];
- unis->m_maxLodDistances[2] = kMaxF32;
- unis->m_maxLodDistances[3] = kMaxF32;
- unis->m_lodReferencePoint = lodReferencePoint;
- unis->m_viewProjectionMat = frustumTestData->m_viewProjMat;
- unis->m_finalRenderTargetSize = Vec2(frustumTestData->m_finalRenderTargetSize);
- if(frustumTestData->m_hzbRt.isValid())
- {
- rpass.bindColorTexture(0, 12, frustumTestData->m_hzbRt);
- cmdb.bindSampler(0, 13, getRenderer().getSamplers().m_nearestNearestClamp.get());
- }
- }
- else
- {
- DistanceGpuVisibilityUniforms unis;
- unis.m_pointOfTest = distTestData->m_pointOfTest;
- unis.m_testRadius = distTestData->m_testRadius;
- unis.m_maxLodDistances[0] = lodDistances[0];
- unis.m_maxLodDistances[1] = lodDistances[1];
- unis.m_maxLodDistances[2] = kMaxF32;
- unis.m_maxLodDistances[3] = kMaxF32;
- unis.m_lodReferencePoint = lodReferencePoint;
- cmdb.setPushConstants(&unis, sizeof(unis));
- }
- if(gatherAabbIndices)
- {
- cmdb.bindStorageBuffer(0, 14, out.m_visibleAaabbIndicesBuffer);
- }
- if(genHash)
- {
- cmdb.bindStorageBuffer(0, 15, out.m_visiblesHashBuffer);
- }
- dispatchPPCompute(cmdb, 64, 1, aabbCount, 1);
- });
- }
- void GpuVisibility::populateRenderGraphMeshletInternal(Bool passthrough, BaseGpuMeshletVisibilityInput& in, GpuMeshletVisibilityOutput& out)
- {
- RenderGraphDescription& rgraph = *in.m_rgraph;
- if(!in.m_taskShaderIndirectArgsBuffer.isValid()) [[unlikely]]
- {
- // Early exit
- return;
- }
- class NonPassthrough
- {
- public:
- Mat4 m_viewProjectionMatrix;
- Mat3x4 m_cameraTransform;
- UVec2 m_viewportSize;
- RenderTargetHandle m_hzbRt;
- }* nonPassthroughData = nullptr;
- if(!passthrough)
- {
- GpuMeshletVisibilityInput& nonPassthroughIn = static_cast<GpuMeshletVisibilityInput&>(in);
- nonPassthroughData = newInstance<NonPassthrough>(getRenderer().getFrameMemoryPool());
- nonPassthroughData->m_viewProjectionMatrix = nonPassthroughIn.m_viewProjectionMatrix;
- nonPassthroughData->m_cameraTransform = nonPassthroughIn.m_cameraTransform;
- nonPassthroughData->m_viewportSize = nonPassthroughIn.m_viewportSize;
- nonPassthroughData->m_hzbRt = nonPassthroughIn.m_hzbRt;
- }
- // Allocate memory
- const U32 bucketCount = m_runCtx.m_renderableInstanceRanges[in.m_technique].getSize();
- ANKI_ASSERT(RenderStateBucketContainer::getSingleton().getBucketCount(in.m_technique) == bucketCount);
- const PersistentMemoryMeshletRendering& mem = m_runCtx.m_persistentMeshletRenderingMem[m_runCtx.m_populateRenderGraphMeshletRenderingCallCount++
- % m_runCtx.m_persistentMeshletRenderingMem.getSize()];
- out.m_drawIndirectArgsBuffer = allocateTransientGpuMem(sizeof(DrawIndirectArgs) * bucketCount);
- out.m_meshletInstancesBuffer =
- BufferView(mem.m_meshletInstancesBuffer)
- .setRange(m_runCtx.m_totalMemRequirements[in.m_technique].m_meshletInstanceCount * sizeof(GpuSceneMeshletInstance));
- out.m_bucketMeshletInstanceRanges = m_runCtx.m_meshletInstanceRanges[in.m_technique];
- // Zero some stuff
- const BufferHandle indirectArgsDep = rgraph.importBuffer(out.m_drawIndirectArgsBuffer, BufferUsageBit::kNone);
- {
- Array<Char, 128> passName;
- snprintf(passName.getBegin(), passName.getSizeInBytes(), "GPU meshlet vis zero: %s", in.m_passesName.cstr());
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(passName.getBegin());
- pass.newBufferDependency(indirectArgsDep, BufferUsageBit::kTransferDestination);
- pass.setWork([drawIndirectArgsBuffer = out.m_drawIndirectArgsBuffer](RenderPassWorkContext& rpass) {
- CommandBuffer& cmdb = *rpass.m_commandBuffer;
- cmdb.pushDebugMarker("Draw indirect args", Vec3(1.0f, 1.0f, 1.0f));
- cmdb.fillBuffer(drawIndirectArgsBuffer, 0);
- cmdb.popDebugMarker();
- });
- }
- out.m_dependency = mem.m_bufferDepedency;
- // Create the renderpass
- Array<Char, 128> passName;
- snprintf(passName.getBegin(), passName.getSizeInBytes(), "GPU meshlet vis: %s", in.m_passesName.cstr());
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(passName.getBegin());
- pass.newBufferDependency(indirectArgsDep, BufferUsageBit::kStorageComputeWrite);
- pass.newBufferDependency(mem.m_bufferDepedency, BufferUsageBit::kStorageComputeWrite);
- pass.newBufferDependency(in.m_dependency, BufferUsageBit::kIndirectCompute);
- pass.setWork([this, nonPassthroughData, computeIndirectArgs = in.m_taskShaderIndirectArgsBuffer, out,
- meshletGroupInstancesBuffer = in.m_meshletGroupInstancesBuffer,
- bucketMeshletGroupInstanceRanges = in.m_bucketMeshletGroupInstanceRanges](RenderPassWorkContext& rpass) {
- CommandBuffer& cmdb = *rpass.m_commandBuffer;
- const U32 bucketCount = out.m_bucketMeshletInstanceRanges.getSize();
- for(U32 i = 0; i < bucketCount; ++i)
- {
- if(out.m_bucketMeshletInstanceRanges[i].m_instanceCount == 0)
- {
- continue;
- }
- const Bool hasHzb = (nonPassthroughData) ? nonPassthroughData->m_hzbRt.isValid() : false;
- const Bool isPassthrough = (nonPassthroughData == nullptr);
- cmdb.bindShaderProgram(m_meshletCullingGrProgs[hasHzb][isPassthrough].get());
- cmdb.bindStorageBuffer(0, 0, meshletGroupInstancesBuffer);
- cmdb.bindStorageBuffer(0, 1, GpuSceneArrays::Renderable::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 2, GpuSceneArrays::MeshLod::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 3, GpuSceneArrays::Transform::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 4, UnifiedGeometryBuffer::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 5, out.m_drawIndirectArgsBuffer);
- cmdb.bindStorageBuffer(0, 6, out.m_meshletInstancesBuffer);
- if(hasHzb)
- {
- rpass.bindColorTexture(0, 7, nonPassthroughData->m_hzbRt);
- cmdb.bindSampler(0, 8, getRenderer().getSamplers().m_nearestNearestClamp.get());
- }
- class Consts
- {
- public:
- Mat4 m_viewProjectionMatrix;
- Vec3 m_cameraPos;
- U32 m_firstDrawArg;
- Vec2 m_viewportSizef;
- U32 m_firstMeshletGroup;
- U32 m_firstMeshlet;
- U32 m_meshletCount;
- U32 m_padding1;
- U32 m_padding2;
- U32 m_padding3;
- } consts;
- consts.m_viewProjectionMatrix = (!isPassthrough) ? nonPassthroughData->m_viewProjectionMatrix : Mat4::getIdentity();
- consts.m_cameraPos = (!isPassthrough) ? nonPassthroughData->m_cameraTransform.getTranslationPart().xyz() : Vec3(0.0f);
- consts.m_firstDrawArg = i;
- consts.m_viewportSizef = (!isPassthrough) ? Vec2(nonPassthroughData->m_viewportSize) : Vec2(0.0f);
- consts.m_firstMeshletGroup = bucketMeshletGroupInstanceRanges[i].getFirstInstance();
- consts.m_firstMeshlet = out.m_bucketMeshletInstanceRanges[i].getFirstInstance();
- consts.m_meshletCount = out.m_bucketMeshletInstanceRanges[i].getInstanceCount();
- cmdb.setPushConstants(&consts, sizeof(consts));
- cmdb.dispatchComputeIndirect(
- BufferView(computeIndirectArgs).incrementOffset(i * sizeof(DispatchIndirectArgs)).setRange(sizeof(DispatchIndirectArgs)));
- };
- });
- }
- Error GpuVisibilityNonRenderables::init()
- {
- ANKI_CHECK(ResourceManager::getSingleton().loadResource("ShaderBinaries/GpuVisibilityNonRenderables.ankiprogbin", m_prog));
- for(MutatorValue hzb = 0; hzb < 2; ++hzb)
- {
- for(GpuSceneNonRenderableObjectType type : EnumIterable<GpuSceneNonRenderableObjectType>())
- {
- for(MutatorValue cpuFeedback = 0; cpuFeedback < 2; ++cpuFeedback)
- {
- ANKI_CHECK(loadShaderProgram("ShaderBinaries/GpuVisibilityNonRenderables.ankiprogbin",
- {{"HZB_TEST", hzb}, {"OBJECT_TYPE", MutatorValue(type)}, {"CPU_FEEDBACK", cpuFeedback}}, m_prog,
- m_grProgs[hzb][type][cpuFeedback]));
- }
- }
- }
- return Error::kNone;
- }
- void GpuVisibilityNonRenderables::populateRenderGraph(GpuVisibilityNonRenderablesInput& in, GpuVisibilityNonRenderablesOutput& out)
- {
- ANKI_ASSERT(in.m_viewProjectionMat != Mat4::getZero());
- RenderGraphDescription& rgraph = *in.m_rgraph;
- U32 objCount = 0;
- switch(in.m_objectType)
- {
- case GpuSceneNonRenderableObjectType::kLight:
- objCount = GpuSceneArrays::Light::getSingleton().getElementCount();
- break;
- case GpuSceneNonRenderableObjectType::kDecal:
- objCount = GpuSceneArrays::Decal::getSingleton().getElementCount();
- break;
- case GpuSceneNonRenderableObjectType::kFogDensityVolume:
- objCount = GpuSceneArrays::FogDensityVolume::getSingleton().getElementCount();
- break;
- case GpuSceneNonRenderableObjectType::kGlobalIlluminationProbe:
- objCount = GpuSceneArrays::GlobalIlluminationProbe::getSingleton().getElementCount();
- break;
- case GpuSceneNonRenderableObjectType::kReflectionProbe:
- objCount = GpuSceneArrays::ReflectionProbe::getSingleton().getElementCount();
- break;
- default:
- ANKI_ASSERT(0);
- }
- if(objCount == 0)
- {
- U32* count;
- out.m_visiblesBuffer = RebarTransientMemoryPool::getSingleton().allocateFrame(sizeof(U32), count);
- *count = 0;
- out.m_visiblesBufferHandle = rgraph.importBuffer(out.m_visiblesBuffer, BufferUsageBit::kNone);
- return;
- }
- if(in.m_cpuFeedbackBuffer.isValid())
- {
- ANKI_ASSERT(in.m_cpuFeedbackBuffer.getRange() == sizeof(U32) * (objCount * 2 + 1));
- }
- const Bool firstRunInFrame = m_lastFrameIdx != getRenderer().getFrameCount();
- if(firstRunInFrame)
- {
- // 1st run in this frame, do some bookkeeping
- m_lastFrameIdx = getRenderer().getFrameCount();
- m_counterBufferOffset = 0;
- m_counterBufferZeroingHandle = {};
- }
- constexpr U32 kCountersPerDispatch = 3; // 1 for the threadgroup, 1 for the visbile object count and 1 for objects with feedback
- const U32 counterBufferElementSize = getAlignedRoundUp(GrManager::getSingleton().getDeviceCapabilities().m_storageBufferBindOffsetAlignment,
- U32(kCountersPerDispatch * sizeof(U32)));
- if(!m_counterBuffer.isCreated() || m_counterBufferOffset + counterBufferElementSize > m_counterBuffer->getSize()) [[unlikely]]
- {
- // Counter buffer not created or not big enough, create a new one
- BufferInitInfo buffInit("GpuVisibilityNonRenderablesCounters");
- buffInit.m_size = (m_counterBuffer.isCreated()) ? m_counterBuffer->getSize() * 2
- : kCountersPerDispatch * counterBufferElementSize * kInitialCounterArraySize;
- buffInit.m_usage = BufferUsageBit::kStorageComputeWrite | BufferUsageBit::kStorageComputeRead | BufferUsageBit::kTransferDestination;
- m_counterBuffer = GrManager::getSingleton().newBuffer(buffInit);
- m_counterBufferZeroingHandle = rgraph.importBuffer(BufferView(m_counterBuffer.get()), buffInit.m_usage);
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("GpuVisibilityNonRenderablesClearCounterBuffer");
- pass.newBufferDependency(m_counterBufferZeroingHandle, BufferUsageBit::kTransferDestination);
- pass.setWork([counterBuffer = m_counterBuffer](RenderPassWorkContext& rgraph) {
- rgraph.m_commandBuffer->fillBuffer(BufferView(counterBuffer.get()), 0);
- });
- m_counterBufferOffset = 0;
- }
- else if(!firstRunInFrame)
- {
- m_counterBufferOffset += counterBufferElementSize;
- }
- // Allocate memory for the result
- out.m_visiblesBuffer = allocateTransientGpuMem((objCount + 1) * sizeof(U32));
- out.m_visiblesBufferHandle = rgraph.importBuffer(out.m_visiblesBuffer, BufferUsageBit::kNone);
- // Create the renderpass
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(in.m_passesName);
- pass.newBufferDependency(getRenderer().getGpuSceneBufferHandle(), BufferUsageBit::kStorageComputeRead);
- pass.newBufferDependency(out.m_visiblesBufferHandle, BufferUsageBit::kStorageComputeWrite);
- if(in.m_hzbRt)
- {
- pass.newTextureDependency(*in.m_hzbRt, TextureUsageBit::kSampledCompute);
- }
- if(m_counterBufferZeroingHandle.isValid()) [[unlikely]]
- {
- pass.newBufferDependency(m_counterBufferZeroingHandle, BufferUsageBit::kStorageComputeRead | BufferUsageBit::kStorageComputeWrite);
- }
- pass.setWork([this, objType = in.m_objectType, feedbackBuffer = in.m_cpuFeedbackBuffer, viewProjectionMat = in.m_viewProjectionMat,
- visibleIndicesBuffHandle = out.m_visiblesBufferHandle, counterBuffer = m_counterBuffer, counterBufferOffset = m_counterBufferOffset,
- objCount](RenderPassWorkContext& rgraph) {
- CommandBuffer& cmdb = *rgraph.m_commandBuffer;
- const Bool needsFeedback = feedbackBuffer.isValid();
- cmdb.bindShaderProgram(m_grProgs[0][objType][needsFeedback].get());
- BufferView objBuffer;
- switch(objType)
- {
- case GpuSceneNonRenderableObjectType::kLight:
- objBuffer = GpuSceneArrays::Light::getSingleton().getBufferView();
- break;
- case GpuSceneNonRenderableObjectType::kDecal:
- objBuffer = GpuSceneArrays::Decal::getSingleton().getBufferView();
- break;
- case GpuSceneNonRenderableObjectType::kFogDensityVolume:
- objBuffer = GpuSceneArrays::FogDensityVolume::getSingleton().getBufferView();
- break;
- case GpuSceneNonRenderableObjectType::kGlobalIlluminationProbe:
- objBuffer = GpuSceneArrays::GlobalIlluminationProbe::getSingleton().getBufferView();
- break;
- case GpuSceneNonRenderableObjectType::kReflectionProbe:
- objBuffer = GpuSceneArrays::ReflectionProbe::getSingleton().getBufferView();
- break;
- default:
- ANKI_ASSERT(0);
- }
- cmdb.bindStorageBuffer(0, 0, objBuffer);
- GpuVisibilityNonRenderableUniforms unis;
- Array<Plane, 6> planes;
- extractClipPlanes(viewProjectionMat, planes);
- for(U32 i = 0; i < 6; ++i)
- {
- unis.m_clipPlanes[i] = Vec4(planes[i].getNormal().xyz(), planes[i].getOffset());
- }
- cmdb.setPushConstants(&unis, sizeof(unis));
- rgraph.bindStorageBuffer(0, 1, visibleIndicesBuffHandle);
- cmdb.bindStorageBuffer(0, 2, BufferView(counterBuffer.get(), counterBufferOffset, sizeof(U32) * kCountersPerDispatch));
- if(needsFeedback)
- {
- cmdb.bindStorageBuffer(0, 3, feedbackBuffer);
- }
- dispatchPPCompute(cmdb, 64, 1, objCount, 1);
- });
- }
- Error GpuVisibilityAccelerationStructures::init()
- {
- ANKI_CHECK(loadShaderProgram("ShaderBinaries/GpuVisibilityAccelerationStructures.ankiprogbin", m_visibilityProg, m_visibilityGrProg));
- ANKI_CHECK(loadShaderProgram("ShaderBinaries/GpuVisibilityAccelerationStructuresZeroRemainingInstances.ankiprogbin", m_zeroRemainingInstancesProg,
- m_zeroRemainingInstancesGrProg));
- BufferInitInfo inf("GpuVisibilityAccelerationStructuresCounters");
- inf.m_size = sizeof(U32) * 2;
- inf.m_usage = BufferUsageBit::kStorageComputeWrite | BufferUsageBit::kStorageComputeRead | BufferUsageBit::kTransferDestination;
- m_counterBuffer = GrManager::getSingleton().newBuffer(inf);
- zeroBuffer(m_counterBuffer.get());
- return Error::kNone;
- }
- void GpuVisibilityAccelerationStructures::pupulateRenderGraph(GpuVisibilityAccelerationStructuresInput& in,
- GpuVisibilityAccelerationStructuresOutput& out)
- {
- in.validate();
- RenderGraphDescription& rgraph = *in.m_rgraph;
- #if ANKI_ASSERTIONS_ENABLED
- ANKI_ASSERT(m_lastFrameIdx != getRenderer().getFrameCount());
- m_lastFrameIdx = getRenderer().getFrameCount();
- #endif
- // Allocate the transient buffers
- const U32 aabbCount = GpuSceneArrays::RenderableBoundingVolumeRt::getSingleton().getElementCount();
- out.m_instancesBuffer = allocateTransientGpuMem(aabbCount * sizeof(AccelerationStructureInstance));
- out.m_someBufferHandle = rgraph.importBuffer(out.m_instancesBuffer, BufferUsageBit::kStorageComputeWrite);
- out.m_renderableIndicesBuffer = allocateTransientGpuMem((aabbCount + 1) * sizeof(U32));
- const BufferView zeroInstancesDispatchArgsBuff = allocateTransientGpuMem(sizeof(DispatchIndirectArgs));
- // Create vis pass
- {
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(in.m_passesName);
- pass.newBufferDependency(getRenderer().getGpuSceneBufferHandle(), BufferUsageBit::kStorageComputeRead);
- pass.newBufferDependency(out.m_someBufferHandle, BufferUsageBit::kStorageComputeWrite);
- pass.setWork([this, viewProjMat = in.m_viewProjectionMatrix, lodDistances = in.m_lodDistances, pointOfTest = in.m_pointOfTest,
- testRadius = in.m_testRadius, instancesBuff = out.m_instancesBuffer, indicesBuff = out.m_renderableIndicesBuffer,
- zeroInstancesDispatchArgsBuff](RenderPassWorkContext& rgraph) {
- CommandBuffer& cmdb = *rgraph.m_commandBuffer;
- cmdb.bindShaderProgram(m_visibilityGrProg.get());
- GpuVisibilityAccelerationStructuresUniforms unis;
- Array<Plane, 6> planes;
- extractClipPlanes(viewProjMat, planes);
- for(U32 i = 0; i < 6; ++i)
- {
- unis.m_clipPlanes[i] = Vec4(planes[i].getNormal().xyz(), planes[i].getOffset());
- }
- unis.m_pointOfTest = pointOfTest;
- unis.m_testRadius = testRadius;
- ANKI_ASSERT(kMaxLodCount == 3);
- unis.m_maxLodDistances[0] = lodDistances[0];
- unis.m_maxLodDistances[1] = lodDistances[1];
- unis.m_maxLodDistances[2] = kMaxF32;
- unis.m_maxLodDistances[3] = kMaxF32;
- cmdb.setPushConstants(&unis, sizeof(unis));
- cmdb.bindStorageBuffer(0, 0, GpuSceneArrays::RenderableBoundingVolumeRt::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 1, GpuSceneArrays::Renderable::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 2, GpuSceneArrays::MeshLod::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 3, GpuSceneArrays::Transform::getSingleton().getBufferView());
- cmdb.bindStorageBuffer(0, 4, instancesBuff);
- cmdb.bindStorageBuffer(0, 5, indicesBuff);
- cmdb.bindStorageBuffer(0, 6, BufferView(m_counterBuffer.get(), 0, sizeof(U32) * 2));
- cmdb.bindStorageBuffer(0, 7, zeroInstancesDispatchArgsBuff);
- const U32 aabbCount = GpuSceneArrays::RenderableBoundingVolumeRt::getSingleton().getElementCount();
- dispatchPPCompute(cmdb, 64, 1, aabbCount, 1);
- });
- }
- // Zero remaining instances
- {
- Array<Char, 64> passName;
- snprintf(passName.getBegin(), sizeof(passName), "%s: Zero remaining instances", in.m_passesName.cstr());
- ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(passName.getBegin());
- pass.newBufferDependency(out.m_someBufferHandle, BufferUsageBit::kStorageComputeWrite);
- pass.setWork([this, zeroInstancesDispatchArgsBuff, instancesBuff = out.m_instancesBuffer,
- indicesBuff = out.m_renderableIndicesBuffer](RenderPassWorkContext& rgraph) {
- CommandBuffer& cmdb = *rgraph.m_commandBuffer;
- cmdb.bindShaderProgram(m_zeroRemainingInstancesGrProg.get());
- cmdb.bindStorageBuffer(0, 0, indicesBuff);
- cmdb.bindStorageBuffer(0, 1, instancesBuff);
- cmdb.dispatchComputeIndirect(zeroInstancesDispatchArgsBuff);
- });
- }
- }
- } // end namespace anki
|