diff --git a/code/Mesh/Instance/InstanceMesh.cpp b/code/Mesh/Instance/InstanceMesh.cpp index 315c74ff01..a465894c11 100644 --- a/code/Mesh/Instance/InstanceMesh.cpp +++ b/code/Mesh/Instance/InstanceMesh.cpp @@ -64,7 +64,7 @@ void InstanceMesh::getTechniques(SmallSet< render::handle_t >& outHandles) const outHandles.insert(part.first); } -void InstanceMesh::cullableBuild( +void InstanceMesh::build( const world::WorldBuildContext& context, const world::WorldRenderView& worldRenderView, const world::IWorldRenderPass& worldRenderPass, @@ -84,6 +84,7 @@ void InstanceMesh::cullableBuild( const auto& meshParts = m_renderMesh->getParts(); // Lazy create the buffers. + const uint32_t bufferItemCount = (uint32_t)alignUp(count, 16); if (count > m_allocatedCount) { m_drawBuffers.resize(0); @@ -95,11 +96,13 @@ void InstanceMesh::cullableBuild( for (uint32_t i = dbSize; i < (peakCascade + 1) * parts.size(); ++i) m_drawBuffers.push_back(m_renderSystem->createBuffer( render::BufferUsage::BuStructured | render::BufferUsage::BuIndirect, - count * sizeof(render::IndexedIndirectDraw), + bufferItemCount * sizeof(render::IndexedIndirectDraw), false )); // Create draw buffers from visibility buffer. + // Compute blocks are executed before render pass, so draws for shadow map rendering all cascades + // are dispatched at the same time. for (uint32_t i = 0; i < parts.size(); ++i) { const auto& part = parts[i]; diff --git a/code/Mesh/Instance/InstanceMesh.h b/code/Mesh/Instance/InstanceMesh.h index 96ceed5829..375854c04a 100644 --- a/code/Mesh/Instance/InstanceMesh.h +++ b/code/Mesh/Instance/InstanceMesh.h @@ -17,7 +17,6 @@ #include "Mesh/IMesh.h" #include "Render/Shader.h" #include "Resource/Proxy.h" -#include "World/Entity/CullingComponent.h" // import/export mechanism. #undef T_DLLCLASS @@ -30,22 +29,22 @@ namespace traktor::render { -//class Buffer; +class Buffer; class IRenderSystem; class ProgramParameters; -//class RenderContext; class Mesh; class Shader; } -//namespace traktor::world -//{ -// -//class IWorldRenderPass; -//class WorldRenderView; -// -//} +namespace traktor::world +{ + +class IWorldRenderPass; +class WorldBuildContext; +class WorldRenderView; + +} namespace traktor::mesh { @@ -57,9 +56,7 @@ namespace traktor::mesh * automatically by the GPU in any number of instances * using hardware instancing in a single draw call. */ -class T_DLLCLASS InstanceMesh -: public IMesh -, public world::CullingComponent::ICullable +class T_DLLCLASS InstanceMesh : public IMesh { T_RTTI_CLASS; @@ -81,11 +78,7 @@ class T_DLLCLASS InstanceMesh void getTechniques(SmallSet< render::handle_t >& outHandles) const; - /* world::CullingComponent::ICullable */ - - virtual const Aabb3& cullableGetBoundingBox() const override final { return getBoundingBox(); } - - virtual void cullableBuild( + void build( const world::WorldBuildContext& context, const world::WorldRenderView& worldRenderView, const world::IWorldRenderPass& worldRenderPass, @@ -93,7 +86,7 @@ class T_DLLCLASS InstanceMesh render::Buffer* visibilityBuffer, uint32_t start, uint32_t count - ) override final; + ); private: friend class InstanceMeshResource; diff --git a/code/Mesh/Instance/InstanceMeshComponent.cpp b/code/Mesh/Instance/InstanceMeshComponent.cpp index 26b5a62b83..172ee920e2 100644 --- a/code/Mesh/Instance/InstanceMeshComponent.cpp +++ b/code/Mesh/Instance/InstanceMeshComponent.cpp @@ -22,7 +22,6 @@ T_IMPLEMENT_RTTI_CLASS(L"traktor.mesh.InstanceMeshComponent", InstanceMeshCompon InstanceMeshComponent::InstanceMeshComponent(const resource::Proxy< InstanceMesh >& mesh) : m_mesh(mesh) { - //m_cullingInstance = m_mesh->allocateInstance(); m_mesh.consume(); } @@ -35,44 +34,36 @@ InstanceMeshComponent::~InstanceMeshComponent() void InstanceMeshComponent::destroy() { - //// Only release instance if resource hasn't been replaced. - //if (!m_mesh.changed()) - //{ - // if (m_mesh && m_meshInstance != nullptr) - // m_mesh->releaseInstance(m_meshInstance); - //} - - if (m_owner != nullptr && m_cullingInstance != nullptr) - { - world::CullingComponent* culling = m_owner->getWorld()->getComponent< world::CullingComponent >(); - culling->releaseInstance(m_cullingInstance); - } - + T_FATAL_ASSERT(m_cullingInstance == nullptr); m_mesh.clear(); MeshComponent::destroy(); } void InstanceMeshComponent::setWorld(world::World* world) { - T_FATAL_ASSERT(m_cullingInstance == nullptr); + // Remove from last world. + if (m_world != nullptr) + { + T_FATAL_ASSERT(m_cullingInstance != nullptr); + world::CullingComponent* culling = m_world->getComponent< world::CullingComponent >(); + culling->releaseInstance(m_cullingInstance); + } + + // Add to new world. if (world != nullptr) { + T_FATAL_ASSERT(m_cullingInstance == nullptr); world::CullingComponent* culling = world->getComponent< world::CullingComponent >(); - m_cullingInstance = culling->allocateInstance(m_mesh); + m_cullingInstance = culling->allocateInstance(this, (intptr_t)m_mesh.getResource()); } + + m_world = world; } void InstanceMeshComponent::setTransform(const Transform& transform) { MeshComponent::setTransform(transform); - // Re-allocate instance if mesh resource has been replaced. - //if (m_mesh.changed()) - //{ - // m_meshInstance = m_mesh->allocateInstance(); - // m_mesh.consume(); - //} - if (m_cullingInstance) m_cullingInstance->setTransform(transform); } @@ -82,22 +73,37 @@ Aabb3 InstanceMeshComponent::getBoundingBox() const return m_mesh->getBoundingBox(); } -void InstanceMeshComponent::update(const world::UpdateParams& update) +void InstanceMeshComponent::build( + const world::WorldBuildContext& context, + const world::WorldRenderView& worldRenderView, + const world::IWorldRenderPass& worldRenderPass +) { - MeshComponent::update(update); - - // Re-allocate instance if mesh resource has been replaced. - //if (m_mesh.changed()) - //{ - // m_meshInstance = m_mesh->allocateInstance(); - // m_meshInstance->setTransform(getTransform().get0()); - // m_mesh.consume(); - //} + // Not used since we're getting called by the culling component when we should build. } -void InstanceMeshComponent::build(const world::WorldBuildContext& context, const world::WorldRenderView& worldRenderView, const world::IWorldRenderPass& worldRenderPass) +void InstanceMeshComponent::cullableBuild( + const world::WorldBuildContext& context, + const world::WorldRenderView& worldRenderView, + const world::IWorldRenderPass& worldRenderPass, + render::Buffer* instanceBuffer, + render::Buffer* visibilityBuffer, + uint32_t start, + uint32_t count +) { - //T_ASSERT_M(0, L"Forgot to register InstanceMeshComponentRenderer?"); + // Draw mesh instances; this method is called for the "first" InstanceMeshComponent using the same ordinal number + // assuming all other components reference the same mesh. + if (m_mesh) + m_mesh->build( + context, + worldRenderView, + worldRenderPass, + instanceBuffer, + visibilityBuffer, + start, + count + ); } } diff --git a/code/Mesh/Instance/InstanceMeshComponent.h b/code/Mesh/Instance/InstanceMeshComponent.h index 12220a2ead..8b75c2c4e8 100644 --- a/code/Mesh/Instance/InstanceMeshComponent.h +++ b/code/Mesh/Instance/InstanceMeshComponent.h @@ -28,7 +28,9 @@ class InstanceMesh; /*! Instancing mesh component. * \ingroup Mesh */ -class T_DLLCLASS InstanceMeshComponent : public MeshComponent +class T_DLLCLASS InstanceMeshComponent +: public MeshComponent +, public world::CullingComponent::ICullable { T_RTTI_CLASS; @@ -45,14 +47,31 @@ class T_DLLCLASS InstanceMeshComponent : public MeshComponent virtual Aabb3 getBoundingBox() const override final; - virtual void update(const world::UpdateParams& update) override final; - - virtual void build(const world::WorldBuildContext& context, const world::WorldRenderView& worldRenderView, const world::IWorldRenderPass& worldRenderPass) override final; + virtual void build( + const world::WorldBuildContext& context, + const world::WorldRenderView& worldRenderView, + const world::IWorldRenderPass& worldRenderPass + ) override final; inline resource::Proxy< InstanceMesh >& getMesh() { return m_mesh; } + /* world::CullingComponent::ICullable */ + + virtual Aabb3 cullableGetBoundingBox() const override final { return getBoundingBox(); } + + virtual void cullableBuild( + const world::WorldBuildContext& context, + const world::WorldRenderView& worldRenderView, + const world::IWorldRenderPass& worldRenderPass, + render::Buffer* instanceBuffer, + render::Buffer* visibilityBuffer, + uint32_t start, + uint32_t count + ) override final; + private: resource::Proxy< InstanceMesh > m_mesh; + world::World* m_world = nullptr; world::CullingComponent::Instance* m_cullingInstance = nullptr; }; diff --git a/code/World/Entity/CullingComponent.cpp b/code/World/Entity/CullingComponent.cpp index b9c7b81984..785b05a327 100644 --- a/code/World/Entity/CullingComponent.cpp +++ b/code/World/Entity/CullingComponent.cpp @@ -47,8 +47,12 @@ void CullingComponent::destroy() T_FATAL_ASSERT_M(m_instances.empty(), L"Culling instances not empty."); safeDestroy(m_instanceBuffer); for (auto visibilityBuffer : m_visibilityBuffers) - safeDestroy(visibilityBuffer); + { + if (visibilityBuffer) + visibilityBuffer->destroy(); + } m_visibilityBuffers.resize(0); + m_renderSystem = nullptr; } void CullingComponent::update(World* world, const UpdateParams& update) @@ -76,6 +80,7 @@ void CullingComponent::build( m_instanceBufferDirty = true; } + // Ensure we have visibility buffers for all cascades. const uint32_t peakCascade = worldRenderView.getCascade(); const uint32_t vbSize = (uint32_t)m_visibilityBuffers.size(); for (uint32_t i = vbSize; i < peakCascade + 1; ++i) @@ -100,7 +105,7 @@ void CullingComponent::build( render::Buffer* visibilityBuffer = m_visibilityBuffers[worldRenderView.getCascade()]; // Cull instances, output are visibility buffer. - // #todo Compute blocks are executed before render pass, so for shadow map rendering all cascades + // Compute blocks are executed before render pass, so for shadow map rendering all cascades // are culled before being rendered. { Vector4 cullFrustum[12]; @@ -115,19 +120,19 @@ void CullingComponent::build( const Vector2 viewSize = worldRenderView.getViewSize(); auto renderBlock = renderContext->allocNamed< render::ComputeRenderBlock >( - str(L"Mesh cull %d", worldRenderView.getCascade()) + str(L"Cull %d", worldRenderView.getCascade()) ); render::Shader::Permutation perm; if (worldRenderPass.getTechnique() == s_techniqueDeferredGBufferWrite) { // Deferred g-buffer pass has access to HiZ texture. - m_shaderCull->setCombination(render::getParameterHandle(L"InstanceMesh_HiZ"), true, perm); + m_shaderCull->setCombination(s_handleCullingHiZ, true, perm); } else { // All other paths use simple frustum culling only. - m_shaderCull->setCombination(render::getParameterHandle(L"InstanceMesh_HiZ"), false, perm); + m_shaderCull->setCombination(s_handleCullingHiZ, false, perm); } renderBlock->program = m_shaderCull->getProgram(perm).program; @@ -150,13 +155,13 @@ void CullingComponent::build( renderContext->compute< render::BarrierRenderBlock >(render::Stage::Compute, render::Stage::Compute, nullptr, 0); } - // Batch draw instances; assumes m_instances are sorted by "cullable". + // Batch draw instances; assumes m_instances are sorted by "ordinal" so we can scan for run length. for (uint32_t i = 0; i < (uint32_t)m_instances.size(); ) { uint32_t j = i + 1; for (; j < (uint32_t)m_instances.size(); ++j) { - if (m_instances[i]->cullable != m_instances[j]->cullable) + if (m_instances[i]->ordinal != m_instances[j]->ordinal) break; } @@ -174,17 +179,18 @@ void CullingComponent::build( } } -CullingComponent::Instance* CullingComponent::allocateInstance(ICullable* cullable) +CullingComponent::Instance* CullingComponent::allocateInstance(ICullable* cullable, intptr_t ordinal) { Instance* instance = new Instance(); instance->owner = this; instance->cullable = cullable; + instance->ordinal = ordinal; instance->transform = Transform::identity(); instance->boundingBox = cullable->cullableGetBoundingBox(); - // Insert instance sorted by cullable so we can calculate run length when building. + // Insert instance sorted by ordinal so we can calculate run length when building. auto it = std::upper_bound(m_instances.begin(), m_instances.end(), instance, [=](Instance* lh, Instance* rh) { - return lh->cullable < rh->cullable; + return lh->ordinal < rh->ordinal; }); m_instances.insert(it, instance); return instance; diff --git a/code/World/Entity/CullingComponent.h b/code/World/Entity/CullingComponent.h index ed32d1dccb..e7f4fa2c6c 100644 --- a/code/World/Entity/CullingComponent.h +++ b/code/World/Entity/CullingComponent.h @@ -65,7 +65,7 @@ class T_DLLCLASS CullingComponent : public IWorldComponent struct T_DLLCLASS ICullable { - virtual const Aabb3& cullableGetBoundingBox() const = 0; + virtual Aabb3 cullableGetBoundingBox() const = 0; virtual void cullableBuild( const WorldBuildContext& context, @@ -82,6 +82,7 @@ class T_DLLCLASS CullingComponent : public IWorldComponent { CullingComponent* owner; ICullable* cullable; + intptr_t ordinal; Transform transform; Aabb3 boundingBox; @@ -100,7 +101,7 @@ class T_DLLCLASS CullingComponent : public IWorldComponent const IWorldRenderPass& worldRenderPass ); - Instance* allocateInstance(ICullable* cullable); + Instance* allocateInstance(ICullable* cullable, intptr_t ordinal); void releaseInstance(Instance*& instance); diff --git a/code/World/World.cpp b/code/World/World.cpp index 8064d83d6a..f9cee43cf4 100644 --- a/code/World/World.cpp +++ b/code/World/World.cpp @@ -26,16 +26,16 @@ void World::destroy() T_FATAL_ASSERT(m_deferredAdd.empty()); T_FATAL_ASSERT(m_deferredRemove.empty()); - for (auto component : m_components) - component->destroy(); - m_components.clear(); - for (auto entity : m_entities) { entity->setWorld(nullptr); entity->destroy(); } m_entities.clear(); + + for (auto component : m_components) + component->destroy(); + m_components.clear(); } void World::setComponent(IWorldComponent* component) diff --git a/code/World/WorldHandles.cpp b/code/World/WorldHandles.cpp index 88dbef3826..2cda87c370 100644 --- a/code/World/WorldHandles.cpp +++ b/code/World/WorldHandles.cpp @@ -1,6 +1,6 @@ /* * TRAKTOR - * Copyright (c) 2022-2023 Anders Pistol. + * Copyright (c) 2022-2024 Anders Pistol. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -99,6 +99,9 @@ const render::Handle s_handleFogVolumeMediumDensity(L"World_FogVolumeMediumDensi // Contact shadows. const render::Handle s_handleContactLightDirection(L"World_ContactLightDirection"); +// Occlusion culling. +const render::Handle s_handleCullingHiZ(L"World_CullingHiZ"); + // ImageGraph inputs. const render::Handle s_handleInputColor(L"InputColor"); const render::Handle s_handleInputColorLast(L"InputColorLast"); diff --git a/code/World/WorldHandles.h b/code/World/WorldHandles.h index bbe59142e9..01d00fbaa6 100644 --- a/code/World/WorldHandles.h +++ b/code/World/WorldHandles.h @@ -109,6 +109,9 @@ extern const render::Handle T_DLLCLASS s_handleFogVolumeMediumDensity; // Contact shadows. extern const render::Handle T_DLLCLASS s_handleContactLightDirection; +// Occlusion culling. +extern const render::Handle T_DLLCLASS s_handleCullingHiZ; + // ImageGraph inputs. extern const render::Handle T_DLLCLASS s_handleInputColor; extern const render::Handle T_DLLCLASS s_handleInputColorLast; diff --git a/data/Source/System/Mesh/Shaders/Instance/Cull/Cull.xdi b/data/Source/System/Mesh/Shaders/Instance/Cull/Cull.xdi index e750a73628..51efed462d 100644 --- a/data/Source/System/Mesh/Shaders/Instance/Cull/Cull.xdi +++ b/data/Source/System/Mesh/Shaders/Instance/Cull/Cull.xdi @@ -5,8 +5,8 @@ {4BAA3A0D-D779-C141-B519-197EEA8640D6} - -398 - 135 + -554 + 108 Global @@ -14,8 +14,8 @@ {B63DBBD3-72C0-C645-8A24-4893BBF22E30} - -169 - 144 + -325 + 117 x @@ -178,13 +178,13 @@ $Buffer[index].visible = $Input; 1212 579 - InstanceMesh_HiZ + World_CullingHiZ {6FAE432D-36E8-D046-96BA-65165BB9DCCC} - 425 + 460 805 World_HiZTexture @@ -281,7 +281,7 @@ $Output = visible ? 1.0f : 0.0f; {7B83C271-F045-4343-9666-B5DC8E2A479D} - 443 + 478 847 FtPoint @@ -307,7 +307,7 @@ $Output = visible ? 1.0f : 0.0f; {2095B07F-6B63-D845-B825-6F4B49DCE4DA} - 377 + 412 889 InstanceMesh_TargetSize @@ -469,7 +469,7 @@ else {099F295D-79C3-9847-A2B5-E3BE5904F79D} - 359 + 412 1145 InstanceMesh_TargetSize @@ -480,7 +480,7 @@ else {68311C48-A517-3146-87E8-D82A66146355} - 425 + 478 1103 FtPoint @@ -498,7 +498,7 @@ else {51312172-C4B7-D843-A00A-5C13B2736AC2} - 407 + 460 1061 World_HiZTexture @@ -516,6 +516,25 @@ else Matrix Frame + + {0E5A6909-38D1-494B-A702-B69D3892E0A1} + + + -487 + 194 + + InstanceMesh_InstanceOffset + Scalar + Draw + + + {CE7F05B8-293A-4D41-B1EC-4682AEE6504A} + + + -186 + 137 + + @@ -758,11 +777,31 @@ else {D2D716D6-C4A1-471F-894A-D718515F6281} + + + + {1E6639B6-8B58-4694-99E7-C058E3583522} + + + + {9F45B2C3-B513-4646-B0C1-663748FD169C} + + {ADB4FC1D-3726-4CC5-B4D5-1E2468274325} + + + {3DE04294-4DEA-4A13-A460-2274647357EA} + + + + + + {32FD3DAA-16C1-44C8-8A1E-E9ECF97F31D2} + {0FF6511C-0293-41A8-840E-81978BD01F7F} @@ -770,8 +809,8 @@ else - - {ADB4FC1D-3726-4CC5-B4D5-1E2468274325} + + {32FD3DAA-16C1-44C8-8A1E-E9ECF97F31D2} diff --git a/data/Source/System/Mesh/Shaders/Instance/Cull/Draw.xdi b/data/Source/System/Mesh/Shaders/Instance/Cull/Draw.xdi index 448a2245e7..10ad0d3ece 100644 --- a/data/Source/System/Mesh/Shaders/Instance/Cull/Draw.xdi +++ b/data/Source/System/Mesh/Shaders/Instance/Cull/Draw.xdi @@ -5,8 +5,8 @@ {AA20BF80-C68A-D649-9BF9-23BC5F6257F8} - -305 - 333 + 119 + 320 x @@ -14,8 +14,8 @@ {0C63324D-D49B-7C44-BE53-17ECD5DBE92E} - -542 - 319 + -106 + 330 Global @@ -23,8 +23,8 @@ {9772F569-3912-1B46-AB4B-157B8CC99284} - -27 - 269 + -23 + 273 InstanceMesh_Visibility @@ -38,7 +38,7 @@ {B6ABD193-2075-0442-A1B6-92D4C05F99CB} - -17 + -7 226 InstanceMesh_Draw @@ -69,8 +69,8 @@ {EF3C22BB-6DAD-ED47-B381-BF0850063421} - 355 - 250 + 358 + 270 WriteOut Default @@ -101,19 +101,25 @@ {618C459F-DE31-4E4D-A0ED-6D01AF8C87AF} PrimFirstIndex + + {7BC3CE94-B279-F442-A983-DD2FA6DFBDD4} + InstanceOffset + @@ -121,8 +127,8 @@ $DrawBuffer[index].firstInstance = 0; {3491574B-839F-B74B-8747-0C4A10BECB57} - 7 - 434 + 15 + 367 InstanceMesh_IndexCount Scalar @@ -133,31 +139,23 @@ $DrawBuffer[index].firstInstance = 0; 31 - 471 + 414 InstanceMesh_FirstIndex Scalar Frame - {63F715EF-F327-F04D-8827-705805B04479} + {2FB4EA3D-40CC-3548-B4A5-D965E96D6C05} - -438 - 398 + -1 + 461 InstanceMesh_InstanceOffset Scalar Draw - - {EBB7D461-F10E-FD4E-B98F-27018D533692} - - - -137 - 341 - - @@ -210,34 +208,24 @@ $DrawBuffer[index].firstInstance = 0; {788C47C3-E973-CF48-9A12-5A2751107996} - - - - {1E6639B6-8B58-4694-99E7-C058E3583522} - - - - {9F45B2C3-B513-4646-B0C1-663748FD169C} - - {ADB4FC1D-3726-4CC5-B4D5-1E2468274325} - - {3DE04294-4DEA-4A13-A460-2274647357EA} + + {89D6EAF6-DAAD-C647-B640-7F86B233A509} - - {32FD3DAA-16C1-44C8-8A1E-E9ECF97F31D2} + + {1E6639B6-8B58-4694-99E7-C058E3583522} - {89D6EAF6-DAAD-C647-B640-7F86B233A509} + {7BC3CE94-B279-F442-A983-DD2FA6DFBDD4} diff --git a/data/Source/System/Mesh/Shaders/Instance/UnpackInstanceData.xdi b/data/Source/System/Mesh/Shaders/Instance/UnpackInstanceData.xdi index d6d040025f..05b56d1318 100644 --- a/data/Source/System/Mesh/Shaders/Instance/UnpackInstanceData.xdi +++ b/data/Source/System/Mesh/Shaders/Instance/UnpackInstanceData.xdi @@ -59,8 +59,8 @@ {BD8029D8-D20D-AE40-9895-EA83610D8339} - 431 - 773 + 425 + 759 InstanceWorld @@ -86,8 +86,8 @@ {5193F279-0901-F345-97E6-238E9D406009} - 415 - 846 + 409 + 832 InstanceWorldLast @@ -173,8 +173,8 @@ {8A459FBA-5749-254B-9853-127DB9AF95F8} - 491 - 919 + 238 + 886 gl_DrawID @@ -232,6 +232,25 @@ $Output = gl_DrawID; BoundingBoxMax + + {24C3ACAD-6FB5-2143-9B61-CD34A943D39E} + + + 453 + 905 + + + + {ECE608D7-5F60-4A4F-A5D9-17256271FD77} + + + 152 + 962 + + InstanceMesh_InstanceOffset + Scalar + Draw + @@ -354,26 +373,6 @@ $Output = gl_DrawID; {F2E22CA6-DFF3-4B20-A70A-0D7A44EACD8C} - - - - {897E2C02-E5A3-A643-AAF8-1507B49AC1C3} - - - - {0FF6511C-0293-41A8-840E-81978BD01F7F} - - - - - - {897E2C02-E5A3-A643-AAF8-1507B49AC1C3} - - - - {0FF6511C-0293-41A8-840E-81978BD01F7F} - - @@ -414,6 +413,46 @@ $Output = gl_DrawID; {731844D4-AFDC-4EAA-8B41-C4BA2455898F} + + + + {1E6639B6-8B58-4694-99E7-C058E3583522} + + + + {9F45B2C3-B513-4646-B0C1-663748FD169C} + + + + + + {897E2C02-E5A3-A643-AAF8-1507B49AC1C3} + + + + {3DE04294-4DEA-4A13-A460-2274647357EA} + + + + + + {32FD3DAA-16C1-44C8-8A1E-E9ECF97F31D2} + + + + {0FF6511C-0293-41A8-840E-81978BD01F7F} + + + + + + {32FD3DAA-16C1-44C8-8A1E-E9ECF97F31D2} + + + + {0FF6511C-0293-41A8-840E-81978BD01F7F} + +