[
  {
    "path": ".editorconfig",
    "content": "# Visual Studio generated .editorconfig file with C++ settings.\nroot = true\n\n[*.{c++,cc,cpp,cppm,cu,cuh,cxx,fx,h,h++,hh,hlsl,hpp,hxx,inl,ipp,ixx,tlh,tli}]\nindent_size = 4;\nindent_style = space;\n\n# Visual C++ Code Style settings\n\ncpp_generate_documentation_comments = xml\n\n# Visual C++ Formatting settings\n\ncpp_indent_braces = false\ncpp_indent_multi_line_relative_to = innermost_parenthesis\ncpp_indent_within_parentheses = indent\ncpp_indent_preserve_within_parentheses = true\ncpp_indent_case_contents = true\ncpp_indent_case_labels = false\ncpp_indent_case_contents_when_block = false\ncpp_indent_lambda_braces_when_parameter = true\ncpp_indent_goto_labels = one_left\ncpp_indent_preprocessor = leftmost_column\ncpp_indent_access_specifiers = false\ncpp_indent_namespace_contents = true\ncpp_indent_preserve_comments = false\ncpp_new_line_before_open_brace_namespace = new_line\ncpp_new_line_before_open_brace_type = new_line\ncpp_new_line_before_open_brace_function = new_line\ncpp_new_line_before_open_brace_block = new_line\ncpp_new_line_before_open_brace_lambda = new_line\ncpp_new_line_scope_braces_on_separate_lines = true\ncpp_new_line_close_brace_same_line_empty_type = false\ncpp_new_line_close_brace_same_line_empty_function = false\ncpp_new_line_before_catch = true\ncpp_new_line_before_else = true\ncpp_new_line_before_while_in_do_while = false\ncpp_space_before_function_open_parenthesis = remove\ncpp_space_within_parameter_list_parentheses = false\ncpp_space_between_empty_parameter_list_parentheses = false\ncpp_space_after_keywords_in_control_flow_statements = true\ncpp_space_within_control_flow_statement_parentheses = false\ncpp_space_before_lambda_open_parenthesis = false\ncpp_space_within_cast_parentheses = false\ncpp_space_after_cast_close_parenthesis = false\ncpp_space_within_expression_parentheses = false\ncpp_space_before_block_open_brace = true\ncpp_space_between_empty_braces = false\ncpp_space_before_initializer_list_open_brace = false\ncpp_space_within_initializer_list_braces = true\ncpp_space_preserve_in_initializer_list = true\ncpp_space_before_open_square_bracket = false\ncpp_space_within_square_brackets = false\ncpp_space_before_empty_square_brackets = false\ncpp_space_between_empty_square_brackets = false\ncpp_space_group_square_brackets = true\ncpp_space_within_lambda_brackets = false\ncpp_space_between_empty_lambda_brackets = false\ncpp_space_before_comma = false\ncpp_space_after_comma = true\ncpp_space_remove_around_member_operators = true\ncpp_space_before_inheritance_colon = true\ncpp_space_before_constructor_colon = true\ncpp_space_remove_before_semicolon = true\ncpp_space_after_semicolon = true\ncpp_space_remove_around_unary_operator = true\ncpp_space_around_binary_operator = insert\ncpp_space_around_assignment_operator = insert\ncpp_space_pointer_reference_alignment = left\ncpp_space_around_ternary_operator = insert\ncpp_use_unreal_engine_macro_formatting = true\ncpp_wrap_preserve_blocks = one_liners\n\n# Visual C++ Inlcude Cleanup settings\n\ncpp_include_cleanup_add_missing_error_tag_type = suggestion\ncpp_include_cleanup_remove_unused_error_tag_type = dimmed\ncpp_include_cleanup_sort_after_edits = false\ncpp_sort_includes_error_tag_type = none\ncpp_sort_includes_priority_case_sensitive = false\ncpp_sort_includes_priority_style = quoted\ncpp_includes_style = default\ncpp_includes_use_forward_slash = true\n\n[*.md]\nspelling_languages = en-us\nspelling_error_severity = warning"
  },
  {
    "path": ".gitattributes",
    "content": "# Default behavior in case core.autocrlf set\n* text=auto\n\n*.dll filter=lfs diff=lfs merge=lfs -text\n*.pdb filter=lfs diff=lfs merge=lfs -text\n"
  },
  {
    "path": ".gitignore",
    "content": "*.*~\nscenes\nbin/\n_build/\n_install/\nbuild/\n.vscode/\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "stages:\r\n  - build\r\n\r\nvariables:\r\n  GIT_SUBMODULE_STRATEGY: recursive\r\n\r\nbuild-windows:\r\n  stage: build\r\n  tags:\r\n    - os/win11\r\n  rules:\r\n    - if: '$ENABLE_JOBS =~ /build-windows/'\r\n  parallel:\r\n    matrix:\r\n      - BUILD_TYPE: ['Release', 'Debug']\r\n        DX12: 'ON'\r\n        VULKAN: 'OFF'\r\n  script:\r\n    - tools/set_vs_vars.ps1\r\n    - mkdir build\r\n    - cd build\r\n    - cmake .. -G \"Visual Studio 17 2022\" -A x64 \"-DCMAKE_BUILD_TYPE=$BUILD_TYPE\" \"-DDONUT_WITH_DX12=$DX12\" \"-DDONUT_WITH_VULKAN=$VULKAN\"\r\n    - cmake --build . --config \"$BUILD_TYPE\" --target package\r\n    \r\n  artifacts:\r\n    when: on_success\r\n    access: all\r\n    expire_in: 30 days\r\n    paths: ['build/rtxmg.*.zip']"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"extern/donut\"]\n\tpath = extern/donut\n\turl = https://github.com/NVIDIA-RTX/Donut.git\n[submodule \"assets\"]\n\tpath = assets\n\turl = https://github.com/NVIDIA-RTX/RTXMG-Sample-Assets.git\n[submodule \"extern/osd_lite\"]\n\tpath = extern/osd_lite\n\turl = https://github.com/NVIDIA-RTX/OSD-Lite.git\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# RTX Mega Geometry SDK Change Log\n\n## 1.0.1\n\nImprovements\n* Add smooth vertex normal support which allows for lower tessellation rates and includes memory profiler row for vertex normals\n* Filter out degenerate normals for backface culling to improve backface test and reduce triangle count\n\nDebugging Improvements\n* Improve shader debug with stable cluster index output from path tracing shader\n* Add surface highlighting feature where path-tracer will blink highlight a selected debug surface\n* Add utilities to shader debug to force output\n\nBug Fixes\n* Fix regression in surface 1-ring culling caused by 1D thread-ordering change, which resulted in incorrect lanes doing work for unrelated waves\n* Fix crash when refreshing media list after changing json/obj filters with an asset already loaded\n\n## 1.0.0\n\nVulkan Support\n* Requires Vulkan SDK 1.4.313 or greater which uses Cluster SPIRV intrinsics\n* Convert all bindless arrays to use ResourceDescriptorHeap via Vulkan's mutable descriptor extension\n* Fix validation warnings and Vulkan shutdown crashes\n\nMinor Changes\n* Expose isolation level in the UI\n* Add smooth single crease sharpness, which prevents transition artifacts between single and multicrease edges. Only visible if the isolation level is lowered.\n* Improve Profiler \"Frame\" Tab, add average times in tool tip, expose motion vector pass time\n* Update to Streamline v2.8.0\n\nBug Fixes\n* Fix thread-ordering to be compliant with SM6.6 1D quad lane ordering.\n* Fix micro-triangle view toggle when in DLAA mode.\n* Fix cases where a surface resulted in over U16_MAX clusters\n* Fix tessellation for when there was per-material displacement scaling\n* Fix crash for some malformed OBJ files with '0' values for some indices.\n\n## 0.9.2\n\nPerformance Improvements \n\nTest Scene: amy_kitchenset.scene.json (default camera: 79M microtriangles) \nHardware: RTX 5090 @ 4K Render Resolution (r572.83)\n\n* Compute Cluster tiling: 4.0ms -> 1.0ms (300% speedup)\n    * Coalesced UAV writes for structs, unaligned members were causing UAV readbacks\n    * Coalesce per wave atomics into a groupshared atomic to reduce pressure on global/UAV atomics by 4x\n* Fill clusters: 4.9ms to -> 1.0ms (390% speedup)\n    * Fixed cases where the compiler was unable to unroll loops due to dynamic loop counts\n    * Specialized shaders by subdivision surface type, with a special path for Pure BSpline surfaces. Prefetch all control points into shared memory to be used wave wide.\n\nMinor Fixes\n\n* Fix SpecularHitT guide buffer to DLSS-RR to improve coherence of specular rays.\n\n## 0.9.1\n\nStability\n* Fix crash on scenes with multiple subdivision mesh instances when the topology quality color mode was selected\n* Fix a crash if scene with audio is loaded and no audio devices are present.\n\nTopology Quality\n* Add button in the Subdivision Evaluator tab to switch to topology quality view if issues are detected\n* Fix Subdivision Evaluator UI not resetting upon scene load.\n\nUI\n* Application Window Size/Maximized/Fullscreen and Window state is now saved to and restored from imgui.ini. Delete imgui.ini to reset layout\n\nMinor\n* Make initial VRAM check non-fatal but add warnings about performance degradation, memory budgets.\n* Made localToWorld transform use Matrix3x4 for consistency\n* Style clean-up\n* Update donut version\n\n## 0.9.0\n\nInitial beta release.\n"
  },
  {
    "path": "CMake/FetchDXC.cmake",
    "content": "#\n# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#  * Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n#  * Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in the\n#    documentation and/or other materials provided with the distribution.\n#  * Neither the name of NVIDIA CORPORATION nor the names of its\n#    contributors may be used to endorse or promote products derived\n#    from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n#\ninclude(FetchContent)\n\nset(DXC_URL https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.8.2407/dxc_2024_07_31.zip)\nset(DXC_SHA256_HASH e2627f004f0f9424d8c71ea1314d04f38c5a5096884ae9217f1f18bd320267b5)\nFetchContent_Declare(\n    DXC\n    URL ${DXC_URL}\n    URL_HASH SHA256=${DXC_SHA256_HASH}\n    DOWNLOAD_EXTRACT_TIMESTAMP ON\n)\nFetchContent_MakeAvailable(DXC)\nmessage(STATUS \"Updating DXC from ${DXC_URL}, SHA256 ${DXC_SHA256_HASH}, into folder ${dxc_SOURCE_DIR}\")\n\nset(SHADERMAKE_FIND_DXC OFF CACHE BOOL \"\" FORCE)\nset(DXC_PATH \"${dxc_SOURCE_DIR}/bin/x64/dxc.exe\" CACHE STRING \"\" FORCE)"
  },
  {
    "path": "CMake/FetchImplot.cmake",
    "content": "#\n# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#  * Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n#  * Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in the\n#    documentation and/or other materials provided with the distribution.\n#  * Neither the name of NVIDIA CORPORATION nor the names of its\n#    contributors may be used to endorse or promote products derived\n#    from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n#\n\nif( TARGET implot )\n    return()\nendif()\n\n\nif (NOT TARGET imgui)\n    message(FATAL_ERROR \"Implot requires imgui\")\nendif()\n\ninclude(FetchContent)\nFetchContent_Declare(\n    implot\n    GIT_REPOSITORY https://github.com/epezent/implot.git\n    GIT_TAG v0.16\n    )\nFetchContent_MakeAvailable(implot)\n\n# Override Imgui build - we want a lean static library\n\nset(implot_srcs\n    ${CMAKE_BINARY_DIR}/_deps/implot-src/implot.cpp\n    ${CMAKE_BINARY_DIR}/_deps/implot-src/implot.h\n    ${CMAKE_BINARY_DIR}/_deps/implot-src/implot_internal.h\n    ${CMAKE_BINARY_DIR}/_deps/implot-src/implot_items.cpp\n)\n\nadd_library(implot STATIC ${implot_srcs})\nset_target_properties(implot PROPERTIES POSITION_INDEPENDENT_CODE ON)\nadd_compile_definitions(implot PRIVATE IMGUI_DEFINE_MATH_OPERATORS)\ntarget_include_directories(implot PUBLIC \"${CMAKE_BINARY_DIR}/_deps/implot-src/\")\ntarget_link_libraries(implot imgui)\n"
  },
  {
    "path": "CMake/FetchNVAPI.cmake",
    "content": "#\n# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#  * Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n#  * Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in the\n#    documentation and/or other materials provided with the distribution.\n#  * Neither the name of NVIDIA CORPORATION nor the names of its\n#    contributors may be used to endorse or promote products derived\n#    from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n#\ninclude(FetchContent)\n\nset(NVAPI_FETCH_URL \"https://github.com/NVIDIA/nvapi.git\" CACHE STRING \"Url to nvapi git repo to fetch\")\nset(NVAPI_FETCH_TAG \"ce6d2a183f9559f717e82b80333966d19edb9c8c\" CACHE STRING \"Tag of nvapi git repo\")\nset(NVAPI_FETCH_DIR \"\" CACHE STRING \"Directory to fetch streamline to, empty uses build directory default\")\n\ninclude(FetchContent)\nFetchContent_Declare(\n    nvapi\n    GIT_REPOSITORY ${NVAPI_FETCH_URL}\n    GIT_TAG ${NVAPI_FETCH_TAG}\n    SOURCE_DIR ${NVAPI_FETCH_DIR}\n)\nFetchContent_MakeAvailable(nvapi)\n\nmessage(STATUS \"Updating nvapi from ${NVAPI_FETCH_URL}, tag ${NVAPI_FETCH_TAG}, into folder ${nvapi_SOURCE_DIR}\")\nset(NVAPI_SEARCH_PATHS \"${nvapi_SOURCE_DIR}\")"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\n\nproject(rtxmg LANGUAGES C CXX VERSION 1.0.1)\n\nset(CMAKE_CONFIGURATION_TYPES \"Debug;Release;RelWithDebInfo;ReleaseAsan\" CACHE STRING \"Configurations\" FORCE)\nset(CMAKE_CXX_STANDARD 23)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS ON)\nset(CMAKE_FIND_ROOT_PATH ${CMAKE_SOURCE_DIR}/extern)\n\n####################################\n# Static analysis\n####################################\nset(RULESET_PATH \"${CMAKE_SOURCE_DIR}/rtxmg_static_analysis.ruleset\")\nadd_compile_options(\"-wd26495\")\nset_property( GLOBAL PROPERTY VS_USER_PROPS \"rtxmg_static_analysis.props\")\n\n####################################\n# ASAN\n####################################\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_SOURCE_DIR}/bin\")\nforeach(CONFIG_TYPE DEBUG RELEASE MINSIZEREL RELWITHDEBINFO RELEASEASAN)\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG_TYPE} \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\")\nendforeach()\nset(CMAKE_CXX_FLAGS_RELEASEASAN \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fsanitize=address\")\nset(CMAKE_EXE_LINKER_FLAGS_RELEASEASAN \"${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}\")\nset(CMAKE_SHARED_LINKER_FLAGS_RELEASEASAN \"${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}\")\n\n####################################\n# AgilitySDK\n####################################\nif(WIN32)\n    # this SDK requires features from the D3D12 Agility SDK on Win 10\n    if(CMAKE_SYSTEM_VERSION LESS 11.0)\n        set(DONUT_D3D_AGILITY_SDK_URL \"https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/1.614.1\")\n        # set fetch destination to cmake default build/_deps folder\n        set(DONUT_D3D_AGILITY_SDK_FETCH_DIR \"\" CACHE STRING \"\")\n        include(\"${PROJECT_SOURCE_DIR}/extern/donut/cmake/FetchAgilitySDK.cmake\")\n    else()\n        message(STATUS \"Windows 11 or newer detected: disabling Agility SDK\")\n    endif()\nendif()\n\n####################################\n# DXC\n# WindowsSDK version is buggy and the compiler crashes\n####################################\nset(SHADERMAKE_FIND_DXC ON CACHE BOOL \"\" FORCE)\n\n####################################\n# Streamline\n####################################\nset(DONUT_WITH_STREAMLINE ON CACHE BOOL \"\" FORCE)\nset(DONUT_STREAMLINE_FETCH_URL \"https://github.com/NVIDIA-RTX/Streamline/releases/download/v2.8.0/streamline-sdk-v2.8.0.zip\" CACHE STRING \"\")\nset(DONUT_STREAMLINE_FETCH_SHA256 \"313669f8cf886f823ea0518a50712efa5e2e8623689ed2a6bd0d9353e475bc47\" CACHE STRING \"\")\nset(STREAMLINE_FEATURE_DLSS_SR ON CACHE BOOL \"\" FORCE)\nset(STREAMLINE_FEATURE_DLSS_RR ON CACHE BOOL \"\" FORCE)\nset(STREAMLINE_INSTALL_DIR \"bin\" CACHE STRING \"\" FORCE)\n\n####################################\n# NVAPI\n####################################\ninclude(\"${PROJECT_SOURCE_DIR}/CMake/FetchNVAPI.cmake\")\n\n####################################\n# DONUT\n####################################\noption(DONUT_WITH_DX11 OFF)\nset(DONUT_WITH_VULKAN ON CACHE BOOL \"Enable the Vulkan version of Donut\")\nset(NVRHI_WITH_NVAPI ON CACHE BOOL \"\" FORCE)\nset(DONUT_WITH_AFTERMATH ON CACHE BOOL \"\" FORCE)\nset(NVRHI_INSTALL OFF CACHE BOOL \"\" FORCE)\n\n# Shaders\nset(DONUT_SHADERS_OUTPUT_DIR \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders/framework\")\nset(DONUT_DIR \"${PROJECT_SOURCE_DIR}/extern/donut\")\n\nlist(APPEND RTXMG_SHADERS_INCLUDE_DIR\n\"${PROJECT_SOURCE_DIR}\"\n\"${PROJECT_SOURCE_DIR}/extern\"\n\"${PROJECT_SOURCE_DIR}/extern/donut/include\"\n\"${PROJECT_SOURCE_DIR}/rtxmg/include\"\n\"${nvapi_SOURCE_DIR}\"\n\"${PROJECT_SOURCE_DIR}/extern/donut/nvrhi/include\")\n\nset(RTXMG_SHADERMAKE_OPTIONS \"--embedPDB --hlsl2021\")\n\n#-vd disable validator due to a bug in \n# https://github.com/microsoft/DirectXShaderCompiler/issues/7181\n# should be fixed in version 1.8.2505\nset(RTXMG_SHADERMAKE_OPTIONS_SPIRV \"--vulkanMemoryLayout scalar --compilerOptions -Vd\") \nset(RTXMG_SHADERS_SHADERMODEL \"6_6\")\n\nset(RTXMG_SHADERS_IGNORED_INCLUDES \"\")\nlist(APPEND RTXMG_SHADERS_IGNORED_INCLUDES\n    \"donut/core/math/math.h\"\n    \"nvrhi/nvrhi.h\"\n    \"array\"\n    \"assert.h\"\n    \"cstdint\"\n    \"cstdio\"\n    \"stdio.h\"\n    \"cmath\"\n    \"ostream\"\n    \"rtxmg/utils/buffer.h\"\n)\n\nadd_subdirectory(extern/donut)\nadd_subdirectory(extern/osd_lite EXCLUDE_FROM_ALL)\nadd_subdirectory(rtxmg)\nadd_subdirectory(demo)\n\nadd_subdirectory(extern)\n\nset_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT rtxmg_demo)\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "NVIDIA RTX SDKs LICENSE\n\nThis license is a legal agreement between you and NVIDIA Corporation (\"NVIDIA\") and governs the use of the NVIDIA RTX \nsoftware development kits, including the DLSS SDK, NGX SDK, RTXGI SDK, RTXDI SDK, RTX Video SDK and/or NRD SDK, if and \nwhen made available to you under this license (in each case, the SDK).\nThis license can be accepted only by an adult of legal age of majority in the country in which the SDK is used. If you are \nunder the legal age of majority, you must ask your parent or legal guardian to consent to this license. If you are entering this \nlicense on behalf of a company or other legal entity, you represent that you have legal authority and you will mean the \nentity you represent. \nBy using the SDK, you affirm that you have reached the legal age of majority, you accept the terms of this license, and you \ntake legal and financial responsibility for the actions of your permitted users. \n\nYou agree to use the SDK only for purposes that are permitted by (a) this license, and (b) any applicable law, regulation or \ngenerally accepted practices or guidelines in the relevant jurisdictions.\n1. LICENSE. Subject to the terms of this license and the terms in the supplement attached, NVIDIA hereby grants you a non-\nexclusive, non-transferable license, without the right to sublicense (except as expressly provided in this license) to: \na. Install and use the SDK,\nb. Modify and create derivative works of sample source code delivered in the SDK, and\nc. Distribute any software and materials within the SDK, other than developer tools provided for your internal use, as \nincorporated in object code format into a software application subject to the distribution requirements indicated in this \nlicense.\n\n2. DISTRIBUTION REQUIREMENTS. These are the distribution requirements for you to exercise the grants above:\na.\tAn application must have material additional functionality, beyond the included portions of the SDK. \nb.\tThe following notice shall be included in modifications and derivative works of source code distributed: This software \ncontains source code provided by NVIDIA Corporation. \nc.\tYou agree to distribute the SDK subject to the terms at least as protective as the terms of this license, including (without \nlimitation) terms relating to the license grant, license restrictions and protection of NVIDIAs intellectual property rights. \nAdditionally, you agree that you will protect the privacy, security and legal rights of your application users. \nd.\tYou agree to notify NVIDIA in writing of any known or suspected distribution or use of the SDK not in compliance with \nthe requirements of this license, and to enforce the terms of your agreements with respect to the distributed portions of \nthe SDK. \n3. AUTHORIZED USERS. You may allow employees and contractors of your entity or of your subsidiary(ies) to access and use \nthe SDK from your secure network to perform work on your behalf. If you are an academic institution you may allow users \nenrolled or employed by the academic institution to access and use the SDK from your secure network. You are responsible \nfor the compliance with the terms of this license by your authorized users. \n\n4. LIMITATIONS. Your license to use the SDK is restricted as follows:\na.\tYou may not reverse engineer, decompile or disassemble, or remove copyright or other proprietary notices from any \nportion of the SDK or copies of the SDK. \nb.\tExcept as expressly provided in this license, you may not copy, sell, rent, sublicense, transfer, distribute, modify, or \ncreate derivative works of any portion of the SDK. For clarity, you may not distribute or sublicense the SDK as a stand-alone \nproduct.\nc.\t Unless you have an agreement with NVIDIA for this purpose, you may not indicate that an application created with the \nSDK is sponsored or endorsed by NVIDIA.\nd.\t You may not bypass, disable, or circumvent any technical limitation, encryption, security, digital rights management or \nauthentication mechanism in the SDK.  \ne.\tYou may not use the SDK in any manner that would cause it to become subject to an open source software license. As \nexamples, licenses that require as a condition of use, modification, and/or distribution that the SDK be: (i) disclosed or \ndistributed in source code form; (ii) licensed for the purpose of making derivative works; or (iii) redistributable at no charge.\nf.\t Unless you have an agreement with NVIDIA for this purpose, you may not use the SDK with any system or application \nwhere the use or failure of the system or application can reasonably be expected to threaten or result in personal injury, \ndeath, or catastrophic loss. Examples include use in avionics, navigation, military, medical, life support or other life critical \napplications. NVIDIA does not design, test or manufacture the SDK for these critical uses and NVIDIA shall not be liable to \nyou or any third party, in whole or in part, for any claims or damages arising from such uses. \ng.\tYou agree to defend, indemnify and hold harmless NVIDIA and its affiliates, and their respective employees, contractors, \nagents, officers and directors, from and against any and all claims, damages, obligations, losses, liabilities, costs or debt, \nfines, restitutions and expenses (including but not limited to attorneys fees and costs incident to establishing the right of \nindemnification) arising out of or related to your use of the SDK outside of the scope of this license, or not in compliance \nwith its terms. \n\n5. UPDATES. NVIDIA may, at its option, make available patches, workarounds or other updates to this SDK. Unless the \nupdates are provided with their separate governing terms, they are deemed part of the SDK licensed to you as provided in \nthis license. Further, NVIDIA may, at its option, automatically update the SDK or other software in the system, except for \nthose updates that you may opt-out via the SDK API. You agree that the form and content of the SDK that NVIDIA provides \nmay change without prior notice to you. While NVIDIA generally maintains compatibility between versions, NVIDIA may in \nsome cases make changes that introduce incompatibilities in future versions of the SDK.\n\n6. PRE-RELEASE VERSIONS. SDK versions identified as alpha, beta, preview, early access or otherwise as pre-release may not \nbe fully functional, may contain errors or design flaws, and may have reduced or different security, privacy, availability, and \nreliability standards relative to commercial versions of NVIDIA software and materials. You may use a pre-release SDK \nversion at your own risk, understanding that these versions are not intended for use in production or business-critical \nsystems. NVIDIA may choose not to make available a commercial version of any pre-release SDK. NVIDIA may also choose to \nabandon development and terminate the availability of a pre-release SDK at any time without liability.\n\n7. THIRD-PARTY COMPONENTS. The SDK may include third-party components with separate legal notices or terms as may \nbe described in proprietary notices accompanying the SDK. If and to the extent there is a conflict between the terms in this \nlicense and the third-party license terms, the third-party terms control only to the extent necessary to resolve the conflict. \n\n8. OWNERSHIP. \n\n8.1 NVIDIA reserves all rights, title and interest in and to the SDK not expressly granted to you under this license. NVIDIA \nand its suppliers hold all rights, title and interest in and to the SDK, including their respective intellectual property rights. \nThe SDK is copyrighted and protected by the laws of the United States and other countries, and international treaty \nprovisions.\n\n8.2 Subject to the rights of NVIDIA and its suppliers in the SDK, you hold all rights, title and interest in and to your \napplications and your derivative works of the sample source code delivered in the SDK including their respective intellectual \nproperty rights. \n \n9. FEEDBACK. You may, but are not obligated to, provide Feedback to NVIDIA. Feedback means all suggestions, fixes, \nmodifications, feature requests or other feedback regarding the SDK. Feedback, even if designated as confidential by you, \nshall not create any confidentiality obligation for NVIDIA. If you provide Feedback, you hereby grant NVIDIA, its affiliates \nand its designees a non-exclusive, perpetual, irrevocable, sublicensable, worldwide, royalty-free, fully paid-up and \ntransferable license, under your intellectual property rights, to publicly perform, publicly display, reproduce, use, make, \nhave made, sell, offer for sale, distribute (through multiple tiers of distribution), import, create derivative works of and \notherwise commercialize and exploit the Feedback at NVIDIAs discretion. You will not give Feedback (i) that you have \nreason to believe is subject to any restriction that impairs the exercise of the grant stated in this section, such as third-party \nintellectual property rights or (ii) subject to license terms which seek to require any product incorporating or developed \nusing such Feedback, or other intellectual property of NVIDIA or its affiliates, to be licensed to or otherwise shared with any \nthird party. \n\n10. NO WARRANTIES. THE SDK IS PROVIDED AS-IS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW NVIDIA AND \nITS AFFILIATES EXPRESSLY DISCLAIM ALL WARRANTIES OF ANY KIND OR NATURE, WHETHER EXPRESS, IMPLIED OR \nSTATUTORY, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, FITNESS FOR A \nPARTICULAR PURPOSE, USAGE OF TRADE AND COURSE OF DEALING. NVIDIA DOES NOT WARRANT THAT THE SDK WILL \nMEET YOUR REQUIREMENTS OR THAT THE OPERATION THEREOF WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ALL \nERRORS WILL BE CORRECTED. \n\n11. LIMITATIONS OF LIABILITY. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW NVIDIA AND ITS AFFILIATES SHALL \nNOT BE LIABLE FOR ANY (I) SPECIAL, INCIDENTAL, PUNITIVE OR CONSEQUENTIAL DAMAGES, OR FOR DAMAGES FOR  (A) \nANY LOST PROFITS, PROJECT DELAYS, LOSS OF USE, LOSS OF DATA OR LOSS OF GOODWILL, OR (B) THE COSTS OF \nPROCURING SUBSTITUTE PRODUCTS, ARISING OUT OF OR IN CONNECTION WITH THIS LICENSE OR THE USE OR \nPERFORMANCE OF THE SDK, WHETHER SUCH LIABILITY ARISES FROM ANY CLAIM BASED UPON BREACH OF CONTRACT, \nBREACH OF WARRANTY, TORT (INCLUDING NEGLIGENCE), PRODUCT LIABILITY OR ANY OTHER CAUSE OF ACTION OR \nTHEORY OF LIABILITY, EVEN IF NVIDIA HAS PREVIOUSLY BEEN ADVISED OF, OR COULD REASONABLY HAVE FORESEEN, THE \nPOSSIBILITY OF SUCH DAMAGES. IN NO EVENT WILL NVIDIAS AND ITS AFFILIATES TOTAL CUMULATIVE LIABILITY UNDER OR \nARISING OUT OF THIS LICENSE EXCEED US$10.00. THE NATURE OF THE LIABILITY OR THE NUMBER OF CLAIMS OR SUITS \nSHALL NOT ENLARGE OR EXTEND THIS LIMIT. \n\n12. TERMINATION. Your rights under this license will terminate automatically without notice from NVIDIA if you fail to \ncomply with any term and condition of this license or if you commence or participate in any legal proceeding against \nNVIDIA with respect to the SDK. NVIDIA may terminate this license with advance written notice to you, if NVIDIA decides to \nno longer provide the SDK in a country or, in NVIDIAs sole discretion, the continued use of it is no longer commercially \nviable. Upon any termination of this license, you agree to promptly discontinue use of the SDK and destroy all copies in your \npossession or control. Your prior distributions in accordance with this license are not affected by the termination of this \nlicense. All provisions of this license will survive termination, except for the license granted to you. \n\n13. APPLICABLE LAW. This license will be governed in all respects by the laws of the United States and of the State of \nDelaware, without regard to the conflicts of laws principles. The United Nations Convention on Contracts for the \nInternational Sale of Goods is specifically disclaimed. You agree to all terms of this license in the English language. The state \nor federal courts residing in Santa Clara County, California shall have exclusive jurisdiction over any dispute or claim arising \nout of this license. Notwithstanding this, you agree that NVIDIA shall still be allowed to apply for injunctive remedies or \nurgent legal relief in any jurisdiction. \n\n14. NO ASSIGNMENT. This license and your rights and obligations thereunder may not be assigned by you by any means or \noperation of law without NVIDIAs permission. Any attempted assignment not approved by NVIDIA in writing shall be void \nand of no effect. NVIDIA may assign, delegate or transfer this license and its rights and obligations, and if to a non-affiliate \nyou will be notified.\n \n15. EXPORT. The SDK is subject to United States export laws and regulations. You agree to comply with all applicable U.S. \nand international export laws, including the Export Administration Regulations (EAR) administered by the U.S. Department \nof Commerce and economic sanctions administered by the U.S. Department of Treasurys Office of Foreign Assets Control \n(OFAC). These laws include restrictions on destinations, end-users and end-use. By accepting this license, you confirm that \nyou are not currently residing in a country or region currently embargoed by the U.S. and that you are not otherwise \nprohibited from receiving the SDK.\n\n16. GOVERNMENT USE. The SDK, documentation and technology (Protected Items) are Commercial products as this \nterm is defined at 48 C.F.R. 2.101, consisting of commercial computer software and commercial computer software \ndocumentation as such terms are used in, respectively, 48 C.F.R. 12.212 and 48 C.F.R. 227.7202 & 252.227-7014(a)(1). \nBefore any Protected Items are supplied to the U.S. Government, you will (i) inform the U.S. Government in writing that the \nProtected Items are and must be treated as commercial computer software and commercial computer software \ndocumentation developed at private expense; (ii) inform the U.S. Government that the Protected Items are provided \nsubject to the terms of the Agreement; and (iii) mark the Protected Items as commercial computer software and \ncommercial computer software documentation developed at private expense. In no event will you permit the U.S. \nGovernment to acquire rights in Protected Items beyond those specified in 48 C.F.R. 52.227-19(b)(1)-(2) or 252.227-7013(c) \nexcept as expressly approved by NVIDIA in writing.\n\n17. NOTICES. You agree that any notices that NVIDIA sends you electronically, such as via email, will satisfy any legal \ncommunication requirements. Please direct your legal notices or other correspondence to NVIDIA Corporation, 2788 San \nTomas Expressway, Santa Clara, California 95051, United States of America, Attention: Legal Department.\n\n18. ENTIRE AGREEMENT. This license is the final, complete and exclusive agreement between the parties relating to the \nsubject matter of this license and supersedes all prior or contemporaneous understandings and agreements relating to this \nsubject matter, whether oral or written. If any court of competent jurisdiction determines that any provision of this license \nis illegal, invalid or unenforceable, the remaining provisions will remain in full force and effect. Any amendment or waiver \nunder this license shall be in writing and signed by representatives of both parties.\n\n19. LICENSING. If the distribution terms in this license are not suitable for your organization, or for any questions regarding \nthis license, please contact NVIDIA at nvidia-rtx-license-questions@nvidia.com.\n(v. February 23, 2024)\n\n                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\nNVIDIA RTX SUPPLEMENT TO SOFTWARE LICENSE AGREEMENT FOR NVIDIA SOFTWARE DEVELOPMENT KITS\nThe terms in this supplement govern your use of the NVIDIA RTX SDKs, including the DLSS SDK, NGX SDK, RTXGI SDK, RTXDI \nSDK, Video SDK and/or NRD SDK, if and when made available to you (in each case, the SDK) under the terms of your \nlicense agreement (Agreement) as modified by this supplement. Capitalized terms used but not defined below have the \nmeaning assigned to them in the Agreement.\nThis supplement is an exhibit to the Agreement and is incorporated as an integral part of the Agreement. In the event of \nconflict between the terms in this supplement and the terms in the Agreement, the terms in this supplement govern.  \n\n1. Interoperability. Your applications that incorporate, or are based on, the SDK must be fully interoperable with \ncompatible GPU hardware products designed by NVIDIA or its affiliates. Further, the DLSS SDK and NGX SDK are licensed for \nyou to develop applications only for their use in systems with NVIDIA GPUs.\n\n2. Game License. You may, but are not obligated to, provide your game or related content (Game Content) to NVIDIA. If \nyou provide Game Content, you hereby grant NVIDIA, its affiliates and its designees a non-exclusive, perpetual, irrevocable, \nworldwide, royalty-free, fully paid-up license, to use the Game Content to improve NVIDIA DLSS SDK and DLSS Model \nTraining. The terms in this section do not apply to the Video SDK.\n\n3. Limitations for the DLSS SDK and NGX SDK. Your applications that incorporate, or are based on, the DLSS SDK or NGX SDK \nmay be deployed in a cloud service that runs on systems that consume NVIDIA vGPU software, and any other cloud service \nuse of such SDKs or their functionality is outside of the scope of the Agreement. For the purpose of this section, cloud \nservices include application service providers or service bureaus, operators of hosted/virtual system environments, or \nhosting, time sharing or providing any other type of service to others. \n4. Notification for the DLSS SDK and NGX SDK. You are required to notify NVIDIA prior to commercial release of an \napplication (including a plug-in to a commercial application) that incorporates, or is based on, the DLSS SDK or NGX SDK. \nPlease send notifications to: https://developer.nvidia.com/sw-notification and provide the following information in the \nemail: company name, publisher and developer name, NVIDIA SDK used, application name, platform (i.e. PC, Linux), \nscheduled ship date, and weblink to product/video. \n\n5. Audio and Video Encoders and Decoders. You acknowledge and agree that it is your sole responsibility to obtain any \nadditional third-party licenses required to make, have made, use, have used, sell, import, and offer for sale your products or \nservices that include or incorporate any third-party software and content relating to audio and/or video encoders and \ndecoders from, including but not limited to, Microsoft, Thomson, Fraunhofer IIS, Sisvel S.p.A., MPEG-LA, and Coding \nTechnologies. NVIDIA does not grant to you under this Agreement any necessary patent or other rights with respect to any \naudio and/or video encoders and decoders. The terms in this section do not apply to the Video SDK.\n\n6. DLSS SDK Terms. By installing or using the DLSS SDK you agree that NVIDIA can make over-the-air updates of DLSS in \nsystems that have DLSS installed, including (without limitation) for quality, stability or performance improvements or to \nsupport new hardware. If you publicly release a DLSS integration in an end user game or application that presents material \nstability, performance, image quality, or other technical issues impacting the user experience, you will work to quickly \naddress the integration issues. In the case issues are not addressed, NVIDIA reserves the right, as a last resort, to \ntemporarily disable the DLSS integration until the issues can be fixed.\n7. Marketing.\n7.1 Marketing Activities. Your license to the SDK(s) under the Agreement is subject to your compliance with the following \nmarketing terms:\n(a)  Identification by You in the DLSS SDK, Video SDK or NGX SDK.  During the term of the Agreement, NVIDIA agrees that \nyou may identify NVIDIA on your websites, printed collateral, trade-show displays and other retail packaging materials, as \nthe supplier of the DLSS SDK, Video SDK or NGX SDK for the applications that were developed with use of such SDKs, \nprovided that all such references to NVIDIA will be subject to NVIDIA's prior review and written approval, which will not be \nunreasonably withheld or delayed. \n(b)  NVIDIA Trademark Placement in Applications with the DLSS SDK or NGX SDK.  For applications that incorporate the DLSS \nSDK or NGX SDK or portions thereof, you must attribute the use of the applicable SDK and include the NVIDIA Marks on \nsplash screens, in the about box of the application (if present), and in credits for game applications.\n(c) NVIDIA Trademark Placement in Applications with a licensed SDK, other than the DLSS SDK, Video SDK or NGX SDK. For \napplications that incorporates and/or makes use of a licensed SDK, other than the DLSS SDK, Video SDK or NGX SDK, you \nmust attribute the use of the applicable SDK and include the NVIDIA Marks on the credit screen for applications that have \nsuch credit screen, or where a credit screen is not present prominently in end user documentation for the application. \n(d) Identification by NVIDIA in the DLSS SDK, Video SDK or NGX SDK.  You agree that NVIDIA may identify you on NVIDIA's \nwebsites, printed collateral, trade-show displays, and other retail packaging materials as an individual or entity that \nproduces products and services which incorporate  the DLSS SDK, Video SDK or NGX SDK as applicable. To the extent that \nyou provide NVIDIA with input or usage requests with regard to the use of your logo or materials, NVIDIA will use \ncommercially reasonable efforts to comply with such requests.  For the avoidance of doubt, NVIDIAs rights pursuant to this \nsection shall survive any expiration or termination of the Agreement with respect to existing applications which incorporate \nthe DLSS SDK, Video SDK or NGX SDK.\n(e) Applications Marketing Material in the DLSS SDK Video SDK or NGX SDK. You may provide NVIDIA with screenshots, \nimagery, and video footage of applications representative of your use of the NVIDIA DLSS SDK or NGX SDKs in your \napplication (collectively, Assets). You hereby grant to NVIDIA the right to create and display self-promotional demo \nmaterials using the Assets, and after release of the application to the public to distribute, sub-license, and use the Assets to \npromote and market the NVIDIA RTX SDKs.  To the extent you provide NVIDIA with input or usage requests with regard to \nthe use of your logo or materials, NVIDIA will use commercially reasonable efforts to comply with such requests.  For the \navoidance of doubt, NVIDIAs rights pursuant to this section shall survive any termination of the Agreement with respect to \napplications which incorporate the NVIDIA RTX SDK.\n7.2 Trademark Ownership and Licenses. Trademarks are owned and licenses as follows:\n(a)\tOwnership of Trademarks.  Each party owns the trademarks, logos, and trade names (collectively \"Marks\") for their \nrespective products or services, including without limitation in applications, and the NVIDIA RTX SDKs. Each party agrees to \nuse the Marks of the other only as permitted in this exhibit.\n\n(b)\tTrademark License to NVIDIA.  You grant to NVIDIA a non-exclusive, non-sub licensable, non-transferable (except as set \nforth in the assignment provision of the Agreement), worldwide license to refer to you and your applications, and to use \nyour Marks on NVIDIA's marketing materials and on NVIDIA's website (subject to any reasonable conditions of you) solely \nfor NVIDIAs marketing activities set forth in this exhibit Sections (d)-(e) above.  NVIDIA will follow your specifications for \nyour Marks as to style, color, and typeface as reasonably provided to NVIDIA.\n\n(c)\tTrademark License to You.  NVIDIA grants to you a non-exclusive, non-sub licensable, non-transferable (except as set \nforth in the assignment provision of the Agreement), worldwide license, subject to the terms of this exhibit and the \nAgreement, to use NVIDIA RTX, NVIDIA GeForce RTX in combination with GeForce products, and/or NVIDIA Quadro \nRTX  in combination with Quadro products (collectively, the NVIDIA Marks) on your marketing materials and on your \nwebsite (subject to any reasonable conditions of NVIDIA) solely for your marketing activities set forth in this exhibit Sections \n6.1 (a)-(c) above.  For the avoidance of doubt, you will not and will not permit others to use any NVIDIA Mark for any other \ngoods or services, or in a way that tarnishes, degrades, disparages or reflects adversely any of the NVIDIA Marks or NVIDIAs \nbusiness or reputation, or that dilutes or otherwise harms the value, reputation or distinctiveness of or NVIDIAs goodwill in \nany NVIDIA Mark. In addition to the termination rights set forth in the Agreement, NVIDIA may terminate this trademark \nlicense at any time upon written notice to you. You will follow NVIDIA's use guidelines and specifications for NVIDIA's Marks \nas to style, color and typeface as provided in NVIDIA Marks and submit a sample of each proposed use of NVIDIA's Marks at \nleast two (2) weeks prior to the desired implementation of such use to obtain NVIDIA's prior written approval (which \napproval will not be unreasonably withheld or delayed).  If NVIDIA does not respond within ten (10) business days of your \nsubmission of such sample, the sample will be deemed unapproved. All goodwill associated with use of NVIDIA Marks will \ninure to the sole benefit of NVIDIA. For the video SDK, contact NVIDIA at nvidia-rtx-video-sdk-license-\nquestions@nvidia.com.\n\n7.3 Use Guidelines. Use of the NVIDIA Marks is subject to the following guidelines:\n(a)\tBusiness Practices.  You covenant that you will: (a)  conduct business with respect to NVIDIAs products in a manner that \nreflects favorably at all times on the good name, goodwill and reputation of such products; (b) avoid deceptive, misleading \nor unethical practices that are detrimental to NVIDIA, its customers, or end users; (c) make no false or misleading \nrepresentations with regard to NVIDIA or its products; and (d) not publish or employ or cooperate in the publication or \nemployment of any misleading or deceptive advertising or promotional materials.\n\n(b)\tNo Combination Marks or Similar Marks.  You agree not to (a) combine NVIDIA Marks with any other content without \nNVIDIAs prior written approval, or (b) use any other trademark, trade name, or other designation of source which creates a \nlikelihood of confusion with NVIDIA Marks.\n\n(v. February 23, 2024)\n\n"
  },
  {
    "path": "README.md",
    "content": "# RTX Mega Geometry\n\n![Title](./docs/images/banner.jpg)\n\n<br/>\n<div align=\"center\">\n·\n<a href=\"CHANGELOG.md\">Change Log </a>\n·\n<a href=\"docs/QuickStart.md\">Quick Start</a>\n·\n</div>\n<br/>\n\n## Overview\n\nRTX Mega Geometry (RTX MG) is a DX12 and Vulkan code sample that shows how to quickly\nbuild ray-tracing acceleration structures for subdivision surfaces with structured\nclusters. It contains a reference HLSL path tracing demo app that can be used as a\nlearning tool to begin integration with your own graphics engine.\n\n## Features\n\n* Real-time adaptive sampling of Catmull-Clark limit surfaces\n* Structured cluster tessellation and displacement mapping\n* BVH build with cluster templates using NVAPI for DXR, and the VK_NV_cluster_acceleration_structure extension for Vulkan.\n* Hierarchical Z-buffer for reducing tessellation rate of non-primary ray geometry\n* DLSS Ray Reconstruction denoising\n\n## Requirements\n\nTo Run:\n- Windows 10\n- NVIDIA RTX GPU (10 GB VRAM or greater)\n- GeForce Game Ready Driver 570 or later\n- DirectX Raytracing 1.1 API or later\n\nTo Build:\n- CMake v3.31+\n- Windows 10 SDK 10.0.20348.0 or later\n- MSVC Compiler 19.43.34810 (Visual Studio 2022 17.13) or later\n- For Windows: DirectX 12 AgilitySDK will be fetched automatically\n- For Vulkan: Vulkan SDK 1.4.313 or later\n\n## Folder Structure\n\n|           |                                                                      |\n| -\t        | -                                                                    |\n| /bin      | default CMake folder for binaries and compiled shaders               |\n| /build    | default CMake folder for build files                                 |\n| /external\t| external submoduled libraries and SDKs, including osd_lite and donut |\n| /assets   | models, textures, scene files (git submodule)                        |\n| /rtxmg    | **RTX Mega Geometry core**                                           |\n\n## Build\n\nAt the moment, only Windows builds are supported.\n\n1. Clone the repository **with all submodules recursively**:\n   \n   `git clone --recurse-submodules https://github.com/NVIDIA-RTX/rtxmg.git`\n\n2. Use CMake to configure the build and generate the project files.\n   \n   ```\n   cd rtxmg\n   cmake CMakeLists.txt -B ./build\n   ```\n\n   Use `-G \"some tested VS version\"` if specific Visual Studio or other environment \n   version required. Make sure the x64 platform is used. \n\n3. Build the solution generated by CMake in the `./build/` folder.\n\n   For example, if using Visual Studio, open the generated solution `build/rtxmg.sln` \n   and build it.\n\n4. Select and run the `rtxmg_demo` project. Binaries get built to the `bin` folder. \n   Media assets are loaded from the `assets` folder.\n\n\n ## User Interface\n\nOnce the application is running, most of the SDK features can be accessed via the\nUI window on the left hand side. The UI is self-documenting : hover the mouse over\nwidgets to read tool-tips. See the <a href=\"docs/QuickStart.md\">Quick Start guide</a>\nfor more details.\n\nCamera can be moved using W/S/A/D keys and rotated by dragging with the left mouse\ncursor.  Holding the Alt key and left-click dragging will orbit the camera around\nthe scene's center.\n\n## Command Line\n\n- `-mf` loads a specific .scene.json or OBJ file; \n  example: `-mf programmer-art.scene.json`\n- `-d` to enable the graphics API debug layer or runtime, \n  and the [NVRHI](https://github.com/NVIDIA-RTX/NVRHI) validation layer.\n \nAdditional command line flags can be found by running with the `-h` flag.\n\n## Contact\n\nRTX Mega Geometry is under active development. Please report any issues directly\nthrough GitHub issue tracker, and for any information, suggestions or general \nrequests please feel free to contact us at rtxmg-sdk-support@nvidia.com\n\n## Related RTX Mega Geometry Repositories\n\nVulkan\n * [vk_animated_clusters](https://github.com/nvpro-samples/vk_animated_clusters) :\n   `VK_NV_cluster_acceleration_structure` for animated content.\n * [vk_tessellated_clusters](https://github.com/nvpro-samples/vk_tessellated_clusters) :\n   adaptive triangle tessellation and displacement using `VK_NV_cluster_acceleration_structure`\n * [vk_lod_clusters](https://github.com/nvpro-samples/vk_lod_clusters) :\n   cluster-lod system and streaming using `VK_NV_cluster_acceleration_structure`\n * [vk_partitioned_tlas](https://github.com/nvpro-samples/vk_partitioned_tlas)\n   update the TLAS of large dynamic scenes with `VK_NV_partitioned_acceleration_structure`\n\nTools\n * [meshoptimizer.org](https://meshoptimizer.org) - meshoptimizer provides ray tracing friendly clusterization through `meshopt_buildMeshletsSpatial`. If you are interested in building a continuous level of detail system, have a look at its clusterlod.h demo, it shows the necessary steps.\n\n## Citation\nIf you use RTX Mega Geometry in a research project leading to a publication, \nplease cite the project.\n\nBibTex:\n```bibtex\n@online{RTX MG,\n   title   = {{{NVIDIA}}\\textregistered{} {RTX Mega Geometry}},\n   author  = {{NVIDIA}},\n   year    = 2025,\n   url     = {https://github.com/NVIDIA-RTX/rtxmg.git},\n   urldate = {2025-02-06},\n}\n```\n\n## Known Issues\n\n1. The RTX MG SDK does not currently support unstructured clusters or rasterization.\n\n## License\n\nSee [LICENSE.txt](LICENSE.txt)\n\nThis project includes NVAPI software. All uses of NVAPI software are governed by the license terms specified here: https://github.com/NVIDIA/nvapi/blob/main/License.txt.\n"
  },
  {
    "path": "demo/CMakeLists.txt",
    "content": "# include (\"private.cmake\" OPTIONAL)\n\ninclude (\"${DONUT_DIR}/compileshaders.cmake\")\n\nadd_subdirectory(audio)\n\n\nfile(GLOB shaders \"shaders/*\")\nfile(GLOB sources \"*.cpp\" \"*.h\" \"osd_port_sources/tmr/*.h\" *.cfg)\n\nif(NOT EXISTS \"${PROJECT_SOURCE_DIR}/CMake/FetchImplot.cmake\")\n    message(FATAL_ERROR \"Missing FetchImplot.cmake file\")\nendif()\ninclude(\"${PROJECT_SOURCE_DIR}/CMake/FetchImplot.cmake\")\nset_target_properties(implot PROPERTIES FOLDER \"Third-Party Libraries\")\n\nset(app rtxmg_demo)\nset(folder \"Demo\")\n\nlist(APPEND RTXMG_SHADERS_INCLUDE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\")\n\ndonut_compile_shaders_all_platforms(\n    TARGET ${app}_shaders\n    CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/shaders.cfg\n    FOLDER ${folder}\n    SOURCES ${shaders}\n    OUTPUT_BASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders/${app}\n    SHADERMAKE_OPTIONS ${RTXMG_SHADERMAKE_OPTIONS}\n    SHADERMAKE_OPTIONS_SPIRV ${RTXMG_SHADERMAKE_OPTIONS_SPIRV}\n    SHADER_MODEL ${RTXMG_SHADERS_SHADERMODEL}\n    IGNORE_INCLUDES ${RTXMG_SHADERS_IGNORED_INCLUDES}\n    INCLUDES ${RTXMG_SHADERS_INCLUDE_DIR}\n)\n\nadd_executable(${app} \n    ${sources} \n)\ntarget_link_libraries(${app} PUBLIC envmap rtxmg donut_render donut_app donut_engine implot)\n\noption(RTXMG_DEV_FEATURES \"Enable development features (debugging, etc)\" OFF)\nif(RTXMG_DEV_FEATURES)\n    target_compile_definitions(${app} PUBLIC RTXMG_DEV_FEATURES=1)\nendif()\ntarget_compile_definitions(${app} PUBLIC RTXMG_VERSION=\"${PROJECT_VERSION}\")\n\nif(ENABLE_AUDIO_ENGINE)\n    if(TARGET audio_engine)\n        target_link_libraries(${app} PRIVATE audio_engine)\n    else()\n        message(WARNING \"Audio engine option is enabled, but configuration failed\")\n    endif()\nendif()\n\nadd_dependencies(${app} ${app}_shaders)\nset_target_properties(${app} PROPERTIES FOLDER ${folder})\nadd_compile_definitions( \"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING=1\")\n\ntarget_include_directories(${app} PUBLIC \n    ${PROJECT_SOURCE_DIR} \n    ${PROJECT_SOURCE_DIR}/extern\n)\n\nadd_subdirectory(envmap)\n\nif(DONUT_D3D_AGILITY_SDK_ENABLED)\n    target_compile_definitions(${app} PUBLIC DONUT_D3D_AGILITY_SDK_ENABLED=1)\n    target_compile_definitions(${app} PUBLIC DONUT_D3D_AGILITY_SDK_VERSION=${DONUT_D3D_AGILITY_SDK_VERSION})\n    add_custom_command(TARGET ${app} POST_BUILD\n        COMMAND \n            ${CMAKE_COMMAND} -E make_directory \"$<TARGET_FILE_DIR:${app}>/D3D12/\"\n        COMMAND \n            ${CMAKE_COMMAND} -E copy_if_different ${DONUT_D3D_AGILITY_SDK_CORE_DLL} \"$<TARGET_FILE_DIR:${app}>/D3D12/\"\n    )\nendif()\n\nif(WIN32)\n    add_custom_command( TARGET ${app} POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:${app}> $<TARGET_FILE_DIR:${app}>\n        COMMAND_EXPAND_LISTS)\nendif()\n\n#\n# Installation & packaging\n#\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)\n    set(CMAKE_INSTALL_PREFIX \"${CMAKE_SOURCE_DIR}/_install\" CACHE PATH \"Installation directory\" FORCE)\nendif()\n\ninstall(TARGETS ${app} DESTINATION \"bin\")\n\ninstall(DIRECTORY \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders\" DESTINATION \"bin\" )\n\nif(WIN32)\n    install(FILES $<TARGET_RUNTIME_DLLS:${app}> DESTINATION \"bin\")\n\n    # generate some shortcuts to run various scenes\n    file(WRITE \"${CMAKE_CURRENT_BINARY_DIR}/run_rtxmg.bat\" \"start \\\"\\\" bin/rtxmg_demo.exe\")\n    install(FILES \"${CMAKE_CURRENT_BINARY_DIR}/run_rtxmg.bat\" DESTINATION \".\")\n\n    file(WRITE \"${CMAKE_CURRENT_BINARY_DIR}/run_amy_kitchenset.bat\" \"start \\\"\\\" bin/rtxmg_demo.exe -mf amy_kitchenset.scene.json\")\n    install(FILES \"${CMAKE_CURRENT_BINARY_DIR}/run_amy_kitchenset.bat\" DESTINATION \".\")\n\n    file(WRITE \"${CMAKE_CURRENT_BINARY_DIR}/run_barbarian.bat\" \"start \\\"\\\" bin/rtxmg_demo.exe -mf barbarian_pt.scene.json\")\n    install(FILES \"${CMAKE_CURRENT_BINARY_DIR}/run_barbarian.bat\" DESTINATION \".\")\n\n    set(_os \"win64\")\nelseif (UNIX AND NOT APPLE)\n    set(_os \"linux64\")\nendif()\n\nif(DONUT_D3D_AGILITY_SDK_ENABLED)\n    install(DIRECTORY \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/D3D12\" DESTINATION \"bin\" )\nendif()\n\nif(IS_DIRECTORY \"${PROJECT_SOURCE_DIR}/assets\")\n    install(\n        DIRECTORY \"${PROJECT_SOURCE_DIR}/assets\" \n        DESTINATION \".\" \n        PATTERN \"*.bin\" EXCLUDE)\nendif()\n\nset(CPACK_GENERATOR \"ZIP\")\nset(CPACK_PACKAGE_FILE_NAME \"${CMAKE_PROJECT_NAME}.${PROJECT_VERSION}.${_os}\")\n\ninclude(CPack)\n"
  },
  {
    "path": "demo/args.cpp",
    "content": "//\n// Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#include \"args.h\"\n#include \"rtxmg_demo.h\"\n\n#include <assert.h>\n#include <cstdio>\n#include <functional>\n#include <map>\n#include <numbers>\n#include <stdexcept>\n\n#include \"rtxmg/scene/json.h\"\n\n// clang-format on\n\nstatic void printUsageAndExit(const char* argv0,\n    const std::string& token = {})\n{\n    // clang-format off\n\n    static char const* msg =\n        \"Usage  : &s [options]\\n\"\n        \"Options: \\n\"\n        \"  -h                      | --help                  Print this usage message\\n\"\n        \"  -at <mode>              | --adaptiveTessellation  Mode is: [uniform | world | sphere]\\n\"\n        \"                          | --aftermath             Enable Aftermath for GPU Crash debugging (not compatible with --debug)\"\n        \"  -a <true|false>         | --amplification         Tessellation amplification (default true).\\n\"\n        \"  -bbt <t|c>              | --bvhBuildType          [t (triangles) | c (clusters)]\\n\"\n        \"  -cc <mode>              | --clusterColor          Mode is: [base | level (isolation) | uv | bid (block id) | tid (tri id) | \\n\"\n        \"                                                    cid (cluster id) | n (normal) | bNum (# blocks) | tArea (utri area)]\\n\"\n        \"  -ctr <f>                | --coarseTessellationRate Coarse adaptive edge sampling rate\\n\"\n        \"  -d                      | --debug                 Enable D3D12 Debug Layer\\n\"\n        \"  -gd                     | --gpudebug              Enable D3D12 GPU Validation\\n\"\n        \"  -ds <float>             | --displacementScale     Displacement scale along normal (relative to the largest extent of the object's AABB)\\n\"\n        \"  -ed <true|false>        | --enableDenoiser        Enable Denoiser\\n\"\n        \"  -envmap <envmap.exr>    |                         Specify the environment map to use\\n\"\n        \"  -es <e0> <e1> <e2> <e3> | --edgeSegments          Cluster edge m_size for all four edges (default 5 5 5 5)\\n\"\n        \"  -f <filename>           | --file                  Specify file for image output\\n\"\n        \"  -ftr <f>                | --fineTessellationRate  Fine adaptive edge sampling rate\\n\"\n        \"  -isoLevelSharp <int>                              Max isolation level near sharp features such as creases (default 6)\\n\"\n        \"  -isoLevelSmooth <int>                             Max isolation level near smooth features such as extraordinary vertices (default 3)\\n\"\n        \"  -ll <n>                 | --loglevel              Set logCallbackLevel (default: 2)\\n\"\n        \"                          | --logAccelBuild         Log each buffer of the acceleration build (slow!)\\n\"\n        \"                          | --maxClusters           Memory budget Set the max num of clustersverts\\n\"\n        \"                          | --maxVerts              Memory budget Set the max num of verts\\n\"\n        \"                          | --maxClasMB             Memory budget Set the max num of clas memory in MB\\n\"\n        \"  -mrl <int>              | --maxRefinementLevel    Legacy: same as isoLevelSharp\\n\"\n        \"  -mc <r> <g> <b>         | --missColor             Miss color\\n\"\n        \"  -mf <filepath>          | --meshInputFile         Read .obj or scene file\\n\"\n        \"  -p \\\"[eye][at][up]fov\\\"   | --cameraPos             Camera pose\\n\"\n        \"  -ptmb <n>               | --ptMaxBounces          Max PT bounces\\n\"\n        \"  -qbits <n>              | --quantizeBits          Quantize vertex positions by n bits [0,32)\\n\"\n        \"  -qf <true|false>        | --quadFiltering         Filter out 1x1 clusters during metric tessellation phase\\n\"\n        \"  -res <w> <h>            | --resolution            Set image dimensions to <w>x<h> (default 768 768)\\n\"\n        \"  -sm [prim_rays|ao|pt]   | --shadingMode           primary rays or AO or path tracing\\n\"\n        \"  -spp <n>                                          Number of samples per pixel (need to use a perfect square number, default 1)\\n\"\n        \"  -tv <true|false>        | --timeview              Enable Timeview (default false)\\n\"\n        \"  -vn <true|false>        | --vertexNormals         Enable vertex normals computation (default false)\\n\"\n        \"  -wf <true|false>        | --wireframe             Set wireframe (default true)\\n\"\n        \"  -wm                     | --windowMaximized       Start window maximized (default false)\\n\";\n\n    // clang-format on\n\n    std::fprintf(stderr, msg, argv0);\n\n    if (token.find(\"Unknown option\") != std::string::npos)\n        std::fprintf(stderr, \"\\n****** %s ******\", token.c_str());\n    else if (!token.empty())\n        std::fprintf(stderr, \"\\n****** Invalid usage of '%s' ******\",\n            token.c_str());\n    exit(1);\n}\n\nstatic bool parseBooleanArg(char const* token,\n    char const* value, bool defaultValue)\n{\n    if (std::strncmp(value, \"true\", 4) == 0)\n        return true;\n    if (std::strncmp(value, \"false\", 5) == 0)\n        return false;\n    printUsageAndExit(\"rtxmg_demo\", token);\n    return defaultValue;\n}\n\nvoid Args::Parse(int argc, char const* const* argv)\n{\n    static std::map<std::string, ShadingMode> shadingModes{\n        {\"prim_rays\", ShadingMode::PRIMARY_RAYS},\n        {\"ao\", ShadingMode::AO},\n        {\"pt\", ShadingMode::PT} };\n\n    static std::map<std::string, ColorMode> colorModes{\n        {\"base\", ColorMode::BASE_COLOR},\n        {\"uv\", ColorMode::COLOR_BY_CLUSTER_UV},        \n        {\"cid\", ColorMode::COLOR_BY_CLUSTER_ID},\n        {\"tid\", ColorMode::COLOR_BY_MICROTRI_ID},\n        {\"n\", ColorMode::COLOR_BY_NORMAL},\n        {\"texcoord\", ColorMode::COLOR_BY_TEXCOORD},\n        {\"mat\", ColorMode::COLOR_BY_MATERIAL},\n        {\"tArea\", ColorMode::COLOR_BY_MICROTRI_AREA} };\n\n    static const std::map<std::string, TextureType> TextureTypes = {\n        { \"-envmap\", TextureType::ENVMAP }\n    };\n\n    static std::map <std::string, TessellatorConfig::AdaptiveTessellationMode> adaptiveTessellationModes = {\n        {\"uniform\", TessellatorConfig::AdaptiveTessellationMode::UNIFORM},\n        {\"sphere\", TessellatorConfig::AdaptiveTessellationMode::SPHERICAL_PROJECTION},\n        {\"world\", TessellatorConfig::AdaptiveTessellationMode::WORLD_SPACE_EDGE_LENGTH}\n    };\n\n    auto parseEnum = [&argv]<typename T>(std::string const& arg, std::map<std::string, T> const& enumModes)\n    {\n        if (const auto it = enumModes.find(arg); it != enumModes.end())\n            return it->second;\n        else\n            printUsageAndExit(argv[0], arg);\n        return T(0);\n    };\n\n    for (int i = 1; i < argc; ++i)\n    {\n        const std::string arg(argv[i]);\n\n        if (arg == \"-d3d12\" || arg == \"-dx12\" || arg == \"--d3d12\" || arg == \"--dx12\")\n            continue;\n        \n        if (arg == \"-vk\" || arg == \"-vulkan\" || arg == \"--vk\" || arg == \"--vulkan\")\n            continue;\n\n        auto parseArgValues = [&argc, &argv, &i, &arg](int n,\n            std::function<void()> func)\n            {\n                if (i >= argc - n)\n                    printUsageAndExit(argv[0], argv[i]);\n                func();\n            };\n\n        if (arg == \"--debug\" || arg == \"-d\")\n        {\n            debug = true;\n        }\n        else if (arg == \"--gpudebug\" || arg == \"-gd\")\n        {\n            gpuValidation = true;\n        }\n        else if (arg == \"--aftermath\")\n        {\n            aftermath = true;\n        }\n        else if (arg == \"--sllog\")\n        {\n            enableStreamlineLog = true;\n        }\n        else if (arg == \"--help\" || arg == \"-h\")\n        {\n            printUsageAndExit(argv[0]);\n        }\n        else if (arg == \"--file\" || arg == \"-f\")\n        {\n            parseArgValues(1, [&]() { outfile = argv[++i]; });\n        }\n        else if (arg == \"--timeview\" || arg == \"-tv\")\n        {\n            parseArgValues(1, [&]() { enableTimeView = parseBooleanArg(arg.c_str(), argv[++i], enableTimeView); });\n        }\n        else if (arg == \"-p\" || arg == \"--cameraPos\")\n        {\n            parseArgValues(1, [&]() { camString = std::string(argv[++i]); });\n        }\n        else if (arg == \"-res\" || arg == \"--resolution\")\n        {\n            parseArgValues(2, [&]()\n                {\n                    width = atoi(argv[++i]);\n                    height = atoi(argv[++i]);\n                });\n            resolutionSetByCmdLine = true;\n        }\n        else if (arg == \"--wireframe\" || arg == \"-wf\")\n        {\n            parseArgValues(1, [&]()\n                {\n                    enableWireframe =\n                        parseBooleanArg(arg.c_str(), argv[++i], enableWireframe);\n                });\n        }\n        else if (arg == \"--displacementScale\" || arg == \"-ds\")\n        {\n            parseArgValues(1, [&]() { dispScale = (float)atof(argv[++i]); });\n        }\n        else if (arg == \"--adaptiveTessellation\" || arg == \"-at\")\n        {\n            parseArgValues(\n                1, [&]() { tessMode = parseEnum(argv[++i], adaptiveTessellationModes); });\n        }\n        else if (arg == \"--edgeSegments\" || arg == \"-es\")\n        {\n            parseArgValues(4, [&]()\n            {\n                edgeSegments.x = static_cast<uint32_t>(atoi(argv[++i]));\n                edgeSegments.y = static_cast<uint32_t>(atoi(argv[++i]));\n                edgeSegments.z = static_cast<uint32_t>(atoi(argv[++i]));\n                edgeSegments.w = static_cast<uint32_t>(atoi(argv[++i]));\n            });\n        }\n        else if (arg == \"--missColor\" || arg == \"-mc\")\n        {\n            parseArgValues(3, [&]()\n                {\n                    missColor = { (float)atof(argv[++i]), (float)atof(argv[++i]),\n                                 (float)atof(argv[++i]) };\n                });\n        }\n        else if (arg == \"--ptMaxBounces\" || arg == \"-ptmb\")\n        {\n            parseArgValues(1, [&]() { ptMaxBounces = atoi(argv[++i]); });\n        }\n        else if (arg == \"--meshInputFile\" || arg == \"-mf\")\n        {\n            parseArgValues(1, [&]() { meshInputFile = argv[++i]; });\n        }\n        else if (arg == \"--maxRefinementLevel\" || arg == \"-mrl\" ||\n            arg == \"-isoLevelSharp\")\n        {\n            parseArgValues(1, [&]() { isoLevelSharp = atoi(argv[++i]); });\n        }\n        else if (arg == \"-isoLevelSmooth\")\n        {\n            parseArgValues(1, [&]() { isoLevelSmooth = atoi(argv[++i]); });\n        }\n        else if (arg == \"-envmap\")\n        {\n            try\n            {\n                textures[parseEnum(arg, TextureTypes)] = argv[++i];\n            }\n            catch (std::exception& e)\n            {\n                std::fprintf(stderr, \"Invalid option in '%s %s' : %s\\n\", arg.c_str(), argv[i], e.what());\n                exit(1);\n            }\n        }\n        else if (arg == \"--shadingMode\" || arg == \"-sm\")\n        {\n            parseArgValues(\n                1, [&]() { shadingMode = parseEnum(argv[++i], shadingModes); });\n        }\n        else if (arg == \"-spp\")\n        {\n            parseArgValues(1, [&]()\n                {\n                    spp = atoi(argv[++i]);\n                    const int sqrt_spp =\n                        static_cast<int>(std::sqrt(static_cast<float>(spp)));\n                    if (sqrt_spp * sqrt_spp !=\n                        spp) // check if spp is a perfect sqare number\n                        printUsageAndExit(argv[0], arg);\n                });\n        }\n        else if (arg == \"--logAccelBuild\")\n        {\n            enableAccelBuildLogging = true;\n        }\n        else if (arg == \"--vertBufferMB\")\n        {\n            parseArgValues(1, [&]() { tessMemorySettings.vertexBufferBytes = size_t(atoi(argv[++i])) << 20ull; });\n        }\n        else if (arg == \"--maxClusters\")\n        {\n            parseArgValues(1, [&]() { tessMemorySettings.maxClusters = atoi(argv[++i]); });\n        }\n        else if (arg == \"--clasBufferMB\")\n        {\n            parseArgValues(1, [&]() { tessMemorySettings.clasBufferBytes = size_t(atoi(argv[++i])) << 20ull; });\n        }\n        else if (arg == \"--fineTessellationRate\" || arg == \"-ftr\")\n        {\n            parseArgValues(1, [&]() { fineTessellationRate = (float)atof(argv[++i]); });\n        }\n        else if (arg == \"--coarseTessellationRate\" || arg == \"-ctr\")\n        {\n            parseArgValues(1, [&]() { coarseTessellationRate = (float)atof(argv[++i]); });\n        }\n        else if (arg == \"--enableDenoiser\" || arg == \"-ed\")\n        {\n            enableDenoiser = true;\n        }\n        else if (arg == \"--windowMaximized\" || arg == \"-wm\")\n        {\n            startMaximized = true;\n        }\n        else if (arg == \"--vertexNormals\" || arg == \"-vn\")\n        {\n            parseArgValues(1, [&]() { enableVertexNormals = parseBooleanArg(arg.c_str(), argv[++i], enableVertexNormals); });\n        }\n        else\n            printUsageAndExit(argv[0], std::string(\"Unknown option: \") + argv[i]);\n    }\n}\n\n\nauto parseJsonEnum = []<typename T, size_t N>(const Json::Value & node,\n    const std::array<const char*, N> &enums, T & result) constexpr\n{\n    uint8_t index = 0;\n    for (const char* e : enums)\n    {\n        if (std::strncmp(node.asString().c_str(), e, std::strlen(e)) == 0)\n        {\n            result = T(index);\n            break;\n        }\n        ++index;\n    }\n};\n\nArgs& operator << (Args& args, const Json::Value& node)\n{\n    if (const auto& value = node[\"envmap rotation\"]; value.isDouble())\n    {\n        value >> args.envmapAzimuth;\n        args.envmapAzimuth = (args.envmapAzimuth / 180.f) * float(std::numbers::pi);\n    }\n\n    if (const auto& value = node[\"envmap elevation\"]; value.isDouble())\n    {\n        value >> args.envmapElevation;\n        args.envmapElevation = (args.envmapElevation / 180.f) * float(std::numbers::pi);\n    }\n    if (const auto& value = node[\"envmap intensity\"]; value.isDouble())\n    {\n        value >> args.envmapIntensity;\n    }\n    if (const auto& value = node[\"shading mode\"]; value.isString())\n        parseJsonEnum(value, kShadingModeNames, args.shadingMode);\n\n    if (const auto& value = node[\"color mode\"]; value.isString())\n    {\n        parseJsonEnum(value, kColorModeNames, args.colorMode);\n    }\n\n    if (const auto& value = node[\"spp\"]; value.isIntegral())\n        value >> args.spp;\n    if (const auto& value = node[\"max bounces\"]; value.isIntegral())\n        value >> args.ptMaxBounces;\n\n    if (const auto& value = node[\"firefly max intensity\"]; value.isDouble())\n        value >> args.firefliesClamp;\n\n    if (const auto& value = node[\"exposure\"]; value.isDouble())\n        value >> args.exposure;\n\n    if (const auto& value = node[\"tonemap operator\"]; value.isString())\n        parseJsonEnum(value, kToneMapOperatorNames, args.tonemapOperator);\n\n    // displacement\n    if (const auto& value = node[\"displacementScale\"]; value.isDouble())\n        value >> args.dispScale;\n\n    if (const auto& value = node[\"displacementBias\"]; value.isDouble())\n    {\n        value >> args.dispBias;\n        donut::log::warning(\"Scene arg displacementBias is not supported, ignoring\");\n    }\n\n    if (const auto& value = node[\"wireframe\"]; value.isBool())\n        value >> args.enableWireframe;\n\n    if (const auto& value = node[\"wireframe thickness\"]; value.isDouble())\n        value >> args.wireframeThickness;\n\n    if (const auto& value = node[\"enableDenoiser\"]; value.isBool())\n        value >> args.enableDenoiser;\n\n    return args;\n}"
  },
  {
    "path": "demo/args.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n// clang-format off\n\n#pragma once\n\n#include \"rtxmg_demo.h\"\n#include \"rtxmg/cluster_builder/tessellator_config.h\"\n#include \"rtxmg/scene/scene.h\"\n\n#include <array>\n#include <string>\n#include <json/json.h>\n\n#include <donut/core/math/math.h>\n\nusing namespace donut::math;\n\n// clang-format on\n\n// All state & values controllable from the command-line\n// used to initialize the application on launch.\n//\n// note: much of this state should be broken up into sub-sections\n// note: currently the application owns this struct and can override\n//       some of its values interactively (which is less than ideal).\n\n// Things that can be modified by a scene.json file\nstruct SceneArgs\n{\n    std::array<std::string, TextureType::TEXTURE_TYPE_COUNT> textures;\n\n    float envmapAzimuth = 0.f;\n    float envmapElevation = 0.f;\n    float envmapIntensity = 1.f;\n\n    ColorMode colorMode = ColorMode::BASE_COLOR;\n    ShadingMode shadingMode = ShadingMode::PT;\n    TonemapOperator tonemapOperator = TonemapOperator::Aces;\n\n    float wireframeThickness = .1f;\n\n    int spp = 1;\n    int ptMaxBounces = 2;\n\n    float firefliesClamp = 0.f;\n    float exposure = 1.f;\n\n    float dispScale = 1.f;\n    float dispBias = 0.f;\n\n    float roughnessOverride = 0.f;\n\n    bool enableWireframe = false;\n    bool enableDenoiser = true;\n};\n\nstruct Args : SceneArgs\n{\n    // convenience accessors for legacy code\n    using SceneArgs::colorMode;\n    using SceneArgs::dispBias;\n    using SceneArgs::dispScale;\n    using SceneArgs::enableWireframe;\n    using SceneArgs::envmapAzimuth;\n    using SceneArgs::envmapElevation;\n    using SceneArgs::envmapIntensity;\n    using SceneArgs::exposure;\n    using SceneArgs::firefliesClamp;\n    using SceneArgs::ptMaxBounces;\n    using SceneArgs::shadingMode;\n    using SceneArgs::spp;\n    using SceneArgs::wireframeThickness;\n\n    SceneArgs& sceneArgs() { return *this; }\n\n    int width = 1920;\n    int height = 1080;\n    bool resolutionSetByCmdLine = false;\n\n    std::string outfile;\n    std::string meshInputFile = std::string{};\n    std::string camString;\n\n    uint4 edgeSegments = { 8, 8, 8, 8 };\n\n    unsigned char quantNBits{ 0 };\n\n    bool enableFrustumVisibility = true;\n    bool enableBackfaceVisibility = true;\n    bool enableHiZVisibility = true;\n    bool updateTessCamera = true;\n    bool enableVertexNormals = false;\n\n    TessellatorConfig::MemorySettings tessMemorySettings;\n    TessellatorConfig::VisibilityMode visMode = TessellatorConfig::VisibilityMode::VIS_LIMIT_EDGES;\n    TessellatorConfig::AdaptiveTessellationMode tessMode = TessellatorConfig::AdaptiveTessellationMode::SPHERICAL_PROJECTION;\n\n    // Note: the defaults here are intended for TMR\n    int isoLevelSharp = 6;\n    int isoLevelSmooth = 3;\n    uint32_t globalIsolationLevel = TessellatorConfig::kMaxIsolationLevel;\n\n    float fineTessellationRate = TessellatorConfig::kDefaultFineTessellationRate;\n    float coarseTessellationRate = TessellatorConfig::kDefaultCoarseTessellationRate;\n    ClusterPattern clusterPattern = ClusterPattern::SLANTED;\n        \n    float3 missColor = { .75, .75, .75 };\n\n    bool debug = false;\n    bool gpuValidation = false;\n    bool aftermath = false;\n    bool enableStreamlineLog = false;    \n    bool enableAccelBuildLogging = false;\n    bool enableTimeView = false;\n    bool startMaximized = false;\n\n    void Parse(int argc, char const* const* argv);\n};\nArgs& operator << (Args& args, const Json::Value& node);\n"
  },
  {
    "path": "demo/audio/CMakeLists.txt",
    "content": "\noption(ENABLE_AUDIO_ENGINE \"Enable audio playback\" ON)\n\nif (ENABLE_AUDIO_ENGINE)\n\n    set(_audio_src_files\n        audio.cpp\n        audio.h\n        waveFile.cpp\n        waveFile.h\n        xaudiofaudio.h)\n\n    if (UNIX)\n\n        # Linux builds emulate XAudio with the FAudio library ; typical installation with\n        # 'sudo apt install libfaudio-dev' (same on Windows Subsystem for Linux)\n\n        find_path(_faudio_include_dir FAudio.h HINTS \"/usr/include\")\n        find_library(_faudio_lib libFAudio.so HINTS \"/usr/lib/\" \"/usr/lib/x86_64-linux-gnu/\" )\n\n        if (_faudio_include_dir AND _faudio_lib)\n\n            add_library(FAudio INTERFACE)\n\n            target_include_directories(FAudio INTERFACE ${_faudio_include_dir})\n            target_link_libraries(FAudio INTERFACE ${_faudio_lib})\n            target_compile_definitions(FAudio INTERFACE AUDIO_ENGINE_XAUDIO_ON_FAUDIO)\n\n            message(STATUS \"Found FAudio: ${_faudio_include_dir}\")\n        endif()\n\n        if (NOT TARGET FAudio)\n            message(WARNING \n                \"Audio engine is enabled but missing libfaudio-dev (disabling audio). \"\n                \"Set ENABLE_AUDIO_ENGINE=OFF to skip this warning.\")\n            return()\n        endif()\n        \n    endif()\n   \n    add_library(audio_engine STATIC ${_audio_src_files})\n\n    target_include_directories(audio_engine PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/..\")\n    target_compile_definitions(audio_engine PUBLIC AUDIO_ENGINE_ENABLED)\n\n    if (TARGET FAudio)\n        target_link_libraries(audio_engine FAudio)\n    endif()\n\n    if (MSVC)\n        target_compile_definitions(audio_engine PUBLIC AUDIO_ENGINE_WITH_XAUDIO)\n    endif()\n\n    set_target_properties(audio_engine PROPERTIES FOLDER Demo)\n\nendif()\n"
  },
  {
    "path": "demo/audio/audio.cpp",
    "content": "// clang-format off\n#include \"./audio.h\"\n#include \"./waveFile.h\"\n\n#include <cstring>\n\n// clang-format on\n\n#ifndef SAFE_RELEASE\n    #define SAFE_RELEASE(x) \\\n       if(x != NULL)        \\\n       {                    \\\n          x->Release();     \\\n          x = NULL;         \\\n       }\n#endif\n\nnamespace audio {\n\nstatic std::vector<std::string> _errors;\n\nstd::unique_ptr<Voice> Voice::create(Engine& engine, std::filesystem::path const& m_filepath, bool loop) {\n\n    if (!engine.isReady())\n        return nullptr;\n\n    if (auto wave = WaveFile::read(m_filepath))\n        return Voice::create(engine, std::move(wave), loop);\n    else\n        engine.error((std::string(\"cannpt read: \")+ m_filepath.generic_string()).c_str());\n    return nullptr;\n}\n\nstd::unique_ptr<Voice> Voice::create(Engine& engine, std::shared_ptr<WaveFile const> wave, bool loop) \n{\n    if (!engine._xaudio2)\n        return nullptr;\n\n    WaveFile::Fmt const& fmt = wave->getFormat();\n\n    IXAudio2SourceVoice * voice = nullptr;\n\n    WAVEFORMATEX wfmtx;\n    wfmtx.wFormatTag = WAVE_FORMAT_PCM;\n    wfmtx.nChannels = fmt.numChannels;\n    wfmtx.nSamplesPerSec = fmt.samplesPerSec;\n    wfmtx.nAvgBytesPerSec = fmt.bytesPerSec;\n    wfmtx.nBlockAlign = fmt.blockAlign;\n    wfmtx.wBitsPerSample = fmt.bitsPerSample;\n    wfmtx.cbSize = 0;\n\n    XAUDIO2_SEND_DESCRIPTOR sendDescriptors[1];\n    sendDescriptors[0].Flags = XAUDIO2_SEND_USEFILTER;\n    sendDescriptors[0].pOutputVoice = GRAB_VOICE_OBJ(engine._masteringVoice);\n    const XAUDIO2_VOICE_SENDS sendList = { 1, sendDescriptors };\n\n    HRESULT hr;\n\n    hr = engine._xaudio2->CreateSourceVoice(&voice, &wfmtx, 0, 2.0f, NULL, &sendList);\n    if (FAILED(hr)) {\n        engine.error((std::string(\"error CreateSourceVoice : \")+wave->m_filepath.generic_string()).c_str());\n        return nullptr;\n    }\n\n    std::unique_ptr<Voice> result(new Voice);\n    result->_voice = voice;\n    result->_wave = std::move(wave);\n\n    std::memset(&result->_buffer, 0, sizeof(XAUDIO2_BUFFER));\n    result->_buffer.pAudioData = (BYTE const *)result->_wave->getSamplesData();\n    result->_buffer.Flags = XAUDIO2_END_OF_STREAM;\n    result->_buffer.AudioBytes = result->_wave->getSamplesDataSize();\n    result->_buffer.LoopCount = loop ? XAUDIO2_LOOP_INFINITE : XAUDIO2_NO_LOOP_REGION;\n\n    if (result->submitBuffer(engine))\n        return result;\n    return nullptr;\n}\n\nVoice::~Voice() {\n    if (_voice)\n        _voice->DestroyVoice();\n}\n\nbool Voice::submitBuffer(Engine& engine, float offset) {\n    _voice->Stop();\n    WaveFile::Fmt const& fmt = _wave->getFormat();\n    if (offset > 0.f)\n        _buffer.PlayBegin = _buffer.LoopBegin = uint32_t(offset * float(fmt.samplesPerSec));\n    if (FAILED(_voice->SubmitSourceBuffer(&_buffer))) {\n        engine.error(\"SubmitSourceBuffer failed\");\n        return false;\n    }\n    return true;\n}\n\nbool Voice::setStart(Engine& engine, float offset)\n{\n    if (offset < 0.f)\n        return false;\n     _voice->FlushSourceBuffers();\n    return submitBuffer(engine, offset);\n}\n\nbool Voice::rewind() {\n    _voice->Stop();\n    _voice->FlushSourceBuffers();\n    return _voice->SubmitSourceBuffer(&_buffer)==S_OK;\n}\n\nint Voice::getBuffersQueued() {\n    XAUDIO2_VOICE_STATE xstate;\n    _voice->GetState(&xstate, 0);\n    return xstate.BuffersQueued;\n}\n\nint Voice::getSamplesPlayed() {\n    XAUDIO2_VOICE_STATE xstate;\n    _voice->GetState(&xstate, 0);\n    return (int)xstate.SamplesPlayed;\n}\n\n//\n//\n//\n\nEngine::~Engine() {\n    if (_masteringVoice)\n        _masteringVoice->DestroyVoice();\n    if (_xaudio2)\n        _xaudio2->Release();\n}\n\nstd::shared_ptr<Engine> Engine::create() {\n\n    static std::shared_ptr<Engine> _engine;\n\n    if (_engine && _engine->isReady()) {\n        return _engine;\n    }\n\n    if (!_engine) {\n\n        _engine = std::make_shared<Engine>(*new Engine);\n\n        HRESULT hr;\n        if (FAILED(hr = CoInitializeEx(NULL, COINIT_MULTITHREADED))) {\n            _errors.push_back(\"Error initializing multi-threaded mode\");\n            return _engine;\n        }\n\n        if (FAILED(hr = XAudio2Create(&_engine->_xaudio2, 0))) {\n            _errors.push_back(\"XAudio2Create failed\");\n            return _engine;\n        }\n\n        if (FAILED(hr = _engine->_xaudio2->CreateMasteringVoice(&_engine->_masteringVoice))) {\n            SAFE_RELEASE(_engine->_xaudio2);\n            _errors.push_back(\"CreateMasteringVoice failed\");\n        }\n    }\n    return _engine;\n}\n\nvoid Engine::mute(bool mute) {\n    \n    if (!isReady())\n        return;\n    \n    if (mute) {\n        _masteringVoice->GetVolume(&_masterVolume);\n        _masteringVoice->SetVolume(0.f);\n    } else {\n        _masteringVoice->SetVolume(_masterVolume);\n        _masterVolume = -1.f;\n    }\n}\n\nstd::vector<std::string> const & Engine::getErrors() {\n    return _errors;\n}\n\nvoid Engine::error(char const * msg) {\n    _errors.push_back(msg);\n}\n\n};\n"
  },
  {
    "path": "demo/audio/audio.h",
    "content": "#pragma once\n\n// clang-format off\n\n#if  defined(AUDIO_ENGINE_WITH_XAUDIO)\n    #include <xaudio2.h>\n    #include <XAudio2fx.h>\n    #define GRAB_VOICE_OBJ(P) (P)\n#elif defined(AUDIO_ENGINE_XAUDIO_ON_FAUDIO)\n    #include <audio/xaudiofaudio.h>\n#endif\n\n#include <memory>\n#include <string>\n#include <vector>\n#include <filesystem>\n\n// clang-format on\n\nnamespace donut::vfs\n{\n    class IFileSystem;\n}\n\nnamespace audio {\n\nclass Engine;\nclass WaveFile;\n\n//\n// Voice\n//\n\nclass Voice {\n\npublic:\n\n    static std::unique_ptr<Voice> create(Engine& engine,\n        std::filesystem::path const& wavefile, bool loop=true);\n\n    static std::unique_ptr<Voice> create(Engine& engine,\n        std::shared_ptr<WaveFile const> wav, bool loop=true);\n\n    ~Voice();\n\n    void start() { _voice->Start(0); }\n    void stop() { _voice->Stop(0); }\n    void playOnce() { _voice->Start(0); _voice->ExitLoop(); }\n    bool rewind();\n\n    // sets the audio playback starting point (offset in seconds)\n    bool setStart(Engine& engine, float offset);\n\n    // returns the number of buffers queued for this voice\n    int getBuffersQueued();\n    \n    // total number of samples played by the voice since creation\n    int getSamplesPlayed();\n\n    void setVolume(float volume) { _voice->SetVolume(volume); }\n    float getVolume() { float v; _voice->GetVolume(&v); return v; }\n\n    void setPitch(float pitch) { _voice->SetFrequencyRatio(pitch); }\n    float getPitch() { float p; _voice->GetFrequencyRatio(&p); return p; }\n\nprivate:\n\n    bool submitBuffer(Engine& engine, float offset = 0.f);\n\n    Voice() = default;\n\n    IXAudio2SourceVoice * _voice = nullptr;\n\n    XAUDIO2_BUFFER _buffer;\n\n    std::shared_ptr<WaveFile const> _wave;\n};\n\n//\n// Engine\n//\n\nclass Engine {\n\npublic:\n\n    static std::shared_ptr<Engine> create();\n\n    ~Engine();\n\n    void mute(bool mute);\n\n    static std::weak_ptr<Engine> get();\n\n    bool isReady() const { return _xaudio2 && _masteringVoice; }\n\n    void error(char const * msg);\n\n    static std::vector<std::string> const & getErrors();\n\nprivate:\n\n    friend class Voice;\n\n    float _masterVolume = 1.f;\n\n    IXAudio2 * _xaudio2 = nullptr;\n    IXAudio2MasteringVoice * _masteringVoice = nullptr;\n};\n\n}; // end namespace audio\n\n"
  },
  {
    "path": "demo/audio/waveFile.cpp",
    "content": "// clang-format off\n\n#include \"./waveFile.h\"\n\n#include <cassert>\n#include <cmath>\n#include <cstring>\n#include <fstream>\n\nnamespace fs = std::filesystem;\n\nstatic std::vector<uint8_t> readFile(fs::path const& m_filepath) {\n    \n    std::ifstream file(m_filepath.generic_string().c_str(), std::ios::binary);\n\n    if (!file.is_open())\n        return {};\n\n    file.seekg(0, std::ios::end);\n    size_t size = file.tellg();\n    file.seekg(0, std::ios::beg);\n\n    std::vector<uint8_t> data(size);\n\n    file.read((char*)data.data(), size);\n\n    if (!file.good())\n        return {};\n    \n    return data;\n}\n\n// clang-format on\nnamespace audio {\n\nstruct WaveFile::Chunk{\n    char      id[4]; // \"data\"  string\n    uint32_t  size;  // Sampled data length\n    \n    inline uint8_t const* data() const {\n        return reinterpret_cast<uint8_t const*>(this) + sizeof(Chunk);\n    }\n};\n\nstruct WaveFile::Header {\n    char      id[4];   // \"data\"  string\n    uint32_t  size;    // Sampled data length\n    char      wave[4]; // WAVE signature\n    inline bool validate() const {\n        if (std::memcmp(id, \"RIFF\", 4) != 0)\n            return false;\n        if (std::memcmp(wave, \"WAVE\", 4) != 0)\n            return false;\n        return true;\n    }\n};\n\nWaveFile::Chunk const* WaveFile::getSubChunk(std::vector<uint8_t> const& data, char const* name) {\n\n    // some WAV files have additional undocumented sub-chunks within the header ;\n    // this searches for a specific sub-chunk by name.\n\n    for (uint8_t const* ptr = data.data() + sizeof(Header); ptr < (data.data() + data.size()); ) {\n\n        Chunk const* chunk = reinterpret_cast<Chunk const*>(ptr);\n\n        if (std::memcmp(chunk->id, name, 4) == 0)\n            return chunk;\n\n        ptr += sizeof(Chunk) + chunk->size;\n    }\n    return nullptr;\n}\n\nstd::unique_ptr<WaveFile> WaveFile::read(fs::path const& m_filepath) {\n    auto wave = create(std::move(readFile(m_filepath)));   \n    if (wave)\n        wave->m_filepath = m_filepath;\n    return wave;\n}\n\nstd::unique_ptr<WaveFile> WaveFile::create(std::vector<uint8_t>&& data) {\n\n    // see: http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf\n\n    if (data.empty())\n        return nullptr;\n\n    // header chunk\n    Header const* header = reinterpret_cast<Header const*>(data.data());\n    if (!header->validate())\n        return nullptr;\n\n    // format sub-chunk\n    Fmt const* fmt = nullptr;\n    if (Chunk const* chunk = getSubChunk(data, \"fmt \"))\n        fmt = reinterpret_cast<Fmt const*>(chunk->data());\n    else\n        return nullptr;\n    if (fmt->audioFormat != 1)\n        return nullptr;\n\n    // data sub-chunk\n    Chunk const* dataChunk = getSubChunk(data, \"data\");\n    if (!dataChunk)\n        return nullptr;\n\n    auto wave = new WaveFile;\n    wave->_fmt = fmt;\n    wave->_dataChunk = dataChunk;\n    wave->_data = std::move(data);\n    return std::unique_ptr<WaveFile>(wave);\n}\n\nWaveFile::Fmt const& WaveFile::getFormat() const {\n    return *_fmt;\n}\n\nuint32_t WaveFile::getSamplesDataSize() const {\n    return _dataChunk->size;\n}\n\nvoid const* WaveFile::getSamplesData() const {\n    return _dataChunk->data();\n}\n\nuint32_t WaveFile::getNumSamplesTotal() const {\n    return _dataChunk->size / (_fmt->numChannels * _fmt->bitsPerSample / 8);\n}\n\nfloat WaveFile::duration() const {\n    return float(getNumSamplesTotal() / _fmt->samplesPerSec);\n}\n\nstd::unique_ptr<WaveForm> WaveFile::computeWaveform(\n    uint32_t size, float start_time, float end_time, int channel) const {\n\n    uint32_t start_index = static_cast<uint32_t>( std::max( 0.f, start_time * static_cast<float>( _fmt->samplesPerSec ) ) );\n    uint32_t end_index = end_time < 0.f ? getNumSamplesTotal() :\n                                          static_cast<uint32_t>( end_time * static_cast<float>( _fmt->samplesPerSec ) );\n\n    start_index = std::min(start_index, getNumSamplesTotal());\n    end_index = std::min(end_index, getNumSamplesTotal());\n\n    if (start_index >= end_index)\n        return nullptr;\n\n    auto process = [this, &size]<typename T>(uint32_t start, uint32_t end, int channel) {\n        assert(end > start);\n\n        T const* ptr = reinterpret_cast<T const*>(_data.data());\n        T const* data_end = reinterpret_cast<T const*>(_data.data() + _data.size());\n\n        uint32_t nsamples = end - start;\n        uint16_t nchannels = _fmt->numChannels;\n\n        uint32_t block_count = size;\n        uint32_t block_size =\n            static_cast<uint32_t>( std::ceil( float( nsamples ) / float( 2 * static_cast<float>( block_count ) ) ) );\n\n        auto wform = std::make_unique<WaveForm>();\n        wform->maxs.resize(block_count);\n        wform->mins.resize(block_count);\n\n        for (uint32_t block = 0; (block < block_count) && (ptr < data_end); ++block) {\n\n            float min = std::numeric_limits<T>::max();\n            float max = std::numeric_limits<T>::min();\n\n            for (uint32_t sample = 0; (sample < block_size) && (ptr < data_end); ++sample) {\n                if (channel == -1) {\n                    for (uint16_t channel = 0; channel < nchannels; ++channel) {\n                        min = std::min(min, float(*ptr++) / std::numeric_limits<T>::max());\n                        max = std::max(max, float(*ptr++) / std::numeric_limits<T>::max());\n                    }\n                } else {\n                    T const* channelPtr = ptr + (channel * 2);\n                    min = std::min(min, float(*channelPtr++) / std::numeric_limits<T>::max());\n                    max = std::max(max, float(*channelPtr) / std::numeric_limits<T>::max());\n                    ptr += nchannels * 2;\n                }\n            }\n            wform->mins[block] = min;\n            wform->maxs[block] = max;\n        }\n        return wform;\n    };\n\n    switch (_fmt->bitsPerSample) {\n        case 8: return process.template operator()<int8_t>(start_index, end_index, channel);\n        case 16: return process.template operator()<int16_t>(start_index, end_index, channel);\n    }\n    return nullptr;\n}\n\n} // end namespace audio\n"
  },
  {
    "path": "demo/audio/waveFile.h",
    "content": "#pragma once\n\n// clang-format off\n\n#include <cstdint>\n#include <memory>\n#include <limits>\n#include <filesystem>\n#include <vector>\n\n// clang-format on\n\nnamespace audio {\n\nstruct WaveForm {\n    std::vector<float> mins;\n    std::vector<float> maxs;\n};\n\nclass WaveFile {\n\npublic:\n\n    struct Fmt {\n        uint16_t audioFormat;    // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM\n        uint16_t numChannels;    // Number of channels 1=Mono 2=Sterio\n        uint32_t samplesPerSec;  // Sampling Frequency in Hz\n        uint32_t bytesPerSec;    // bytes per second\n        uint16_t blockAlign;     // 2=16-bit mono, 4=16-bit stereo\n        uint16_t bitsPerSample;  // Number of bits per sample\n    };\n\n    static std::unique_ptr<WaveFile> read(std::filesystem::path const& m_filepath);\n\n    static std::unique_ptr<WaveFile> create(std::vector<uint8_t>&& data);\n\n    std::filesystem::path m_filepath;\n\n    Fmt const& getFormat() const;\n\n    void const * getSamplesData() const;   \n    uint32_t getSamplesDataSize() const;\n    uint32_t getNumSamplesTotal() const;\n\n    float duration() const;\n\n    std::unique_ptr<WaveForm> computeWaveform(\n        uint32_t size, float start_time = 0.f, float end_time = -1.f, int channel = -1) const;\n\nprivate:\n\n    WaveFile() = default;\n\n    struct Chunk;\n    struct Header;\n\n    static Chunk const* getSubChunk(std::vector<uint8_t> const& data, char const* name);\n\n    mutable Fmt const* _fmt = nullptr;\n    mutable Chunk const* _dataChunk = nullptr;\n\n    std::vector<uint8_t> _data;\n};\n\n}; // end namespace audio\n"
  },
  {
    "path": "demo/audio/xaudiofaudio.h",
    "content": "#pragma once\n\n#include <cstring>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#include <FAudio.h>\n#include <FAudioFX.h>\n\n/////////////////\n// imitate the smallest subset of the XAudio2 COM interface that MeshletsDemo wants, on top of FAudio\n\ntypedef FAudioBuffer XAUDIO2_BUFFER;\ntypedef FAudioBufferWMA XAUDIO2_BUFFER_WMA;\ntypedef FAudioVoiceSends XAUDIO2_VOICE_SENDS;\ntypedef FAudioVoiceState XAUDIO2_VOICE_STATE;\ntypedef FAudioSendDescriptor XAUDIO2_SEND_DESCRIPTOR;\ntypedef FAudioEffectChain XAUDIO2_EFFECT_CHAIN;\ntypedef FAudioProcessor XAUDIO2_PROCESSOR;\ntypedef FAudioWaveFormatEx WAVEFORMATEX;\ntypedef FAudioVoiceCallback IXAudio2VoiceCallback;\n#define XAUDIO2_COMMIT_NOW FAUDIO_COMMIT_NOW\n#define XAUDIO2_SEND_USEFILTER FAUDIO_SEND_USEFILTER\n#define XAUDIO2_DEFAULT_FREQ_RATIO FAUDIO_DEFAULT_FREQ_RATIO\n#define XAUDIO2_LOOP_INFINITE FAUDIO_LOOP_INFINITE\n#define XAUDIO2_NO_LOOP_REGION FAUDIO_NO_LOOP_REGION\n#define XAUDIO2_END_OF_STREAM FAUDIO_END_OF_STREAM\n#define XAUDIO2_DEFAULT_CHANNELS FAUDIO_DEFAULT_CHANNELS\n#define XAUDIO2_DEFAULT_SAMPLERATE FAUDIO_DEFAULT_SAMPLERATE\n#define XAUDIO2_DEFAULT_PROCESSOR FAUDIO_DEFAULT_PROCESSOR\nclass IXAudio2;\nclass IXAudio2SourceVoice;\nclass IXAudio2MasteringVoice;\n\n#define GRAB_VOICE_OBJ(P) ((P)->_GetFAudioVoidPtr()) /* fudge because of how I've implemented the shim */\n\n/////////////////\n// ... plus some basic win32-alikes to make the shim easier to use transparently\n#ifndef _WIN32\ntypedef uint32_t HRESULT;\ntypedef uint8_t BYTE;\n#define S_OK 0\n#define FAILED(R) ((HRESULT)(R) < 0)\n\n#define CoInitializeEx(FOO,BAR) S_OK /* not really using COM, pretend we're fine */\n#define COINIT_MULTITHREADED\n\n#define WAVE_FORMAT_PCM FAUDIO_FORMAT_PCM\n#endif // _WIN32\n\n/////////////////\n// here's the core of the fakey XAudio2 COM interface\nclass IXAudio2MasteringVoice\n{\npublic:\n  static IXAudio2MasteringVoice* _XAudio2MasteringVoiceCreateFromFAudioMasteringVoice(FAudioMasteringVoice *faudiomv)\n  {\n    IXAudio2MasteringVoice* rtn = new IXAudio2MasteringVoice;\n    rtn->_fa_voice = faudiomv;\n    return rtn;\n  };\n  FAudioVoice* _GetFAudioVoidPtr() {return _fa_voice;};\n\n  HRESULT SetVolume(float Volume, uint32_t OperationSet=XAUDIO2_COMMIT_NOW) { return FAudioVoice_SetVolume(_fa_voice,Volume,OperationSet); };\n  void GetVolume(float *pVolume) { FAudioVoice_GetVolume(_fa_voice,pVolume); };\n\n  void DestroyVoice() noexcept { FAudioVoice_DestroyVoice(_fa_voice); _fa_voice = NULL; }\n\nprivate:\n  FAudioMasteringVoice* _fa_voice;\n};\n\nclass IXAudio2SourceVoice\n{\npublic:\n  static IXAudio2SourceVoice* _XAudio2SourceVoiceCreateFromFAudioSourceVoice(FAudioSourceVoice *faudiosv)\n  {\n    IXAudio2SourceVoice* rtn = new IXAudio2SourceVoice;\n    rtn->_fa_voice = faudiosv;\n    return rtn;\n  };\n\n  void DestroyVoice() { FAudioVoice_DestroyVoice(_fa_voice); _fa_voice = NULL; };\n  HRESULT Start(uint32_t Flags=0, uint32_t OperationSet=XAUDIO2_COMMIT_NOW) { return FAudioSourceVoice_Start(_fa_voice,Flags,OperationSet); };\n  HRESULT Stop(uint32_t Flags=0, uint32_t OperationSet=XAUDIO2_COMMIT_NOW) { return FAudioSourceVoice_Stop(_fa_voice,Flags,OperationSet); };\n  HRESULT ExitLoop(uint32_t OperationSet=XAUDIO2_COMMIT_NOW) { return FAudioSourceVoice_ExitLoop(_fa_voice,OperationSet); };\n  HRESULT SetVolume(float Volume, uint32_t OperationSet=XAUDIO2_COMMIT_NOW) { return FAudioVoice_SetVolume(_fa_voice,Volume,OperationSet); };\n  void GetVolume(float *pVolume) { FAudioVoice_GetVolume(_fa_voice,pVolume); };\n  HRESULT SetFrequencyRatio(float Ratio, uint32_t OperationSet=XAUDIO2_COMMIT_NOW) { return FAudioSourceVoice_SetFrequencyRatio(_fa_voice,Ratio,OperationSet); };\n  void GetFrequencyRatio(float *pRatio) { FAudioSourceVoice_GetFrequencyRatio(_fa_voice,pRatio); };\n  HRESULT SubmitSourceBuffer(const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA = NULL) { return FAudioSourceVoice_SubmitSourceBuffer(_fa_voice,pBuffer,pBufferWMA); };\n  HRESULT FlushSourceBuffers() { return FAudioSourceVoice_FlushSourceBuffers(_fa_voice); };\nvoid GetState(XAUDIO2_VOICE_STATE* pVoiceState, uint32_t Flags=0) { FAudioSourceVoice_GetState(_fa_voice,pVoiceState,Flags); };\n\nprivate:\n  FAudioSourceVoice* _fa_voice;\n};\n\nclass IXAudio2\n{\npublic:\n  static IXAudio2* _XAudio2CreateFromFAudio(FAudio *faudio)\n  {\n    IXAudio2* rtn = new IXAudio2;\n    rtn->_faudio = faudio;\n    return rtn;\n  };\n  unsigned long Release()\n  { uint32_t refcount = FAudio_Release(_faudio); if (!refcount) _faudio = nullptr; return refcount; };\n\n  HRESULT CreateSourceVoice(\n      IXAudio2SourceVoice **ppSourceVoice,\n      const WAVEFORMATEX *pSourceFormat,\n      uint32_t Flags = 0,\n      float MaxFrequencyRatio = XAUDIO2_DEFAULT_FREQ_RATIO,\n      IXAudio2VoiceCallback *pCallback = NULL,\n      const XAUDIO2_VOICE_SENDS *pSendList = NULL,\n      const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL)\n  {\n    FAudioSourceVoice* faudiosv;\n    HRESULT rtn = FAudio_CreateSourceVoice(_faudio,&faudiosv,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain);\n    *ppSourceVoice = IXAudio2SourceVoice::_XAudio2SourceVoiceCreateFromFAudioSourceVoice(faudiosv);\n    return rtn;\n  }\n  HRESULT CreateMasteringVoice(\n      IXAudio2MasteringVoice **ppMasteringVoice,\n      uint32_t InputChannels = XAUDIO2_DEFAULT_CHANNELS,\n      uint32_t InputSampleRate = XAUDIO2_DEFAULT_SAMPLERATE,\n      uint32_t Flags = 0,\n      uint32_t DeviceIndex = 0,\n      const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL)\n  {\n    FAudioMasteringVoice* faudiomv;\n    HRESULT rtn = FAudio_CreateMasteringVoice(_faudio,&faudiomv,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain);\n    *ppMasteringVoice = IXAudio2MasteringVoice::_XAudio2MasteringVoiceCreateFromFAudioMasteringVoice(faudiomv);\n    return rtn;\n  }\n\nprivate:\n  FAudio* _faudio;\n};\n\nstatic HRESULT XAudio2Create(\n  IXAudio2 **ppXAudio2,\n  uint32_t Flags = 0,\n  XAUDIO2_PROCESSOR XAudio2Processor = XAUDIO2_DEFAULT_PROCESSOR\n)\n{\n  FAudio* faudio;\n  HRESULT rtn = FAudioCreate(&faudio,Flags,XAudio2Processor);\n  *ppXAudio2 = IXAudio2::_XAudio2CreateFromFAudio(faudio);\n  return rtn;\n};\n\n\n"
  },
  {
    "path": "demo/blit_params.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef BLIT_PARAMS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define BLIT_PARAMS_H\n\n#include \"rtxmg_demo.h\"\n\n#ifdef __cplusplus\n#include <donut/core/math/math.h>\nusing namespace donut::math;\n#endif\n\nstruct BlitParams\n{\n    BlitDecodeMode m_blitDecodeMode;\n    TonemapOperator m_tonemapOperator;\n    float m_exposure;\n    float m_zNear;\n    \n    float m_zFar;\n    float m_separator;\n    uint _pad[2];\n\n#ifdef __cplusplus\n    BlitParams()\n        : m_blitDecodeMode(BlitDecodeMode::None)\n        , m_tonemapOperator(TonemapOperator::Linear)\n        , m_exposure(1.0f)\n        , m_zNear(1.0f)\n        , m_zFar(100.f)\n        , m_separator(0.5f)\n        , _pad{ 0,0 }\n    {\n    }\n#endif\n};\n\n#endif // BLIT_PARAMS_H\n"
  },
  {
    "path": "demo/envmap/CMakeLists.txt",
    "content": "set(lib envmap)\nset(folder \"Demo\")\ninclude (\"${DONUT_DIR}/compileshaders.cmake\")\n\nfile(GLOB shaders \"shaders/*\")\nfile(GLOB sources \"*.cpp\" \"*.h\" *.cfg)\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${PROJECT_SOURCE_DIR}/rtxmg/include\"\n)\ntarget_link_libraries(${lib} donut_engine)\nset_target_properties(${lib} PROPERTIES FOLDER ${folder})\n\ndonut_compile_shaders_all_platforms(\n    TARGET ${lib}_shaders\n    CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/shaders.cfg\n    FOLDER ${folder}\n    SOURCES ${shaders}\n    OUTPUT_BASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders/${lib}\n    SHADERMAKE_OPTIONS ${RTXMG_SHADERMAKE_OPTIONS}\n    SHADERMAKE_OPTIONS_SPIRV ${RTXMG_SHADERMAKE_OPTIONS_SPIRV}\n    SHADER_MODEL ${RTXMG_SHADERS_SHADERMODEL}\n    IGNORE_INCLUDES ${RTXMG_SHADERS_IGNORED_INCLUDES}\n    INCLUDES ${RTXMG_SHADERS_INCLUDE_DIR}\n)\nadd_dependencies(${lib} ${lib}_shaders)\n"
  },
  {
    "path": "demo/envmap/preprocess_envmap.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <nvrhi/nvrhi.h>\n\n#include \"rtxmg/utils/buffer.h\"\n\nstruct PreprocessEnvMapShaders\n{\n    nvrhi::ComputePipelineHandle m_computeConditionalPSO;\n    nvrhi::ComputePipelineHandle m_computeMarginalPSO;\n\n    nvrhi::BindingLayoutHandle m_bindingLayout;\n};\n\nstruct PreprocessEnvMapResources\n{\n    RTXMGBuffer<float> m_conditionalFunc;\n    RTXMGBuffer<float> m_conditionalCdf;\n    RTXMGBuffer<float> m_marginalFunc;\n    RTXMGBuffer<float> m_marginalCdf;\n    nvrhi::BufferHandle m_params;\n    nvrhi::SamplerHandle m_sampler;\n};"
  },
  {
    "path": "demo/envmap/preprocess_envmap_params.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef PREPROCESS_ENVMAP_PARAMS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define PREPROCESS_ENVMAP_PARAMS_H\n\nstruct PreprocessEnvMapParams\n{\n    uint32_t envMapWidth;\n    uint32_t envMapHeight;\n};\n\n#endif // PREPROCESS_ENVMAP_PARAMS_H"
  },
  {
    "path": "demo/envmap/scan_system.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include \"scan_system.h\"\n\n#include <nvrhi/utils.h>\n#include <donut/core/log.h>\n#include <donut/core/math/math.h>\n\n#include \"rtxmg/utils/buffer.h\"\n\nusing namespace donut;\nusing namespace donut::math;\n\nvoid ScanSystem::Init(std::shared_ptr<donut::engine::ShaderFactory> shaderFactory, nvrhi::IDevice* device)\n{\n    m_prefixScan = shaderFactory->CreateShader(\"envmap/prefix_scan.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n    m_prefixScanPSO.Reset();\n    if (!m_prefixScan)\n    {\n        log::fatal(\"Failed to create prefix scan shader\");\n    }\n    m_prefixScanParams = device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(PrefixScanParams), \"prefixScanParams\",\n        engine::c_MaxRenderPassConstantBufferVersions));\n}\n\nvoid ScanSystem::PrefixScan(nvrhi::IBuffer* inputBuffer, nvrhi::IBuffer* outputBuffer, int inputWidth, int inputHeight, nvrhi::ICommandList* commandList)\n{\n    auto device = commandList->getDevice();\n\n    PrefixScanParams params = {};\n    params.elementCountX = inputWidth;\n    params.elementCountY = inputHeight;\n    params.outputWidth = inputWidth + 2;\n    commandList->writeBuffer(m_prefixScanParams, &params, sizeof(params));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::TypedBuffer_SRV(0, inputBuffer))\n        .addItem(nvrhi::BindingSetItem::TypedBuffer_UAV(0, outputBuffer))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_prefixScanParams));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_prefixScanBSL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for prefix scan shader\");\n    }\n\n    if (!m_prefixScanPSO)\n    {\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(m_prefixScan)\n            .addBindingLayout(m_prefixScanBSL);\n\n        m_prefixScanPSO = device->createComputePipeline(computePipelineDesc);\n    }\n    \n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_prefixScanPSO)\n        .addBindingSet(bindingSet);\n    commandList->setComputeState(state);\n\n    const uint32_t launchDimY = div_ceil(inputHeight, 16);\n    commandList->dispatch(1, launchDimY);\n}"
  },
  {
    "path": "demo/envmap/scan_system.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <nvrhi/nvrhi.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/engine/CommonRenderPasses.h>\n\n#include \"scan_system_shared.h\"\n\nstruct ScanSystem\n{\n    nvrhi::BufferHandle m_prefixScanParams;\n    nvrhi::ShaderHandle m_prefixScan;\n    nvrhi::ComputePipelineHandle m_prefixScanPSO;\n    nvrhi::BindingLayoutHandle m_prefixScanBSL;\n\n    void Init(std::shared_ptr<donut::engine::ShaderFactory> shaderFactory, nvrhi::IDevice* device);\n    void PrefixScan(nvrhi::IBuffer* inputBuffer, nvrhi::IBuffer* outputBuffer, int inputWidth, int inputHeight, nvrhi::ICommandList* commandList);\n};\n"
  },
  {
    "path": "demo/envmap/scan_system_shared.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef SCAN_SYSTEM_SHARED_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define SCAN_SYSTEM_SHARED_H\n\n /// Number of threads per block that are used for computing the horizontal prefix scans for a 2D buffer.\n /// To handle 8k textures this must be at least 128, otherwise a 3-level scan would be required.\n#define PREFIX_SCAN_THREAD_BLOCK_SIZE 512\n\n/// Number of image rows that each kernel invocation will handle during horizontal prefix scans.\n#define PREFIX_SCAN_ROWS_PER_BLOCK 1\n\nstruct PrefixScanParams\n{\n    uint32_t elementCountX;\n    uint32_t elementCountY;\n    uint32_t outputWidth;\n};\n\n#endif // SCAN_SYSTEM_SHARED_H"
  },
  {
    "path": "demo/envmap/shaders/compute_conditional.hlsl",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"envmap/preprocess_envmap_params.h\"\n#include \"rtxmg/utils/constants.h\"\n\nConstantBuffer<PreprocessEnvMapParams> gPreprocessEnvMapParams : register(b0);\n\nTexture2D<float4> gTextureColorIn : register(t0);\nRWBuffer<float> g_ConditionalFunc : register(u0);\n\nSamplerState gTextureColorInSampler : register(s0);\n\n// clang-format off\n[numthreads(16, 16, 1)]\n[shader(\"compute\")]\nvoid main(uint2 dispatchThreadId : SV_DispatchThreadID)\n// clang-format on\n{\n    if (dispatchThreadId.y >= gPreprocessEnvMapParams.envMapHeight || dispatchThreadId.x >= gPreprocessEnvMapParams.envMapWidth)\n    {\n        return;\n    }\n    float2 uv = (float2(dispatchThreadId) + 0.5f) / float2(gPreprocessEnvMapParams.envMapWidth, gPreprocessEnvMapParams.envMapHeight);\n    float3 color = gTextureColorIn.SampleLevel(gTextureColorInSampler, uv, 0).xyz;\n    //    float3 color = gTextureColorIn[dispatchThreadId].xyz;\n\n    const float3 lumConverter = float3(0.299f, 0.587f, 0.114f);\n    float lum = dot(lumConverter, color);\n    float sinTheta = sin(uv[1] * M_PIf); // prefer values away from the poles to compensate for distortion in latlong mapping (PBRT V3 section 14.2.4)\n\n    g_ConditionalFunc[dispatchThreadId.y * gPreprocessEnvMapParams.envMapWidth + dispatchThreadId.x] = lum * sinTheta;\n}\n\n"
  },
  {
    "path": "demo/envmap/shaders/compute_marginal.hlsl",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"envmap/preprocess_envmap_params.h\"\n#include \"rtxmg/utils/constants.h\"\n\nConstantBuffer<PreprocessEnvMapParams> gPreprocessEnvMapParams : register(b0);\n\nTexture2D<float4> gTextureColorIn : register(t0);\n\nRWBuffer<float> gConditionalCdf : register(u1);\nRWBuffer<float> gMarginalFunc : register(u2);\n\n// clang-format off\n[numthreads(1, 32, 1)]\n[shader(\"compute\")]\nvoid main(uint2 dispatchThreadId : SV_DispatchThreadID)\n// clang-format on\n{\n    if (dispatchThreadId.y >= gPreprocessEnvMapParams.envMapHeight)\n    {\n        return;\n    }\n\n    const float funcInt = gConditionalCdf[dispatchThreadId.y * (gPreprocessEnvMapParams.envMapWidth + 2) + gPreprocessEnvMapParams.envMapWidth + 1];\n    gMarginalFunc[dispatchThreadId.y] = funcInt;\n}\n\n"
  },
  {
    "path": "demo/envmap/shaders/envmap.hlsli",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n#ifndef ENVMAP_HLSLI  // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define ENVMAP_HLSLI\n\nfloat sphericalPhi(float3 v)\n{\n    const float p = atan2(v.z, v.x);\n    return (p < 0) ? (p + TWO_PI) : p;\n}\n\nfloat sphericalTheta(float3 v)\n{\n    return acos(clamp(v.y, -1.f, 1.f));\n}\n\nfloat2 sphericalProjection(float3 d)\n{\n    d = normalize(d);\n    const float u = fmod(sphericalPhi(d), TWO_PI) * INV_2PI;\n    const float v = sphericalTheta(d) * M_1_PIf;\n    return float2(u, v);\n}\n\nfloat3\nsphericalDirection(const float2 u, out float sinTheta)\n{\n    const float phi = u.x * TWO_PI;\n    const float theta = u.y * M_PIf;\n    const float cosTheta = cos(theta);\n    sinTheta = sin(theta);\n    return normalize(float3(sinTheta * cos(phi), cosTheta, sinTheta * sin(phi)));\n}\n\nfloat2 convertDirToTexCoords(float3 d, float4x4 rotation)\n{\n    float4 transformed = mul(rotation, float4(d, 1.0f));\n    return sphericalProjection(transformed.xyz);\n}\n\nfloat3 convertTexCoordsToDir(float2 uv, float4x4 rotationInv, out float sinTheta)\n{\n    float3 dir = sphericalDirection(uv, sinTheta);\n    float4 transformed = mul(rotationInv, float4(dir, 1.f));\n    return transformed.xyz;\n}\n\nfloat3 envMapEvaluate(float2 u, Texture2D<float4> tex, float intensity, SamplerState sampler)\n{\n    return intensity * (tex.SampleLevel(sampler, u, 0).rgb);\n}\n\nfloat3 envMapEvaluate(float3 dir, Texture2D<float4> tex, float intensity, SamplerState sampler, float4x4 rotation)\n{\n    const float2 u = convertDirToTexCoords(dir, rotation);\n    return envMapEvaluate(u, tex, intensity, sampler);\n}\nfloat envMapPdf(float2 u, Texture2D<float4> envMap, StructuredBuffer<float> conditional, StructuredBuffer<float> marginalCDF, SamplerState sampler)\n{\n    uint width, height;\n    \n    envMap.GetDimensions(width, height);\n    uint iu = clamp(int(u[0] * width), 0, width - 1);\n    uint iv = clamp(int(u[1] * height), 0, height - 1); \n\n    float conditionalValue = conditional[iv * width + iu];\n    float marginalIntegral = marginalCDF[height+1];\n\n    float sinTheta = sin(u[1] * M_PIf);\n\n    return conditionalValue / (marginalIntegral * TWO_PI * M_PI * sinTheta); // Jacobian term for latlong conversion; see PBRT V3 section 14.2.4.);\n}\n\nfloat envMapPdf(float3 dir, float4x4 rotation, Texture2D<float4> envMap, StructuredBuffer<float> conditional, StructuredBuffer<float> marginalCDF, SamplerState sampler)\n{\n    const float2 u = convertDirToTexCoords(dir, rotation);\n    return envMapPdf(u, envMap, conditional, marginalCDF, sampler);\n}\n\nuint FindInterval(StructuredBuffer<float> cdf, float u, uint row, uint width)\n{\n    // cdf buffer is padded on both sides (firstpad, first, ... , last, lastpad)\n    uint cdfPaddedWidth = width + 2;\n    uint size = width;\n    uint first = 1;\n        \n    while (size > 0)\n    {\n        uint halfSize = size >> 1;\n        uint middle = first + halfSize;\n        uint middleOffset = row * cdfPaddedWidth + middle;\n        \n        bool predResult = cdf[middleOffset] <= u;\n        first = predResult ? middle + 1 : first;\n        size = predResult ? size - halfSize - 1 : halfSize;\n    }\n    \n    return clamp(first - 1, 0, width - 1);\n}\n\nfloat Sample1D(float u, StructuredBuffer<float> func, StructuredBuffer<float> cdf, uint row, uint width, out float pdf, out uint offset)\n{\n    offset = FindInterval(cdf, u, row, width);\n    uint cdfPaddedWidth = width + 2;\n    uint cdfOffset = row * cdfPaddedWidth + offset;\n    uint funcOffset = row * width + offset;\n    \n    float du = u - cdf[cdfOffset];\n    if (cdf[cdfOffset + 1] - cdf[cdfOffset] > 0)\n    {\n        du /= (cdf[cdfOffset + 1] - cdf[cdfOffset]);\n    }\n    float funcInt = cdf[row * (width + 2) + width + 1];\n    pdf = func[funcOffset] / funcInt;\n    return (offset + du) / width;\n}\n\nfloat3 envMapImportanceSample(float2 rndSample, float4x4 rotationInv, out float pdf, \n    out float3 envMapColor, Texture2D<float4> envMap, float intensity, StructuredBuffer<float>conditional, \n    StructuredBuffer<float>marginal, StructuredBuffer<float> conditionalCDF, StructuredBuffer<float> marginalCDF, SamplerState sampler)\n{   \n    uint width, height;\n    float pdfs[2];\n    uint indices[2];\n    float2 latLongUv;\n\n    envMap.GetDimensions(width, height);\n\n    latLongUv[1] = Sample1D(rndSample[1], marginal, marginalCDF, 0, height, pdfs[1], indices[1]);\n    latLongUv[0] = Sample1D(rndSample[0], conditional, conditionalCDF, indices[1], width, pdfs[0], indices[0]);\n    envMapColor = envMapEvaluate(latLongUv, envMap, intensity, sampler);\n    \n    float        sinTheta = 0.f;\n    const float3 dir = convertTexCoordsToDir(latLongUv, rotationInv, sinTheta);\n    pdf = (pdfs[0] * pdfs[1]) / (TWO_PI * M_PI * sinTheta); // Jacobian term for latlong conversion; see PBRT V3 section 14.2.4.\n\n    return dir;\n}\n\n#endif // ENVMAP_HLSLI"
  },
  {
    "path": "demo/envmap/shaders/prefix_scan.hlsl",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"envmap/scan_system_shared.h\"\n\nConstantBuffer<PrefixScanParams> gPrefixScanParams : register(b0);\n\nBuffer<float> input: register(t0);\nRWBuffer<float> output: register(u0);\n\n// clang-format off\n[numthreads(1, 16, 1)]\n[shader(\"compute\")]\nvoid main(uint2 dispatchThreadId : SV_DispatchThreadID)\n// clang-format on\n{\n    int n = gPrefixScanParams.elementCountX;\n\n    if (dispatchThreadId.y >= gPrefixScanParams.elementCountY || dispatchThreadId.x != 0)\n    {\n        return;\n    }\n\n    uint32_t outputOffset = dispatchThreadId.y * gPrefixScanParams.outputWidth;\n    uint32_t inputOffset = dispatchThreadId.y * n;\n\n    output[outputOffset + 0] = 0;\n    float sum = 0;\n    for (int i = 1; i <= n; ++i)\n    {\n        output[outputOffset + i] = output[outputOffset + i - 1] + input[inputOffset + i - 1] / n;\n    }\n\n    float funcInt = output[outputOffset + n];\n    output[outputOffset + n + 1] = funcInt;\n    if (funcInt == 0)\n    {\n        for (int i = 1; i <= n; ++i)\n        {\n            output[outputOffset + i] = float(i) / float(n);\n        }\n    }\n    else\n    {\n        for (int i = 1; i <= n; ++i)\n        {\n            output[outputOffset + i] /= funcInt;\n        }\n    }\n}\n"
  },
  {
    "path": "demo/envmap/shaders.cfg",
    "content": "shaders/prefix_scan.hlsl -T cs\nshaders/compute_conditional.hlsl -T cs\nshaders/compute_marginal.hlsl -T cs\n"
  },
  {
    "path": "demo/gbuffer.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef GBUFFER_H  // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define GBUFFER_H\n\ntypedef float DepthFormat;\ntypedef float4 NormalFormat;\ntypedef float4 AlbedoFormat;\ntypedef float4 SpecularFormat;\ntypedef float SpecularHitTFormat;\ntypedef float RoughnessFormat;\n\nstatic const uint32_t kInvalidInstanceId = ~0u;\nstatic const uint32_t kInvalidSurfaceIndex = ~0u;\n\nstruct HitResult\n{\n    uint32_t instanceId;\n    uint32_t surfaceIndex;\n    float2 surfaceUV;\n    float2 texcoord; // For displacement texture\n};\n\n#ifndef __cplusplus\nHitResult DefaultHitResult()\n{\n    HitResult result;\n    result.instanceId = kInvalidInstanceId;\n    result.surfaceIndex = kInvalidSurfaceIndex;\n    result.surfaceUV = float2(0.0f, 0.f);\n    result.texcoord = float2(0.0f, 0.f);\n\n    return result;\n}\n#endif\n\n#endif // GBUFFER_H"
  },
  {
    "path": "demo/gui.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <imgui_internal.h>\n\n#ifndef _WIN32\n#include <climits>\n#include <cstdio>\n#include <unistd.h>\n#else\n#include <ShObjIdl.h>\n#include <ShlObj_core.h>\n#include <Windows.h>\n#include <codecvt>\n#include <locale>\nconstexpr int const PATH_MAX = MAX_PATH;\n#endif // _WIN32\n\n#include \"rtxmg_demo_app.h\"\n#include \"gui.h\"\n#include \"implot.h\"\n\n#include <charconv>\n#include <filesystem>\n#include <filesystem>\n#include <algorithm>\n#include <string>\n#include <sstream>\n\n#include <donut/app/imgui_renderer.h>\n#include \"rtxmg/scene/scene.h\"\n#include \"rtxmg/utils/constants.h\"\n#include \"rtxmg/utils/formatters.h\"\n#include \"rtxmg/cluster_builder/tessellator_constants.h\"\n\n#include \"korgi.h\"\n#include \"rtxmg/profiler/gui.h\"\n#include \"rtxmg/profiler/statistics.h\"\n\n#include <donut/app/StreamlineInterface.h>\n\nnamespace fs = std::filesystem;\n\nusing namespace donut;\n\n#define UI_RED ImVec4(1.f, 0.f, 0.f, 1.f)\n#define UI_SAGE ImVec4(.3f, .4f, .35f, 1.f)\n#define UI_PLUM ImVec4(.4f, .3f, .35f, 1.f)\n\nconstexpr float kItemWidth = 200.0f;\n\nUserInterface::UserInterface(RTXMGDemoApp& app)\n    : ImGui_Renderer(app.GetDeviceManager()), m_app(app)\n{\n    char const* nvidiaRgGlyphData = GetNVSansFontRgCompressedBase85TTF();\n    m_nvidiaRgFont =\n        AddFontFromMemoryCompressedBase85TTF(nvidiaRgGlyphData, 15.f, nullptr);\n\n    char const* nvidiaBldGlyphData = GetNVSansFontBoldCompressedBase85TTF();\n    m_nvidiaBldFont =\n        AddFontFromMemoryCompressedBase85TTF(nvidiaBldGlyphData, 30.f, nullptr);\n\n    ImGui::GetIO().FontDefault = m_nvidiaRgFont;\n\n    char const* iconicGlyphsData = GetOpenIconicFontCompressedBase85TTF();\n    uint16_t const* iconicGlyphsRange = GetOpenIconicFontGlyphRange();\n\n    m_iconicFont = AddFontFromMemoryCompressedBase85TTF(iconicGlyphsData, 14.f,\n        iconicGlyphsRange);\n\n    m_imgui = ImGui::GetCurrentContext();\n    m_implot = ImPlot::CreateContext();\n\n    ImPlotStyle& style = ImPlot::GetStyle();\n\n    style.FitPadding = ImVec2(0.1f, 0.1f);\n    style.PlotPadding = ImVec2(2, 5);\n    style.LegendPadding = ImVec2(2, 2);\n\n    m_app.SetGui(this);\n\n    SetupIniHandler();\n\n    SetupAudioEngine();\n}\n\nvoid UserInterface::SetupIniHandler()\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    if (const fs::path& binaryPath = app::GetDirectoryWithExecutable(); !binaryPath.empty())\n    {\n        UIData& ui = m_app.GetUIData();\n        ui.iniFilepath = (binaryPath / \"imgui.ini\").generic_string();\n        io.IniFilename = ui.iniFilepath.c_str();\n    }\n\n    io.IniSavingRate = 60.f;  // save every minute only or on quit\n\n    static struct Settings\n    {\n        bool audioMuted = false;\n\n        bool jsonAssetsFilter = false;\n        bool objAssetsFilter = false;\n\n        bool displayStats = false;\n\n        bool wantApply = false;\n\n        RTXMGDemoApp::WindowState windowState = {};\n    } settings;\n\n    ImGuiSettingsHandler ini_handler;\n    ini_handler.TypeName = \"RTXMG\";\n    ini_handler.TypeHash = ImHashStr(ini_handler.TypeName);\n    ini_handler.UserData = this;\n\n    ini_handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) -> void* {\n        settings.wantApply = true;\n        return &settings;\n    };\n\n\n    ini_handler.ApplyAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler) {\n        \n        if (settings.wantApply)\n        {\n            auto* gui = reinterpret_cast<UserInterface*>(handler->UserData);\n            auto* app = &gui->m_app;\n\n            gui->GetProfilerGUI().displayGraphWindow = settings.displayStats;\n\n            gui->MuteAudio(settings.audioMuted);\n\n            UIData& ui = app->GetUIData();\n            ui.includeJsonAssets = settings.jsonAssetsFilter;\n            ui.includeObjAssets = settings.objAssetsFilter;\n\n            const fs::path& mediaPath = app->GetMediaPath();\n            if (!mediaPath.empty())\n            {\n                assert(ui.mediaAssets.empty());\n                auto folder_filters = ui.folderFilters();\n                auto format_filters = ui.formatFilters();\n                ui.mediaAssets = FindMediaAssets(mediaPath, folder_filters.data(), format_filters.data());\n            }\n\n            app->SetWindowState(settings.windowState);\n\n            settings.wantApply = false;\n        }\n    };\n\n\n    ini_handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, void* entry, const char* line) {\n\n        int audioMuted = 0;\n        if (std::sscanf(line, \"AudioMuted=%d\", &audioMuted) == 1)\n            settings.audioMuted = audioMuted;\n\n        uint32_t json = 0, obj = 0;\n        if (std::sscanf(line, \"FormatFilters={ json=%d, obj=%d }\", &json, &obj) == 2)\n        {\n            settings.jsonAssetsFilter = (bool)json;\n            settings.objAssetsFilter = (bool)obj;\n        }\n\n        int displayStats = false;\n        if (std::sscanf(line, \"DisplayStatistics=%d\", &displayStats) == 1)\n            settings.displayStats = displayStats != 0;\n\n        donut::math::int2 windowSize{};\n        if (std::sscanf(line, \"WindowSize=%d,%d\", &windowSize.x, &windowSize.y) == 2)\n        {\n            settings.windowState.windowSize = windowSize;\n        }\n        int windowIsMaximized = false;\n        if (std::sscanf(line, \"WindowIsMaximized=%d\", &windowIsMaximized) == 1)\n        {\n            settings.windowState.isMaximized = windowIsMaximized != 0;\n        }\n        int windowIsFullscreen = false;\n        if (std::sscanf(line, \"WindowIsFullscreen=%d\", &windowIsFullscreen) == 1)\n        {\n            settings.windowState.isFullscreen = windowIsFullscreen != 0;\n        }\n    };\n\n    ini_handler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {\n\n        auto* gui = reinterpret_cast<UserInterface*>(handler->UserData);\n        auto* app = &gui->m_app;\n        \n        UIData& ui = app->GetUIData();\n        \n        settings.audioMuted = ui.audioMuted;\n        settings.jsonAssetsFilter = ui.includeJsonAssets;\n        settings.objAssetsFilter = ui.includeObjAssets;\n        settings.displayStats = gui->GetProfilerGUI().displayGraphWindow;\n        \n        settings.windowState = app->GetWindowState();\n        \n        buf->reserve(buf->size() + 2);  // ballpark reserve\n        buf->appendf(\"[%s][%s]\\n\", handler->TypeName, \"Settings\");\n        buf->appendf(\"AudioMuted=%d\\n\", settings.audioMuted);\n        buf->appendf(\"FormatFilters={ json=%d, obj=%d }\\n\", settings.jsonAssetsFilter, settings.objAssetsFilter);\n        buf->appendf(\"DisplayStatistics=%d\\n\", settings.displayStats);\n        buf->appendf(\"WindowSize=%d,%d\\n\", settings.windowState.windowSize.x, settings.windowState.windowSize.y);\n        buf->appendf(\"WindowIsMaximized=%d\\n\", settings.windowState.isMaximized);\n        buf->appendf(\"WindowIsFullscreen=%d\\n\", settings.windowState.isFullscreen);\n\n        buf->append(\"\\n\");\n    };\n\n    ImGui::AddSettingsHandler(&ini_handler);\n}\n\nvoid UserInterface::BackBufferResized(const uint32_t width,\n    const uint32_t height,\n    const uint32_t sampleCount)\n{\n}\n\nvoid UserInterface::buildUI()\n{\n    int width, height;\n    m_app.GetDeviceManager()->GetWindowDimensions(width, height);\n    float scaleX, scaleY;\n    m_app.GetDeviceManager()->GetDPIScaleInfo(scaleX, scaleY);\n\n    float layoutToDisplay = std::min(scaleX, scaleY);\n    float contentScale = layoutToDisplay > 0.f ? (1.0f / layoutToDisplay) : 1.0f;\n\n    // Layout is done at lower resolution than scaled up virtually past the render target m_size\n    // any element beyond this range is clipped.\n    width = int(width * contentScale);\n    height = int(height * contentScale);\n\n    BuildUIMain({ width, height });\n\n}\n\ntemplate<typename E, size_t N>\nstatic int ImGuiComboFromArray(const char* name, E* selected, const std::array<const char*, N>& labels)\n{\n    bool valueChanged = false;\n\n    int selectedIndex = int(*selected);\n    const char* selectedLabel = selectedIndex < labels.size() ? labels[selectedIndex] : \"Unknown\";\n\n    if (ImGui::BeginCombo(name, selectedLabel))\n    {\n        int index = 0;\n        for (const auto& label : labels)\n        {\n            bool isSelected = selectedIndex == index;\n            if (ImGui::Selectable(label, isSelected))\n            {\n                *selected = (E)index;\n                valueChanged = true;\n            }\n            if (isSelected) ImGui::SetItemDefaultFocus();\n            index++;\n        }\n        ImGui::EndCombo();\n    }\n    return valueChanged;\n}\n\nvoid UserInterface::BuildUIMain(int2 screenLayoutSize)\n{\n    UIData& uiData = GetApp().GetUIData();\n\n    auto& renderer = m_app.GetRenderer();\n\n    KORGI_BUTTON_CALLBACK(0, Play, [this]()\n    {\n        TimeLineEditorState& state = GetApp().GetUIData().timeLineEditorState;\n        state.PlayClicked();\n    });\n    KORGI_BUTTON_CALLBACK(0, Rewind, [this]()\n    {\n        TimeLineEditorState& state = GetApp().GetUIData().timeLineEditorState;\n        state.Rewind();\n    });\n    KORGI_BUTTON_CALLBACK(0, FastForward, [this]()\n    {\n        TimeLineEditorState& state = GetApp().GetUIData().timeLineEditorState;\n        state.FastForward();\n    });\n\n    KORGI_KNOB_CALLBACK(0, Slider1, 0.0, 10.0, [this, &renderer](float val)\n    {\n        renderer.SetExposure(val);\n    });\n    KORGI_BUTTON_CALLBACK(0, Record, [this]()\n    {\n        GetApp().SaveScreenshot();\n    });\n    KORGI_BUTTON_CALLBACK(0, S1, [this]()\n    {\n        GetApp().NextTonemapper();\n    });\n    KORGI_BUTTON_CALLBACK(0, Cycle, [this]()\n    {\n        GetApp().ResetCamera();\n    });\n    KORGI_BUTTON_CALLBACK(0, S2, [this]()\n    {\n        GetApp().IncrementMaxBounces(1);\n    });\n    KORGI_BUTTON_CALLBACK(0, M2, [this]()\n    {\n        GetApp().IncrementMaxBounces(-1);\n    });\n    KORGI_BUTTON_CALLBACK(0, S3, [this]()\n    {\n        GetApp().IncrementColorMode(1);\n    });\n    KORGI_BUTTON_CALLBACK(0, M3, [this]()\n    {\n        GetApp().IncrementColorMode(-1);\n    });\n    KORGI_BUTTON_CALLBACK(0, R3, [this]()\n    {\n        GetApp().ToggleWireframe();\n    });\n    KORGI_KNOB_CALLBACK(0, Slider2, 1, 2000, [this](float val)\n    {\n        GetApp().SetFineTessellationRate(val / 1000.0f);\n    });\n\n    ImVec2 itemSize = ImGui::GetItemRectSize();\n\n    const char* kWindowName = \"Settings\";\n    SetConstrainedWindowPos(kWindowName, ImVec2(10, 10), ImVec2(0.0f, 0.0f), MakeImVec2(screenLayoutSize));\n    ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f), ImGuiCond_Always);\n    ImGui::SetNextWindowSizeConstraints(ImVec2(100.f, 200.f), ImVec2(float(screenLayoutSize.x), screenLayoutSize.y - 70.0f));\n    ImGui::Begin(kWindowName, nullptr, ImGuiWindowFlags_None);\n\n    ImGui::PushItemWidth(kItemWidth);\n\n#ifdef AUDIO_ENGINE_ENABLED\n    static const char* unmutedGlyph = (char*)(u8\"\\ue0d5\" \"## unmuted\");\n    static const char* mutedGlyph = (char*)(u8\"\\ue0d7\" \"## muted\");\n\n    bool muted = uiData.audioMuted;\n    if (muted)\n        ImGui::PushStyleColor(ImGuiCol_Button, UI_RED);\n    ImGui::PushFont(m_iconicFont);\n    if (ImGui::Button(muted ? mutedGlyph : unmutedGlyph, { 20.f, itemSize.y }))\n    {\n        if (m_audioEngine)\n            m_audioEngine->mute(uiData.audioMuted = !muted);\n    }\n    if (muted)\n        ImGui::PopStyleColor();\n    ImGui::PopFont();\n    if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n        ImGui::SetTooltip(\"Mute audio.\");\n    ImGui::SameLine();\n#endif\n\n    ImGui::PushFont(m_iconicFont);\n    if (ImGui::Button((char const*)(u8\"\\ue02c\"\n        \"## screenshot\"),\n        { 0.f, itemSize.y }))\n    {\n        m_app.SaveScreenshot();\n    }\n    ImGui::PopFont();\n    if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n        ImGui::SetTooltip(\"Capture a screenshot.\");\n\n    ImGui::SameLine();\n    bool showMicroTriangles = renderer.GetShowMicroTriangles();\n\n    float buttonWidth = ImGui::GetTextLineHeightWithSpacing() + ImGui::CalcTextSize(\"Visualize Micro Triangles\").x\n        + ImGui::GetStyle().FramePadding.x * 2.0f;\n    ImGui::SetNextItemWidth(buttonWidth);\n\n    if (showMicroTriangles)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.0f, 0.0f, 1.0f));\n    if (ImGui::Button(\"Micro Triangles\"))\n    {\n        renderer.SetShowMicroTriangles(!showMicroTriangles);\n    }\n    if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n        ImGui::SetTooltip(\"Toggle micro triangle visualization mode with a unique color per triangle id.\");\n    if (showMicroTriangles)\n        ImGui::PopStyleColor();\n\n    ImGui::Spacing();\n    ImGui::Separator();\n    ImGui::Spacing();\n\n    ImGui::PushStyleColor(ImGuiCol_Header, UI_SAGE);\n    if (ImGui::CollapsingHeader(\"Scene Loading\", ImGuiTreeNodeFlags_DefaultOpen))\n    {\n        fs::path mediapath = m_app.GetMediaPath();\n\n        // media folder\n        bool objFilesNeedUpdate = false;\n        {\n            ImGui::PushFont(m_iconicFont);\n            if (ImGui::Button((char const*)(u8\"\\ue06b\"\n                \"## media path\"),\n                { 0.f, itemSize.y }))\n            {\n                std::string folderpath = mediapath.generic_string();\n                if (FolderDialog(folderpath))\n                {\n                    mediapath = fs::path(folderpath).lexically_normal();\n                    m_app.SetMediaPath(mediapath);\n                    uiData.currentAsset = nullptr;\n                    objFilesNeedUpdate = true;\n                }\n            }\n            ImGui::PopFont();\n            ImGui::SameLine();\n\n            float buttonWidth = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;\n            ImGui::SetNextItemWidth(kItemWidth - buttonWidth);\n            char buf[1024] = { 0 };\n            std::strncpy(buf, mediapath.generic_string().c_str(), std::size(buf));\n            if (ImGui::InputText(\"Data Folder\", buf, std::size(buf),\n                ImGuiInputTextFlags_EnterReturnsTrue))\n            {\n                mediapath = buf;\n                m_app.SetMediaPath(buf);\n                objFilesNeedUpdate = true;\n            }\n            if (ImGui::IsItemHovered() &&\n                ImGui::GetCurrentContext()->HoveredIdTimer > .5f && !mediapath.empty())\n                ImGui::SetTooltip(\"%s\", mediapath.generic_string().c_str());\n        }\n\n        if (ImGui::Checkbox(\"Json\", &uiData.includeJsonAssets))\n            objFilesNeedUpdate = true;\n        ImGui::SameLine();\n        if (ImGui::Checkbox(\"Obj\", &uiData.includeObjAssets))\n            objFilesNeedUpdate = true;\n\n        if (objFilesNeedUpdate || uiData.mediaAssets.empty())\n        {\n            auto folderFilters = uiData.folderFilters();\n            auto formatFilters = uiData.formatFilters();\n\n            // Store current asset name before refreshing the map\n            std::string currentAssetName = uiData.currentAsset ? uiData.currentAsset->GetName() : \"\";\n            \n            uiData.mediaAssets = FindMediaAssets(mediapath, folderFilters.data(),\n                formatFilters.data());\n            \n            // Restore current asset selection if it still exists\n            if (!currentAssetName.empty())\n            {\n                uiData.SelectCurrentAsset(currentAssetName);\n            }\n            else\n            {\n                uiData.currentAsset = nullptr;\n            }\n        }\n\n        char const* currentAssetName =\n            uiData.currentAsset ? uiData.currentAsset->GetName() : nullptr;\n        if (ImGui::BeginCombo(\"Scene\", currentAssetName,\n            ImGuiComboFlags_HeightLarge))\n        {\n            for (const auto& [key, asset] : uiData.mediaAssets)\n            {\n                bool isSequence = asset.IsSequence();\n\n                std::string const& name = isSequence ? asset.sequenceName : key;\n\n                bool isSelected = currentAssetName && (name == currentAssetName);\n\n                if (ImGui::Selectable(name.c_str(), isSelected))\n                {\n                    LoadAsset(asset, name, asset.frameRange);\n                }\n                if (isSelected)\n                    ImGui::SetItemDefaultFocus();\n            }\n            ImGui::EndCombo();\n        }\n        if (ImGui::IsItemHovered() &&\n            ImGui::GetCurrentContext()->HoveredIdTimer > .5f && uiData.currentAsset &&\n            !uiData.currentAsset->name.empty())\n        {\n            ImGui::SetTooltip(\"%s\", uiData.currentAsset->GetName());\n        }\n    }\n\n#if RTXMG_DEV_FEATURES\n    if (ImGui::CollapsingHeader(\"Debug\", ImGuiTreeNodeFlags_DefaultOpen))\n    {\n        ImGui::PushFont(m_iconicFont);\n        if (ImGui::Button((char const*)(u8\"\\ue0b3\"\n            \"## reload shaders\"),\n            { 0.f, itemSize.y }))\n        {\n            m_app.ReloadShaders();\n        }\n        ImGui::PopFont();\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Reload Shaders (CTRL+R)\");\n\n        ImGui::SameLine();\n        ImGui::PushFont(m_iconicFont);\n        if (ImGui::Button((char const*)(u8\"\\ue071\"\n            \"## dump fill clusters\"),\n            { 0.f, itemSize.y }))\n        {\n            m_app.DumpFineTess();\n        }\n        ImGui::PopFont();\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Dump Fill Clusters.\");\n\n        ImGui::SameLine();\n        ImGui::PushFont(m_iconicFont);\n        if (ImGui::Button((char const*)(u8\"\\ue028\"\n            \"## dump debug buffer\"),\n            { 0.f, itemSize.y }))\n        {\n            m_app.DumpDebugBuffer();\n        }\n        ImGui::PopFont();\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Dump Debug Buffer.\");\n\n        ImGui::SameLine();\n        ImGui::Checkbox(\"Monolithic ClusterBuild\", &uiData.enableMonolithicClusterBuild);\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\n                \"Use a single shader for compute cluster tiling and fill clusters.\\n\"\n                \"Instead of splitting the dispatches by surface type\");\n\n        bool accelBuildLoggingEnabled = m_app.GetAccelBuildLoggingEnabled();\n        if (ImGui::Checkbox(\"AS Log\", &accelBuildLoggingEnabled))\n        {\n            m_app.SetAccelBuildLoggingEnabled(accelBuildLoggingEnabled);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Log accel build buffers (Syncs GPU, Slow!)\");\n\n        ImGui::SameLine();\n        ImGui::Checkbox(\"Build AS\", &uiData.forceRebuildAccelStruct);\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\n                \"Re-tessellate and rebuild acceleration structures every frame.\\n\"\n                \"Disable to freeze tessellation and move the camera around.\\n\"\n                \"Animation will always force a rebuild.\");\n\n        if (!uiData.forceRebuildAccelStruct)\n        {\n            ImGui::SameLine();\n            if (ImGui::Button(\"Build AS Once\"))\n            {\n                m_app.RebuildAS();\n            }\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n                ImGui::SetTooltip(\"Force rebuild-AS one time, useful for debug logging.\");\n        }\n\n#if ENABLE_SHADER_DEBUG\n        int2& debugPixel = renderer.GetDebugPixel();\n        ImGui::InputInt2(\"DebugPixel (Right-click)\", debugPixel.data());\n                \n        if (ImGui::InputInt3(\"Tessellator Debug (Surface, Cluster, Lane)\", m_app.GetDebugSurfaceClusterLaneIndex().data()))\n        {\n            // Update renderer's debug surface index for highlighting\n            renderer.SetDebugSurfaceIndex(m_app.GetDebugSurfaceClusterLaneIndex()[0]);\n            m_app.RebuildAS();\n        }\n        if (ImGui::IsItemHovered() &&\n            ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Set surface >=0 and lane >=0 to debug compute cluster tiling.\\n\"\n                \"Set cluster >=0 and lane >=0 to debug fill clusters.\\n\");\n#endif\n    }\n#endif\n\n    if (!showMicroTriangles && ImGui::CollapsingHeader(\"Rendering\", ImGuiTreeNodeFlags_DefaultOpen))\n    {   \n        bool wireframe = renderer.GetWireframe();\n        if (ImGui::Checkbox(\"Wireframe\", &wireframe))\n        {\n            renderer.SetWireframe(wireframe);\n        }\n        if (ImGui::IsItemHovered() &&\n            ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Display micro-triangles wireframe over the geometry.\");\n\n        ImGui::SameLine();\n        bool displayZBuffer = renderer.GetDisplayZBuffer();\n        if (ImGui::Checkbox(\"Show Occlusion Depth\", &displayZBuffer))\n        {\n            renderer.SetDisplayZBuffer(displayZBuffer);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Show Hi-Z Occlusion Buffer of static geometry used for reducing tessellation\");\n        \n        bool denoiserEnabled = m_app.GetEffectiveDenoiserMode() != DenoiserMode::None;\n        if (!denoiserEnabled && renderer.GetEffectiveShadingMode() == ShadingMode::PT)\n        {\n            bool timeView = renderer.GetTimeView();\n            if (ImGui::Checkbox(\"Heatmap\", &timeView))\n            {\n                renderer.SetTimeView(timeView);\n            }\n            ImGui::SameLine();\n\n            int spp = static_cast<int>(std::sqrt(renderer.GetSPP()) - 1);\n            ImGui::PushItemWidth(65);\n            if (ImGui::Combo(\"SPP\", &spp, \" 1x\\0 4x\\0 9x\\0 16x\\0 25x\\0 36x\\0 49x\\0 64x\\0 81x\\0 100x\\0\"))\n            {\n                renderer.SetSPP((spp + 1) * (spp + 1));\n            }\n            ImGui::PopItemWidth();\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n                ImGui::SetTooltip(\"Samples Per Pixel.\");\n        }\n\n        if (renderer.GetWireframe())\n        {\n            float wireframeThickness = renderer.GetWireframeThickness();\n            if (ImGui::SliderFloat(\"Wireframe Thickness\", &wireframeThickness, 0.f,\n                10.f, \"%.3f\", ImGuiSliderFlags_Logarithmic))\n            {\n                renderer.SetWireframeThickness(wireframeThickness);\n            }\n        }\n        \n        ShadingMode shadingMode = renderer.GetShadingMode();\n        if (ImGuiComboFromArray(\"Shading Mode\", &shadingMode, kShadingModeNames))\n        {\n            renderer.SetShadingMode(shadingMode);\n        }\n\n        ColorMode colorMode = renderer.GetColorMode();\n        if (ImGuiComboFromArray(\"Color Mode\", &colorMode, kColorModeNames))\n        {\n            renderer.SetColorMode(colorMode);\n        }\n\n        ShadingMode effectiveShadingMode = renderer.GetEffectiveShadingMode();\n        if (effectiveShadingMode == ShadingMode::PT)\n        {\n            int maxBounces = std::max(1, std::min(10, renderer.GetPTMaxBounces()));\n            if (ImGui::InputInt(\"Max Bounces\", &maxBounces, 1, 10))\n            {\n                renderer.SetPTMaxBounces(maxBounces);\n            }\n\n            if (!denoiserEnabled)\n            {\n                float fireflyMaxIntensity = renderer.GetFireflyMaxIntensity();\n                if (ImGui::SliderFloat(\"Firefly Max Intensity\", &fireflyMaxIntensity, 0.f, 10.f))\n                {\n                    renderer.SetFireflyMaxIntensity(fireflyMaxIntensity);\n                }\n            }\n\n            float roughness = renderer.GetRoughnessOverride();\n            if (ImGui::SliderFloat(\"Roughness Override\", &roughness, 0.f, 1.f))\n            {\n                renderer.SetRoughnessOverride(roughness);\n            }\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n                ImGui::SetTooltip(\n                    \"Overrides the roughness coefficient for all the materials\\n\"\n                    \"in the scene. Useful for debugging materials.\\n\");\n        \n            float exposure = renderer.GetExposure();\n            if (ImGui::SliderFloat(\"Exposure\", &exposure, 0.f, 1000.f, \"%.3f\", ImGuiSliderFlags_Logarithmic))\n            {\n                renderer.SetExposure(exposure);\n            }\n\n            TonemapOperator tonemap = renderer.GetTonemapOperator();\n            if (ImGuiComboFromArray(\"Tonemapping Operator\", &tonemap, kToneMapOperatorNames))\n            {\n                renderer.SetTonemapOperator(tonemap);\n            }\n        }\n\n        BuildUIEnvmap(itemSize);\n    }\n    ImGui::PopStyleColor();\n    ImGui::Spacing();\n\n    ImGui::PushStyleColor(ImGuiCol_Header, UI_SAGE);\n    if (ImGui::CollapsingHeader(\"Tessellation\", ImGuiTreeNodeFlags_DefaultOpen))\n    {\n        TessellatorConfig::MemorySettings memSettings = m_app.GetTessMemSettings();\n\n        int32_t maxKClusters = memSettings.maxClusters >> 10u;\n        int vertexMB = int32_t(memSettings.vertexBufferBytes >> 20ull);\n        int clasMB = int32_t(memSettings.clasBufferBytes >> 20ull);\n\n        auto& stats = GetApp().m_BuildStats;\n\n        bool memSettingsChanged = false;\n\n        const float kFlashWarningPeriod = 1.0f;\n        float t = fmodf(float(ImGui::GetTime()), kFlashWarningPeriod);  // Flashes every second (0.5s on, 0.5s off)\n        bool shouldHighlight = (t < 0.5f);\n\n        bool highlightMaxClusters = shouldHighlight && stats.desired.m_numClusters > stats.allocated.m_numClusters;\n        bool highlightVertexMemory = shouldHighlight && (stats.desired.m_vertexBufferSize > stats.allocated.m_vertexBufferSize || \n            stats.desired.m_vertexNormalsBufferSize > stats.allocated.m_vertexNormalsBufferSize);\n        bool highlightClasMemory = shouldHighlight && stats.desired.m_clasSize > stats.allocated.m_clasSize;\n\n        const ImVec4 kHighlightColor = ImVec4(0.5f, 0.0f, 0.0f, 1.0f); // Red highlight\n        if (highlightMaxClusters)\n            ImGui::PushStyleColor(ImGuiCol_FrameBg, kHighlightColor);\n        if (ImGui::InputInt(\"Max Clusters (K)\", &maxKClusters, 64, 256, ImGuiInputTextFlags_EnterReturnsTrue))\n        {\n            memSettings.maxClusters = std::min((uint32_t(std::max(maxKClusters, 64)) << 10u), kMaxApiClusterCount);\n            memSettingsChanged = true;\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Max clusters in a scene which affects the size of cluster data buffer and BLAS memory\");\n        if (highlightMaxClusters)\n            ImGui::PopStyleColor();\n\n        if (highlightVertexMemory)\n            ImGui::PushStyleColor(ImGuiCol_FrameBg, kHighlightColor);\n        if (ImGui::InputInt(\"Vertex Memory (MB)\", &vertexMB, 128, 512, ImGuiInputTextFlags_EnterReturnsTrue))\n        {\n            memSettings.vertexBufferBytes = std::min(size_t(std::max(vertexMB, 128)), 8192ull) << 20ull;\n            memSettingsChanged = true;\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Max memory in MB allocated for tessellated vertices (positions + normals when enabled)\");\n        if (highlightVertexMemory)\n            ImGui::PopStyleColor();\n\n        if (highlightClasMemory)\n            ImGui::PushStyleColor(ImGuiCol_FrameBg, kHighlightColor);\n        if (ImGui::InputInt(\"CLAS Memory (MB)\", &clasMB, 128, 512, ImGuiInputTextFlags_EnterReturnsTrue))\n        {\n            memSettings.clasBufferBytes = std::min(size_t(std::max(clasMB, 128)), 8192ull) << 20ull;\n            memSettingsChanged = true;\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Max memory in megabytes allocated for cluster acceleration structures CLAS\");\n        if (highlightClasMemory)\n            ImGui::PopStyleColor();\n\n        if (memSettingsChanged)\n        {\n            m_app.SetTessMemSettings(memSettings);\n        }\n\n        int   clusterPattern = static_cast<int>(m_app.GetClusterTessellationPattern());\n        float comboBoxWidth = ImGui::GetTextLineHeightWithSpacing() + ImGui::CalcTextSize(\"Slanted  \").x\n            + ImGui::GetStyle().FramePadding.x * 2.0f;\n        ImGui::SetNextItemWidth(comboBoxWidth);\n        if (ImGui::Combo(\"Tess Pattern\", &clusterPattern, \"Regular\\0Slanted\\0\"))\n        {\n            m_app.SetClusterTessellationPattern(ClusterPattern(clusterPattern));\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\n                \"Toggles the 'slanted' grid pattern on. 'Slanted' grids\\n\"\n                \"allow for a smoother transition when the number of edge segments on\\n\"\n                \"opposite sides of a quad don't match.\\n\\n\");\n\n        ImGui::SameLine();\n        bool updateTessCamera = m_app.GetUpdateTessellationCamera();\n        if (ImGui::Checkbox(\"Update Tess Camera\", &updateTessCamera))\n        {\n            m_app.SetUpdateTessellationCamera(updateTessCamera);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Keep tessellation camera in sync. Uncheck to lock the camera.\");\n\n        bool enableFrustumVisibility = m_app.GetFrustumVisibilityEnabled();\n        if (ImGui::Checkbox(\"Frustum\", &enableFrustumVisibility))\n            m_app.SetFrustumVisibilityEnabled(enableFrustumVisibility);\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Offscreen geometry uses coarse tessellation rates\");\n\n        ImGui::SameLine();\n        bool enableHiZVisibility = m_app.GetHiZVisibilityEnabled();\n        if (ImGui::Checkbox(\"HiZ\", &enableHiZVisibility))\n            m_app.SetHiZVisibilityEnabled(enableHiZVisibility);\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Dynamic geometry occluded by static geometry uses coarse tessellation rate\");\n\n        TessellatorConfig::VisibilityMode visMode = m_app.GetTessellatorVisibilityMode();\n        if (visMode == TessellatorConfig::VisibilityMode::VIS_LIMIT_EDGES)\n        {\n            ImGui::SameLine();\n            bool enableBackFaceVisibility = m_app.GetBackfaceVisibilityEnabled();\n            if (ImGui::Checkbox(\"Backface\", &enableBackFaceVisibility))\n                m_app.SetBackfaceVisibilityEnabled(enableBackFaceVisibility);\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n                ImGui::SetTooltip(\"Back faces use coarse tessellation rate\");\n        }\n\n        float tessRates[] = { m_app.GetFineTessellationRate(), m_app.GetCoarseTessellationRate() };\n        if (ImGui::SliderFloat2(\"Fine | Coarse Tess Rate\", tessRates, 0.001f, 2.f))\n        {\n            if (tessRates[0] > 0.0f)\n            {\n                m_app.SetFineTessellationRate(tessRates[0]);\n            }\n\n            if (tessRates[1] > 0.0f)\n            {\n                m_app.SetCoarseTessellationRate(tessRates[1]);\n            }\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Tessellation rates for metric:\\n\"\n                \"- Fine: Affects primary visible ray geometry\\n\"\n                \"- Coarse: Affects \\\"culled\\\" geometry: offscreen, backfacing, occluded\\n\");\n\n        TessellatorConfig::AdaptiveTessellationMode tessMode = m_app.GetAdaptiveTessellationMode();\n        if (ImGuiComboFromArray(\"Tessellation Metric\", &tessMode, kAdaptiveTessellationModeNames))\n        {\n            m_app.SetAdaptiveTessellationMode(tessMode);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\n                \"Tessellation metrics:\\n\\n\"\n                \"- Uniform: uniform tessellation factors (all clusters have the\\n\"\n                \"  same number of triangles).\\n\\n\"\n                \"- World space edge length: tessellation factors are derived from\\n\"\n                \"  the length of the control cage edges in world space (independent\\n\"\n                \"  the camera position).\\n\\n\"\n                \"- Spherical projection: tessellation factors are derived from the\\n\"\n                \"  length of the control cage edges scaled by their distance to the\\n\"\n                \"  camera location.\\n\");\n\n        \n        if (ImGuiComboFromArray(\"Visibility Mode\", &visMode, kVisibilityModeNames))\n        {\n            m_app.SetTessellatorVisibilityMode(visMode);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\n                \"Tessellation visibility predicates:\\n\\n\"\n                \"- Surface 1-Ring: uses the 1-ring control cage of a surface to derive\\n\"\n                \"  visibility for an entire surface\\n\\n\"\n                \"- Limit edge: generates visibility predicate for each limit edge a\\n\"\n                \"  surface only.\\n\");\n       \n        int isolationLevel = m_app.GetGlobalIsolationLevel();\n        if (ImGui::SliderInt(\"Global Isolation Level\", &isolationLevel, 1, 6))\n        {\n            m_app.SetGlobalIsolationLevel(uint32_t(isolationLevel));\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Global isolation level for all meshes.\\n\"\n                \"- Needs to be >= the highest finite sharpness, but the default of 6 is sufficient.\\n\"\n                \"- Finite sharpness is any sharpness < 10.0f.\\n\"\n                \"- Infinite sharpness = 10.0f has an optimization that doesn't require isolation\\n\");\n\n        float displacementScale = m_app.GetDisplacementScale();\n        if (ImGui::SliderFloat(\"Displacement Scale\", &displacementScale, 0.0f, 3.0f))\n        {\n            m_app.SetDisplacementScale(displacementScale);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Scaling factor for displacement maps\");\n\n        bool vertexNormalsEnabled = m_app.GetVertexNormalsEnabled();\n        if (ImGui::Checkbox(\"Vertex Normals\", &vertexNormalsEnabled))\n        {\n            m_app.SetVertexNormalsEnabled(vertexNormalsEnabled);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Enable computation of vertex normals from surface derivatives\");\n    }\n    ImGui::PopStyleColor();\n\n    ImGui::Spacing();\n\n#if DONUT_WITH_STREAMLINE\n    ImGui::PushStyleColor(ImGuiCol_Header, UI_SAGE);\n    if (!renderer.GetShowMicroTriangles() && ImGui::CollapsingHeader(\"Denoiser and Upscaling\", ImGuiTreeNodeFlags_DefaultOpen))\n    {\n        using StreamlineInterface = donut::app::StreamlineInterface;\n\n        DenoiserMode denoiserMode = m_app.GetDenoiserMode();\n#if ENABLE_DLSS_SR\n        if (ImGui::Combo(\"Denoiser Mode\", (int*)&denoiserMode, \"None\\0DLSS-SR\\0DLSS-RR\\0\"))\n        {\n            m_app.SetDenoiserMode(denoiserMode);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Set Denoiser mode\");\n#else\n        bool isDlssEnabled = denoiserMode == DenoiserMode::DlssRr;\n        if (ImGui::Checkbox(\"Enable DLSS-RR\", &isDlssEnabled))\n        {\n            m_app.SetDenoiserMode(isDlssEnabled ? DenoiserMode::DlssRr : DenoiserMode::None);\n        }\n        if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            ImGui::SetTooltip(\"Enable DLSS-RR for upscale and denoising\");\n#endif\n\n        if (denoiserMode != DenoiserMode::None)\n        {\n            if (denoiserMode == DenoiserMode::DlssSr ||\n                denoiserMode == DenoiserMode::DlssRr)\n            {\n                const std::array<std::pair<StreamlineInterface::DLSSMode, const char*>, 5> kVisibleDlssModes = { {\n                    {StreamlineInterface::DLSSMode::eUltraPerformance, \"Ultra-Performance\"},\n                    {StreamlineInterface::DLSSMode::eMaxPerformance, \"Performance\"},\n                    {StreamlineInterface::DLSSMode::eBalanced, \"Balanced\"},\n                    {StreamlineInterface::DLSSMode::eMaxQuality, \"Quality\"},\n                    {StreamlineInterface::DLSSMode::eDLAA, \"DLAA\"}\n                } };\n\n                auto iter = std::find_if(kVisibleDlssModes.begin(), kVisibleDlssModes.end(), [&uiData](auto& m) { return m.first == uiData.dlssMode; });\n                if (iter == kVisibleDlssModes.end())\n                {\n                    // Reset to eMaxQuality if we can't find the option\n                    uiData.dlssMode = StreamlineInterface::DLSSMode::eMaxQuality;\n                    iter = std::find_if(kVisibleDlssModes.begin(), kVisibleDlssModes.end(), [&uiData](auto& m) { return m.first == uiData.dlssMode; });\n                }\n\n                if (ImGui::BeginCombo(\"DLSS Mode\", iter->second))\n                {\n                    for (const auto& mode : kVisibleDlssModes)\n                    {\n                        bool isSelected = (mode.first == uiData.dlssMode);\n                        if (ImGui::Selectable(mode.second, isSelected))\n                        {\n                            uiData.dlssMode = mode.first;\n                        }\n                        if (isSelected) ImGui::SetItemDefaultFocus();\n                    }\n                    ImGui::EndCombo();\n                }\n\n#if ENABLE_DLSS_DEV_FEATURE\n                if (uiData.dlssMode != StreamlineInterface::DLSSMode::eUltraQuality &&\n                    uiData.dlssMode != StreamlineInterface::DLSSMode::eOff)\n                {\n                    std::array<const char*, 7> kDlssPresetNames = {\n                        \"Default\",\n                        \"Preset A\",\n                        \"Preset B\",\n                        \"Preset C\",\n                        \"Preset D\",\n                        \"Preset E\",\n                        \"Preset F\"\n                    };\n\n                    std::array<const char*, 7> kDlssRRPresetNames = {\n                        \"Default\",\n                        \"Preset A\",\n                        \"Preset B\",\n                        \"Preset C\",\n                        \"Preset D\",\n                        \"Preset E\",\n                        \"Preset G\"\n                    };\n\n                    if (denoiserMode == DenoiserMode::DlssSr)\n                    {\n                        if (ImGui::BeginCombo(\"DLSS SR Preset\", kDlssPresetNames[(int)uiData.dlssPreset]))\n                        {\n                            for (int i = 0; i < kDlssPresetNames.size(); ++i)\n                            {\n                                bool isSelected = i == static_cast<int>(uiData.dlssPreset);\n\n                                if (ImGui::Selectable(kDlssPresetNames[i], isSelected)) uiData.dlssPreset = (StreamlineInterface::DLSSPreset)i;\n                                if (isSelected) ImGui::SetItemDefaultFocus();\n                            }\n                            ImGui::EndCombo();\n                        }\n                    }\n                    else\n                    {\n                        if (ImGui::BeginCombo(\"DLSS RR Preset\", kDlssRRPresetNames[(int)uiData.dlssRRPreset]))\n                        {\n                            for (int i = 0; i < kDlssRRPresetNames.size(); ++i)\n                            {\n                                bool isSelected = i == static_cast<int>(uiData.dlssRRPreset);\n\n                                if (ImGui::Selectable(kDlssRRPresetNames[i], isSelected)) uiData.dlssRRPreset = (StreamlineInterface::DLSSRRPreset)i;\n                                if (isSelected) ImGui::SetItemDefaultFocus();\n                            }\n                            ImGui::EndCombo();\n                        }\n                    }\n                }\n\n                ImGui::Checkbox(\"Overide LOD Bias\", &uiData.dlssUseLodBiasOverride);\n                if (uiData.dlssUseLodBiasOverride)\n                {\n                    ImGui::SameLine();\n                    ImGui::SliderFloat(\"\", &uiData.dlssLodBiasOverride, -2, 2);\n                }\n#endif\n            }\n\n            if (ImGui::BeginCombo(\"Output\", renderer.GetOutputLabel(renderer.GetOutputIndex()),\n                ImGuiComboFlags_HeightLarge))\n            {\n                for (uint32_t outputIndex = uint32_t(RTXMGRenderer::Output::Accumulation);\n                    outputIndex < uint32_t(RTXMGRenderer::Output::Count);\n                    outputIndex++)\n                {\n                    bool isSelected = outputIndex == uint32_t(renderer.GetOutputIndex());\n\n                    if (ImGui::Selectable(renderer.GetOutputLabel(RTXMGRenderer::Output(outputIndex)), isSelected))\n                    {\n                        renderer.SetOutputIndex(RTXMGRenderer::Output(outputIndex));\n                        renderer.ResetSubframes();\n                    }\n                    if (isSelected)\n                        ImGui::SetItemDefaultFocus();\n                }\n                ImGui::EndCombo();\n            }\n\n            float denoiserSeparator = renderer.GetDenoiserSeparator();\n            if (ImGui::SliderFloat(\"Output | Denoised\", &denoiserSeparator, 0.0f, 1.0f, \"%.2f\"))\n            {\n                renderer.SetDenoiserSeparator(denoiserSeparator);\n            }\n\n            MvecDisplacement mvecDisplacement = renderer.GetMVecDisplacement();\n            if (ImGuiComboFromArray(\"Motion Vectors\", &mvecDisplacement, kMvecDisplacementNames))\n            {\n                renderer.SetMvecDisplacement(mvecDisplacement);\n            }\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n            {\n                ImGui::SetTooltip(\n                    \"Motion Vectors Calculation Mode:\\n\\n\"\n                    \"- From Subd Eval: Compute displacement using the delta between\\n\"\n                    \"  gbuffer hit point and current frame limit surface.\\n\"\n                    \"  Expensive since it re-evalutes limit surface again, but compensates for tess rates\\n\\n\"\n                    \"- From Material: Resample displacement from texture and apply to prev frame limit surface\\n\"\n                    \"  If tess rates vary then there can be a mismatch with the current frame hit point.\\n\");\n            }\n        }\n    }\n    ImGui::PopStyleColor();\n    ImGui::Spacing();\n#endif\n    ImGui::PopItemWidth();\n    ImGui::End();\n\n    m_profiler.fps = (int)(1000.f / (float)m_app.GetCPUFrameTime());\n    m_profiler.desiredTris = stats::clusterAccelSamplers.numTriangles.latest;\n    m_profiler.allocatedTris = stats::clusterAccelSamplers.numTriangles.max;\n    m_profiler.desiredClusters = stats::clusterAccelSamplers.numClusters.latest;\n    m_profiler.allocatedClusters = stats::clusterAccelSamplers.numClusters.max;\n\n    m_profiler.controllerWindow = {\n        .pos = ImVec2(float(screenLayoutSize.x) - 10.f, float(screenLayoutSize.y) - 10.f),\n        .pivot = ImVec2(1.f, 1.f),\n        .size = ImVec2(115, 0)\n    };\n\n    m_profiler.profilerWindow = {\n        .pos = ImVec2(float(screenLayoutSize.x) - 10.f, 10.f),\n        .pivot = ImVec2(1.f, 0.f),\n        .size = ImVec2(800.f, 450.f),\n        .screenLayoutSize = ImVec2(float(screenLayoutSize.x), float(screenLayoutSize.y))\n    };\n\n    m_profiler.BuildUI<stats::FrameSamplers, stats::ClusterAccelSamplers, stats::EvaluatorSamplers, stats::MemUsageSamplers>(m_iconicFont, m_implot,\n        stats::frameSamplers, stats::clusterAccelSamplers, stats::evaluatorSamplers, stats::memUsageSamplers);\n\n    if (stats::evaluatorSamplers.m_topologyQualityButtonPressed)\n    {\n        renderer.SetColorMode(ColorMode::COLOR_BY_TOPOLOGY);\n    }\n\n    float profilerWidth = m_profiler.controllerWindow.size.x;\n    float timelineWidth = float(screenLayoutSize.x) - 30.f - profilerWidth;\n\n    BuildUITimeline(screenLayoutSize, timelineWidth);\n\n    BuildMemoryWarning(screenLayoutSize);\n}\n\nvoid UserInterface::BuildMemoryWarning(int2 screenLayoutSize)\n{\n    auto& stats = GetApp().m_BuildStats;\n    bool clusterCountExceeded = stats.desired.m_numClusters > stats.allocated.m_numClusters;\n    bool clasMemoryExceeded = stats.desired.m_clasSize > stats.allocated.m_clasSize;\n    bool vertexMemoryExceeded = stats.desired.m_vertexBufferSize > stats.allocated.m_vertexBufferSize;\n    bool vertexNormalsMemoryExceeded = stats.desired.m_vertexNormalsBufferSize > stats.allocated.m_vertexNormalsBufferSize;\n\n    if (!clusterCountExceeded && !clasMemoryExceeded && !vertexMemoryExceeded && !vertexNormalsMemoryExceeded)\n        return;\n\n    ImVec2 overlayPos(screenLayoutSize.x * 0.5f, 10.0f); // Center X, 10px from the top\n    ImVec2 overlayPivot(0.5f, 0.0f); // Center horizontally, stick to the top\n\n    ImGui::SetNextWindowPos(overlayPos, ImGuiCond_Always, overlayPivot);\n\n    ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32(128, 0, 0, 200));  // Dark red\n    ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255));    // White text\n\n    ImGui::Begin(\"Memory Exceeded\", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |\n                  ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize);\n\n    ImGui::PushFont(m_nvidiaBldFont);\n    ImGui::Text(\"Tessellation memory budget exceeded\");\n    ImGui::PopFont();\n    ImGui::Text(\"Expect flickering. Increase memory budget in settings\");\n\n    if (clasMemoryExceeded)\n    {\n        char bufDesired[64];\n        char bufAllocated[64];\n        MemoryFormatter(stats.desired.m_clasSize, bufDesired, sizeof(bufDesired));\n        MemoryFormatter(stats.allocated.m_clasSize, bufAllocated, sizeof(bufDesired));\n        ImGui::Text(\"CLAS %s / %s\", bufDesired, bufAllocated);\n    }\n\n    if (clusterCountExceeded)\n    {\n        char bufDesired[64];\n        char bufAllocated[64];\n        HumanFormatter(stats.desired.m_numClusters, bufDesired, sizeof(bufDesired));\n        HumanFormatter(stats.allocated.m_numClusters, bufAllocated, sizeof(bufDesired));\n        ImGui::Text(\"Cluster Count %s/%s\", bufDesired, bufAllocated);\n\n        MemoryFormatter(stats.desired.m_clusterDataSize, bufDesired, sizeof(bufDesired));\n        MemoryFormatter(stats.allocated.m_clusterDataSize, bufAllocated, sizeof(bufDesired));\n        ImGui::Text(\"Cluster Data %s/%s\", bufDesired, bufAllocated);\n    }\n\n    if (vertexMemoryExceeded)\n    {\n        char bufDesired[64];\n        char bufAllocated[64];\n        MemoryFormatter(stats.desired.m_vertexBufferSize, bufDesired, sizeof(bufDesired));\n        MemoryFormatter(stats.allocated.m_vertexBufferSize, bufAllocated, sizeof(bufDesired));\n        ImGui::Text(\"Vertex Buffer %s / %s\", bufDesired, bufAllocated);\n    }\n\n    if (vertexNormalsMemoryExceeded)\n    {\n        char bufDesired[64];\n        char bufAllocated[64];\n        MemoryFormatter(stats.desired.m_vertexNormalsBufferSize, bufDesired, sizeof(bufDesired));\n        MemoryFormatter(stats.allocated.m_vertexNormalsBufferSize, bufAllocated, sizeof(bufDesired));\n        ImGui::Text(\"Vertex Normals Buffer %s / %s\", bufDesired, bufAllocated);\n    }\n    ImGui::End();\n\n    ImGui::PopStyleColor(2);  // Restore previous colors\n}\n\nvoid UserInterface::BuildUITimeline(int2 screenLayoutSize, float timelineWidth)\n{\n    auto& state = GetApp().GetUIData().timeLineEditorState;\n\n    if (state.frameRate == 0.0f || (state.frameRange.y - state.frameRange.x) == 0)\n        return;\n\n    float tw = timelineWidth;\n    ImGui::SetNextWindowPos(ImVec2(tw + 10.f, float(screenLayoutSize.y) - 10.f), 0,\n        ImVec2(1.f, 1.f));\n    ImGui::SetNextWindowSize(ImVec2(tw, 0.f));\n    ImGui::SetNextWindowBgAlpha(.65f);\n    if (ImGui::Begin(\"TimeLine Editor\", nullptr,\n        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration |\n        ImGuiWindowFlags_NoTitleBar))\n    {\n        if (BuildTimeLineEditor(state, float2(tw, 0.f)))\n        {\n        }\n    }\n    ImGui::End();\n}\n\nvoid UserInterface::BuildUIEnvmap(ImVec2 itemSize)\n{\n    auto& renderer = m_app.GetRenderer();\n\n    if (ImGui::CollapsingHeader(\"Environment Map\", ImGuiTreeNodeFlags_DefaultOpen))\n    {\n        if (renderer.GetEffectiveShadingMode() == ShadingMode::PT)\n        {\n            ImGui::PushFont(m_iconicFont);\n\n            if (ImGui::Button((char const*)(u8\"\\ue06b\" \"## env map\"), { 0.f, itemSize.y }))\n            {\n                if (FileDialog(true, \"All files\\0*.*\\0EXR files\\0*.exr\\0HDR files\\0*.hdr\\0\\0\", GetApp().GetUIData().envmapFilepath))\n                {\n                    const std::string filePath = GetApp().GetUIData().envmapFilepath;\n                    if (!filePath.empty())\n                        m_app.SetEnvmapTex(filePath);\n                }\n            }\n            ImGui::PopFont();\n            ImGui::SameLine();\n\n            float buttonWidth = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;\n            ImGui::SetNextItemWidth(kItemWidth - buttonWidth);\n\n            std::shared_ptr<engine::LoadedTexture> envmap = renderer.GetEnvMap();\n            GetApp().GetUIData().envmapFilepath = \"\";\n            GetApp().GetUIData().envmap = envmap;\n            if (envmap)\n            {\n                GetApp().GetUIData().envmapFilepath = envmap->path;\n            }\n\n            char buf[1024] = { 0 };\n            if (renderer.GetEnvMap() != nullptr)\n            {\n                std::strncpy(buf, renderer.GetEnvMap()->path.c_str(), std::size(buf));\n            }\n            if (ImGui::InputText(\"Env Map\", buf, std::size(buf), ImGuiInputTextFlags_EnterReturnsTrue))\n            {\n                if (m_app.SetEnvmapTex(std::string(buf)))\n                    GetApp().GetUIData().envmapFilepath = buf;\n            }\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f && !GetApp().GetUIData().envmapFilepath.empty())\n                ImGui::SetTooltip(\"Path to HDR environment map.\");\n\n            if (renderer.GetEnvMap() != nullptr)\n            {\n                static const char* debugGlyph = (char*)(u8\"\\ue028\" \"## envmap debug\");\n\n                bool debugView = !GetApp().GetUIData().envmapFilepath.empty() && renderer.GetEnableEnvmapHeatmap();\n\n                ImGui::SameLine();\n                if (debugView)\n                    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.f, 0.f, 0.f, 1.f));\n                ImGui::PushFont(m_iconicFont);\n                if (ImGui::Button(debugGlyph, { 20.f, itemSize.y }))\n                {\n                    renderer.SetEnableEnvmapHeatmap(!debugView);\n                }\n                if (debugView)\n                    ImGui::PopStyleColor();\n                ImGui::PopFont();\n                if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n                    ImGui::SetTooltip(\"Heatmap of The Envmap Impostance Sampling.\");\n\n                float intensity = renderer.GetEnvMapIntensity();\n                if (ImGui::SliderFloat(\"Intensity\", &intensity, .001f, 2.f))\n                {\n                    renderer.SetEnvMapIntensity(intensity);\n                }\n                if (ImGui::IsItemHovered() && m_imgui->HoveredIdTimer > .5f)\n                    ImGui::SetTooltip(\"Intensity Scale\");\n\n                float azimuth = 180.f * renderer.GetEnvMapAzimuth() / M_PIf;\n                if (ImGui::SliderFloat(\"Azimuth\", &azimuth, 0.f, 360.f))\n                {\n                    renderer.SetEnvMapAzimuth((azimuth / 180.f) * M_PIf);\n                }\n                if (ImGui::IsItemHovered() && m_imgui->HoveredIdTimer > .5f)\n                    ImGui::SetTooltip(\"Rotation Around Y Axis\");\n\n                float elevation = 180.f * renderer.GetEnvMapElevation() / M_PIf;\n                if (ImGui::SliderFloat(\"Elevation\", &elevation, -90.f, 90.f))\n                {\n                    renderer.SetEnvMapElevation((elevation / 180.f) * M_PIf);\n                }\n                if (ImGui::IsItemHovered() && m_imgui->HoveredIdTimer > .5f)\n                    ImGui::SetTooltip(\"Rotation Around X Axis\");\n            }\n            else\n            {\n                BuildMissColorUI();\n            }\n        }\n        else\n        {\n            BuildMissColorUI();\n        }\n    }\n}\n\nvoid UserInterface::BuildMissColorUI()\n{\n    auto& renderer = m_app.GetRenderer();\n\n    float3 missColor = renderer.GetMissColor();\n    if (ImGui::ColorEdit3(\"Miss Color\", &missColor.x, ImGuiColorEditFlags_Float | ImGuiColorEditFlags_PickerHueWheel))\n    {\n        renderer.SetMissColor(missColor);\n    }\n}\n\nvoid UserInterface::Animate(float elapsedTimeSeconds)\n{\n    ImGui_Renderer::Animate(elapsedTimeSeconds);\n\n    if (GetApp().GetUIData().timeLineEditorState.IsPlaying())\n    {\n        GetApp().GetUIData().timeLineEditorState.Update(elapsedTimeSeconds);\n    }\n}\n\nbool UserInterface::CustomInit(std::shared_ptr<engine::ShaderFactory> shaderFactory)\n{\n    // guaranteed to be called after first scene is loaded, so the attributes are available.\n\n\n    const RTXMGScene::Attributes& attrs = m_app.GetScene().GetAttributes();\n\n    SetAnimationRange(attrs.frameRange, attrs.frameRate);\n\n    if (!attrs.audio.empty())\n        SetupAudioVoice(attrs.audio, GetApp().GetUIData().audioStartTime = attrs.audioStartTime);\n\n    return Init(shaderFactory);\n}\n\nbool UserInterface::BuildTimeLineEditor(TimeLineEditorState& state,\n    float2 size)\n{\n    assert(state.startTime <= state.endTime);\n\n    float fontScale = ImGui::GetIO().FontGlobalScale;\n\n    static float const buttonPanelWidth =\n        200 + fontScale * 230; // assumes a text font-m_size of ~ 14.f\n\n    bool result = false;\n\n    // current time slider\n\n    ImGui::SetNextItemWidth(\n        size.x -\n        buttonPanelWidth); // anchor the button panel to the right of the window\n    float currentTime = state.currentTime;\n    if (ImGui::SliderFloat(\"##Time\", &currentTime, state.startTime, state.endTime,\n        \"%.3f s.\"))\n    {\n        state.currentTime = clamp(currentTime, state.startTime, state.endTime);\n        if (state.setTimeCallback)\n            state.setTimeCallback(state);\n        result = true;\n    }\n    ImVec2 sliderSize = ImGui::GetItemRectSize();\n    ImGui::SameLine();\n\n    // current frame number (editable)\n    ImGui::SetNextItemWidth(fontScale * 45.f);\n    float currentFrame = state.currentTime * state.frameRate;\n    if (ImGui::InputFloat(\"##CurrentFrame\", &currentFrame, 0.f, 0.f, \"%.1f\"))\n    {\n        state.currentTime =\n            clamp(currentFrame / state.frameRate, state.startTime, state.endTime);\n        if (state.setTimeCallback)\n            state.setTimeCallback(state);\n        result = true;\n    }\n    ImGui::SameLine();\n    if (ImGui::IsItemHovered())\n        ImGui::SetTooltip(\"Current frame of animation sequence.\\n\");\n\n    // start & end frame numbers (read-only)\n    float frameStart = state.startTime * state.frameRate;\n    ImGui::SetNextItemWidth(fontScale * 45.f);\n    ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.5f, .5f, .5f, 1.f));\n    ImGui::InputFloat(\"##FrameStart\", &frameStart, 0.f, 0.f, \"%.1f\",\n        ImGuiInputTextFlags_ReadOnly);\n    ImGui::PopStyleColor();\n    ImGui::SameLine();\n    if (ImGui::IsItemHovered())\n        ImGui::SetTooltip(\"First frame of animation sequence.\\n\");\n\n    float frameEnd = state.endTime * state.frameRate;\n    ImGui::SetNextItemWidth(fontScale * 45.f);\n    ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.5f, .5f, .5f, 1.f));\n    ImGui::InputFloat(\"##FrameEnd\", &frameEnd, 0.f, 0.f, \"%.1f\",\n        ImGuiInputTextFlags_ReadOnly);\n    ImGui::PopStyleColor();\n    ImGui::SameLine(0.f, 10.f);\n    if (ImGui::IsItemHovered())\n        ImGui::SetTooltip(\"Last frame of animation sequence.\\n\");\n\n    // playback media buttons\n    static const char* playGlyph = (char*)u8\"\\ue093\";\n    static const char* pauseGlyph = (char*)u8\"\\ue092\";\n    static const char* skip_backGlyph = (char*)u8\"\\ue097\";\n    static const char* skip_fwdGlyph = (char*)u8\"\\ue098\";\n    static const char* rewindGlyph = (char*)u8\"\\ue095\";\n    static const char* fast_fwdGlyph = (char*)u8\"\\ue096\";\n    static const char* repeatGlyph = (char*)u8\"\\ue08e\";\n\n    ImGui::PushFont(m_iconicFont);\n    if (ImGui::Button(rewindGlyph, ImVec2(0.f, sliderSize.y)))\n    {\n        state.Rewind();\n        result = true;\n    }\n    ImGui::SameLine();\n\n    if (ImGui::Button(skip_backGlyph, ImVec2(0.f, sliderSize.y)))\n    {\n        state.StepBackward();\n        result = true;\n    }\n    ImGui::SameLine();\n\n    bool paused = state.IsPaused();\n    if (paused)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.f, 0.f, 0.f, 1.f));\n    if (ImGui::Button(paused ? playGlyph : pauseGlyph, { 0.f, sliderSize.y }))\n    {\n        state.PlayClicked();\n    }\n    if (paused)\n        ImGui::PopStyleColor();\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(skip_fwdGlyph, ImVec2(0.f, sliderSize.y)))\n    {\n        state.StepForward();\n        result = true;\n    }\n    ImGui::SameLine();\n\n    if (ImGui::Button(fast_fwdGlyph, ImVec2(0.f, sliderSize.y)))\n    {\n        state.FastForward();\n        result = true;\n    }\n    ImGui::SameLine(0.f, 10.f);\n\n    bool loop = state.loop;\n    if (loop)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.03f, .08f, .3f, 1.f));\n    if (ImGui::Button(repeatGlyph, { 0.f, sliderSize.y }))\n        state.loop = !loop;\n    if (loop)\n        ImGui::PopStyleColor();\n    ImGui::PopFont();\n    if (ImGui::IsItemHovered())\n        ImGui::SetTooltip(\"Loop animation sequence.\\n\");\n\n    ImGui::SameLine(0.f, 10.f);\n\n    float frameRate = state.frameRate;\n    ImGui::SetNextItemWidth(fontScale * 40.f);\n    if (ImGui::InputFloat(\"##FrameRate\", &frameRate, 0.f, 0.f, \"%.1f\"))\n    {\n        if (state.frameRange.y > state.frameRange.x && frameRate > 0)\n        {\n            state.startTime = float(state.frameRange.x) / frameRate;\n            state.endTime = float(state.frameRange.y) / frameRate;\n        }\n        else if (frameRate == 0.f)\n        {\n            state.startTime = float(state.frameRange.x);\n            state.endTime = float(state.frameRange.y);\n        }\n        state.frameRate = frameRate;\n    }\n    if (ImGui::IsItemHovered())\n        ImGui::SetTooltip(\"Animation frame rate (in frames per seconds).\\n\");\n\n    return result;\n}\n\nvoid UserInterface::LoadAsset(MediaAsset const& asset, std::string const& name,\n    int2 frameRange)\n{\n    bool isSequence = asset.IsSequence();\n\n    const fs::path& mediapath = m_app.GetMediaPath();\n\n    std::string shapePath =\n        (mediapath /\n            (isSequence ? fs::path(asset.sequenceFormat) : fs::path(name)))\n        .generic_string();\n\n    m_app.HandleSceneLoad(shapePath, mediapath.generic_string(), frameRange);\n\n    const RTXMGScene::Attributes& attrs = m_app.GetScene().GetAttributes();\n\n    SetAnimationRange(attrs.frameRange, attrs.frameRate);\n\n    if (!attrs.audio.empty())\n        SetupAudioVoice(attrs.audio, GetApp().GetUIData().audioStartTime = attrs.audioStartTime);\n    else\n        SetupAudioVoice(asset.wavePath, GetApp().GetUIData().audioStartTime = asset.waveStartTime);\n\n    GetApp().GetUIData().showRecommendationWindow = true;\n\n    GetApp().GetUIData().currentAsset = &asset;\n}\n\nvoid UserInterface::SetAnimationRange(int2 frameRange, float frameRate)\n{\n    float startTime = 0.f;\n    float endTime = 0.f;\n\n    if (frameRange.y > frameRange.x)\n    {\n        startTime = float(frameRange.x) / frameRate;\n        endTime = float(frameRange.y) / frameRate;\n    }\n    else\n    {\n        assert(frameRate == 0.f);\n        startTime = endTime = frameRate = 0.f;\n    }\n    auto& editor = GetApp().GetUIData().timeLineEditorState;\n    editor.frameRange = frameRange;\n    editor.startTime = startTime;\n    editor.endTime = endTime;\n    editor.currentTime = startTime;\n    editor.frameRate = frameRate;\n}\n\n// null-terminated array of filter strings\n\nstd::array<char const*, 4> UIData::formatFilters() const\n{\n    std::array<char const*, 4> filters;\n\n    std::fill(filters.begin(), filters.end(), nullptr);\n\n    int idx = 0;\n    if (includeJsonAssets)\n        filters[idx++] = \".json\";\n    if (includeObjAssets)\n        filters[idx++] = \".obj\";\n\n    //Future: include unstructured assets\n    //if (includeEddAssets)\n    //    filters[idx++] = \".eddbin\";\n\n    assert(filters.back() == nullptr);\n\n    return filters;\n}\n\nstd::array<char const*, 5> UIData::folderFilters() const\n{\n    std::array<char const*, 5> filters;\n\n    std::fill(filters.begin(), filters.end(), nullptr);\n\n    int idx = 0;\n\n    // Unused: way to filter which scenes are excluded from the scene selector\n    // Example of use (exclude any files that include \"do_not_show\" in their path:\n    // if (!includePrivateAssets)\n    //     filters[idx++] = \"do_not_show\";\n\n    assert(filters.back() == nullptr);\n\n    return filters;\n}\n\nstatic inline bool isFolderFiltered(fs::path const& p,\n    char const* const* filters)\n{\n    for (char const* const* filter = filters; *filter != nullptr; ++filter)\n        if (p.generic_string().find(*filter) != std::string::npos)\n            return true;\n    return false;\n}\n\nstatic inline bool isFormatFiltered(fs::path const& ext,\n    char const* const* filters)\n{\n    for (char const* const* filter = filters; *filter != nullptr; ++filter)\n        if (ext == *filter)\n            return true;\n    return false;\n}\n\nstatic void postProcessMediaAssets(MediaAssetsMap& assets)\n{\n    for (auto& asset : assets)\n    {\n        if (asset.first.find(\"barbarian\") != std::string::npos)\n        {\n            asset.second.frameRate = 30.f;\n        }\n        else if (asset.first.find(\"rain_restaurant\") != std::string::npos)\n        {\n            // Amy's monologue starts around frame 75, and we need to cut\n            // some silence at the beginning.\n            asset.second.waveStartTime = (100.f / 24.f) - 1.083f;\n        }\n    }\n}\nMediaAssetsMap\nUserInterface::FindMediaAssets(fs::path const& mediapath,\n    char const* const* folderFilters,\n    char const* const* formatFilters)\n{\n    auto ToInt = [](std::string_view str) -> std::optional<int>\n        {\n            int value = 0;\n            if (std::from_chars(str.data(), str.data() + str.size(), value).ec ==\n                std::errc{})\n                return value;\n            return {};\n        };\n\n    auto IsPadded = [](std::string_view digits) { return digits[0] == '0'; };\n\n    auto GetSequenceStr = [](std::string const& str) -> std::string_view\n        {\n            if (auto last = std::find_if(str.rbegin(), str.rend(), ::isdigit);\n                last != str.rend())\n                if (auto first = std::find_if(last, str.rend(),\n                    [](char c) { return !std::isdigit(c); });\n                    first != str.rend())\n                    return { first.base(), last.base() };\n            return {};\n        };\n\n    if (!fs::is_directory(mediapath))\n        return {};\n\n    MediaAssetsMap assets;\n\n    auto InsertAsset = [&mediapath, &assets](fs::path const& rp,\n        std::string const& name = {})\n        {\n            auto [it, success] =\n                assets.insert({ name.empty() ? rp.generic_string() : name, {} });\n            assert(success);\n            it->second.name = it->first;\n            return it;\n        };\n\n    auto opts = std::filesystem::directory_options::follow_directory_symlink;\n    for (auto it = fs::recursive_directory_iterator(mediapath, opts);\n        it != fs::recursive_directory_iterator(); ++it)\n    {\n        if (it->is_directory() && isFolderFiltered(it->path(), folderFilters))\n            it.disable_recursion_pending();\n\n        if (!isFormatFiltered(it->path().extension(), formatFilters))\n        {\n            continue;\n        }\n\n        fs::path rp = fs::relative(it->path(), mediapath).lexically_normal();\n\n        std::string stem = rp.stem().generic_string();\n\n        if (std::string_view seq = GetSequenceStr(stem); !seq.empty())\n        {\n            auto number = ToInt(seq);\n            if (!number)\n                continue;\n\n            std::string name =\n                (rp.parent_path() / std::string_view(stem.data(), seq.data()))\n                .generic_string();\n\n            auto it = assets.find(name);\n\n            if (it == assets.end())\n            {\n                it = InsertAsset(rp, name);\n            }\n\n            if (IsPadded(seq))\n                it->second.padding = std::max(it->second.padding, (int)seq.size());\n            it->second.type = MediaAsset::Type::OBJ_SEQUENCE;\n            it->second.GrowFrameRange(*number);\n        }\n        else\n        {\n            InsertAsset(rp);\n        }\n    }\n\n    for (auto it = assets.begin(); it != assets.end();)\n    {\n        auto& asset = *it;\n\n        if (asset.second.IsSequence())\n        {\n            char buf[1024];\n            if (asset.second.frameRange.x < asset.second.frameRange.y)\n            {\n                std::snprintf(buf, std::size(buf), \"%s[%d-%d].obj\", asset.first.c_str(),\n                    asset.second.frameRange.x, asset.second.frameRange.y);\n                asset.second.sequenceName = buf;\n\n                if (asset.second.padding > 0)\n                    std::snprintf(buf, std::size(buf), \"%s%%0%dd.obj\",\n                        asset.first.c_str(), asset.second.padding);\n                else\n                    std::snprintf(buf, std::size(buf), \"%s%%d.obj\", asset.first.c_str());\n                asset.second.sequenceFormat = buf;\n\n                asset.second.frameRate = 24.f;\n\n                // check for a wave audio file\n                std::snprintf(buf, std::size(buf), \"%s%d.wav\", asset.first.c_str(),\n                    asset.second.frameRange.x);\n                if (fs::is_regular_file(mediapath / buf))\n                    it->second.wavePath = buf;\n\n                it = std::next(it);\n            }\n            else // WAR for a single obj file whose name ends in a number being\n                // mistaken for an animation keyframe\n            {\n                std::snprintf(buf, std::size(buf), \"%s%d.obj\", asset.first.c_str(),\n                    asset.second.frameRange.x);\n                MediaAsset asset = { .frameRange = {0, 0}, .frameRate = 0.f };\n                std::swap(assets[buf], asset);\n                it = assets.erase(it);\n            }\n        }\n        else\n        {\n            // this could be a json file, which the GUI doesn't parse\n            // so we need to set it to an invalid frame range\n            it->second.frameRange = { std::numeric_limits<int>::max(), std::numeric_limits<int>::min() };\n            it->second.frameRate = 0.f;\n            it = std::next(it);\n        }\n    }\n\n    postProcessMediaAssets(assets);\n\n    return assets;\n}\n\nImFont* UserInterface::AddFontFromMemoryCompressedBase85TTF(\n    const char* data, float fontSize, const uint16_t* range)\n{\n    ImFontConfig fontConfig;\n    fontConfig.MergeMode = false;\n    fontConfig.FontDataOwnedByAtlas = false;\n    ImFont* imFont = ImGui::GetCurrentContext()\n        ->IO.Fonts->AddFontFromMemoryCompressedBase85TTF(\n            data, fontSize, &fontConfig, (const ImWchar*)range);\n\n    return imFont;\n}\n\nbool UserInterface::FolderDialog(std::string& m_filepath)\n{\n#ifdef _WIN32\n    IFileOpenDialog* dlg;\n    wchar_t* path = NULL;\n\n    // Create the FileOpenDialog object.\n    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,\n        IID_IFileOpenDialog, (LPVOID*)&dlg);\n    if (SUCCEEDED(hr))\n    {\n        FILEOPENDIALOGOPTIONS options;\n        if (SUCCEEDED(dlg->GetOptions(&options)))\n        {\n            options |= FOS_PICKFOLDERS | FOS_PATHMUSTEXIST;\n            dlg->SetOptions(options);\n        }\n\n        if (SUCCEEDED(dlg->Show(NULL)))\n        {\n            IShellItem* pItem;\n            if (SUCCEEDED(dlg->GetResult(&pItem)))\n            {\n                hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &path);\n                std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;\n                m_filepath = converter.to_bytes(path);\n\n                pItem->Release();\n            }\n        }\n        dlg->Release();\n    }\n    return true;\n#else  // _WIN32\n    // minimal implementation avoiding a GUI library, ignores filters for now,\n    // and relies on external 'zenity' program commonly available on linuxoids\n    char chars[PATH_MAX] = { 0 };\n    std::string app = \"zenity --file-selection --directory\";\n    FILE* f = popen(app.c_str(), \"r\");\n    bool gotname = (nullptr != fgets(chars, PATH_MAX, f));\n    pclose(f);\n\n    if (gotname && chars[0] != '\\0')\n    {\n        filepath = chars;\n\n        // trim newline at end that zenity inserts\n        filepath.erase(filepath.find_last_not_of(\" \\n\\r\\t\") + 1);\n\n        return true;\n    }\n    return false;\n#endif // _WIN32\n}\n\nbool UserInterface::FileDialog(bool bOpen, char const* filters,\n    std::string& m_filepath)\n{\n#ifdef _WIN32\n    IFileOpenDialog* dlg;\n    wchar_t* path = NULL;\n    // Create the FileOpenDialog object.\n    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,\n        IID_IFileOpenDialog, (LPVOID*)&dlg);\n    if (SUCCEEDED(hr))\n    {\n        auto parseTokens = [](char const* str)\n            {\n                std::vector<std::wstring> tokens;\n                while (*str)\n                {\n                    if (size_t len = strlen(str); len > 0)\n                    {\n                        tokens.push_back(std::wstring(str, str + len));\n                        str += len + 1;\n                    }\n                    else\n                        break;\n                }\n                return tokens;\n            };\n\n        auto createFilterSpecs = [](std::vector<std::wstring> const& tokens)\n            {\n                assert((tokens.size() % 2) == 0);\n\n                std::vector<COMDLG_FILTERSPEC> filterSpecs(tokens.size() / 2);\n                for (uint8_t i = 0; i < tokens.size() / 2; ++i)\n                    filterSpecs[i] = { .pszName = tokens[i * 2].c_str(),\n                                      .pszSpec = tokens[i * 2 + 1].c_str() };\n                return filterSpecs;\n            };\n\n        if (auto const& tokens = parseTokens(filters); !tokens.empty())\n        {\n            auto filterSpecs = createFilterSpecs(tokens);\n            dlg->SetFileTypes((uint32_t)filterSpecs.size(), filterSpecs.data());\n        }\n\n        hr = dlg->Show(NULL);\n        if (SUCCEEDED(hr))\n        {\n            IShellItem* pItem;\n            hr = dlg->GetResult(&pItem);\n            if (SUCCEEDED(hr))\n            {\n                hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &path);\n\n                std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;\n\n                m_filepath = converter.to_bytes(path);\n\n                pItem->Release();\n            }\n        }\n        dlg->Release();\n    }\n    return true;\n#else  // _WIN32\n    // minimal implementation avoiding a GUI library, ignores filters for now,\n    // and relies on external 'zenity' program commonly available on linuxoids\n    char chars[PATH_MAX] = { 0 };\n    std::string app = \"zenity --file-selection\";\n    if (!bOpen)\n    {\n        app += \" --save --confirm-overwrite\";\n    }\n    FILE* f = popen(app.c_str(), \"r\");\n    bool gotname = (nullptr != fgets(chars, PATH_MAX, f));\n    pclose(f);\n\n    if (gotname && chars[0] != '\\0')\n    {\n        filepath = chars;\n\n        // trim newline at end that zenity inserts\n        filepath.erase(filepath.find_last_not_of(\" \\n\\r\\t\") + 1);\n\n        return true;\n    }\n    return false;\n#endif // _WIN32\n}\n\nvoid TimeLineEditorState::Update(float elapsedTime)\n{\n    currentTime += elapsedTime;\n\n    if (currentTime > endTime)\n    {\n        if (loop)\n        {\n            currentTime = startTime;\n            if (setTimeCallback)\n                setTimeCallback(*this);\n        }\n        else\n        {\n            currentTime = endTime;\n            mode = Playback::Pause;\n            if (pauseCallback)\n                pauseCallback(*this);\n        }\n    }\n}\n\n#ifdef AUDIO_ENGINE_ENABLED\nvoid UserInterface::SetupAudioEngine()\n{\n    m_audioEngine = audio::Engine::create();\n\n    GetApp().GetUIData().timeLineEditorState.playCallback = [this](TimeLineEditorState const& state)\n        {\n            if (m_voice)\n                m_voice->start();\n        };\n    GetApp().GetUIData().timeLineEditorState.pauseCallback = [this](TimeLineEditorState const& state)\n        {\n            if (m_voice)\n                m_voice->stop();\n        };\n    GetApp().GetUIData().timeLineEditorState.setTimeCallback = [this](TimeLineEditorState const& state)\n        {\n            if (m_voice)\n            {\n                m_voice->stop();\n                m_voice->setStart(*m_audioEngine, state.currentTime - GetApp().GetUIData().audioStartTime);\n                if (state.IsPlaying())\n                    m_voice->start();\n            }\n        };\n}\n\nvoid UserInterface::SetupAudioVoice(const std::string& wavepath, float startTime)\n{\n    auto& state = GetApp().GetUIData().timeLineEditorState;\n\n    if (!m_audioEngine)\n        return;\n\n    if (wavepath.empty())\n    {\n        if (m_voice)\n        {\n            m_voice->stop();\n            m_voice.reset();\n        }\n        return;\n    }\n\n    const fs::path& mediapath = m_app.GetMediaPath();\n\n    std::shared_ptr<audio::WaveFile> wavefile = audio::WaveFile::read(mediapath / wavepath);\n    if (!wavefile)\n        return;\n\n    m_voice = audio::Voice::create(*m_audioEngine, wavefile);\n    if (!m_voice)\n        return;\n\n    float offset = state.currentTime - startTime;\n    m_voice->setStart(*m_audioEngine, offset);\n}\n\nvoid UserInterface::MuteAudio(bool mute)\n{\n    GetApp().GetUIData().audioMuted = mute;\n    if (m_audioEngine)\n        m_audioEngine->mute(GetApp().GetUIData().audioMuted);\n}\n\n#else\nvoid UserInterface::SetupAudioEngine() {}\nvoid UserInterface::SetupAudioVoice(const std::string& wavepath, float startTime) {}\nvoid UserInterface::MuteAudio(bool) {}\n#endif\n"
  },
  {
    "path": "demo/gui.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <filesystem>\n#include <map>\n\n#include <donut/app/imgui_renderer.h>\n#include <donut/app/StreamlineInterface.h>\n#include <donut/core/math/math.h>\n#include <donut/engine/TextureCache.h>\n\n#include \"implot.h\"\n#include \"rtxmg_demo.h\"\n\n#include \"rtxmg/profiler/gui.h\"\n\nusing namespace donut::math;\n\nnamespace fs = std::filesystem;\n\nclass RTXMGDemoApp;\nstruct GLFWindow;\n\n#ifdef AUDIO_ENGINE_ENABLED\n#include <audio/audio.h>\n#include <audio/waveFile.h>\n#else\nnamespace audio\n{\n    class Engine {};\n    class Voice {};\n}\n#endif\n\nstruct MediaAsset\n{\n    enum class Type : uint8_t\n    {\n        OBJ_FILE = 0,\n        OBJ_SEQUENCE\n    } type = Type::OBJ_FILE;\n\n    std::string name;\n\n    std::string sequenceName; // decorated sequence name to display in GUI\n    std::string\n        sequenceFormat; // format string to generate paths to individual files\n    int padding =\n        0; // number of digits in sequence numbers (or 0 if no padding detected)\n\n    std::string wavePath;\n    float waveStartTime = 0.f; // offset on audio start time\n\n    int2 frameRange = { std::numeric_limits<int>::max(),\n                       std::numeric_limits<int>::min() };\n    float frameRate = 24.f;\n\n    bool IsSequence() const { return type == Type::OBJ_SEQUENCE; }\n\n    char const* GetName() const\n    {\n        return IsSequence() ? sequenceName.c_str() : name.c_str();\n    }\n\n    void GrowFrameRange(int frame)\n    {\n        frameRange.x = std::min(frame, frameRange.x);\n        frameRange.y = std::max(frame, frameRange.y);\n    };\n};\n\ntypedef std::map<std::string, MediaAsset> MediaAssetsMap;\n\nstruct TimeLineEditorState\n{\n    template <typename T> constexpr T clamp(T value, T lower, T upper)\n    {\n        return std::min(std::max(value, lower), upper);\n    }\n\n    enum class Playback : uint8_t { Pause = 0, Play } mode = Playback::Pause;\n    bool loop = true;\n    int2 frameRange = { 0, 0 };\n    float frameRate = 30.f;\n    float startTime = 0.f;\n    float endTime = 0.f;\n    float currentTime = 0.f;\n\n    std::function<void(TimeLineEditorState const&)> playCallback;\n    std::function<void(TimeLineEditorState const&)> pauseCallback;\n    std::function<void(TimeLineEditorState const&)> setTimeCallback;\n\n    void Update(float elapsedTime);\n    float AnimationTime() const { return currentTime - startTime; }\n\n    // programmatic manipulation\n    inline bool IsPlaying() const { return mode == Playback::Play; }\n    inline bool IsPaused() const { return mode == Playback::Pause; }\n\n    inline void SetFrame(float time)\n    {\n        currentTime = clamp(time / frameRate, startTime, endTime);\n    }\n    inline void StepForward()\n    {\n        currentTime = clamp(currentTime + 1.f / frameRate, startTime, endTime);\n        if (setTimeCallback)\n            setTimeCallback(*this);\n    }\n    inline void StepBackward()\n    {\n        currentTime = clamp(currentTime - 1.f / frameRate, startTime, endTime);\n        if (setTimeCallback)\n            setTimeCallback(*this);\n    }\n    inline void Rewind()\n    {\n        currentTime = startTime;\n        if (setTimeCallback)\n            setTimeCallback(*this);\n    }\n    inline void FastForward()\n    {\n        currentTime = endTime;\n        if (setTimeCallback)\n            setTimeCallback(*this);\n    }\n\n    inline void PlayClicked()\n    {\n        bool paused = IsPaused();\n\n        if (paused && playCallback)\n            playCallback(*this);\n        else if (pauseCallback)\n            pauseCallback(*this);\n\n        mode = paused ? TimeLineEditorState::Playback::Play\n            : TimeLineEditorState::Playback::Pause;\n    }\n};\n\nstruct UIData\n{\n    bool showUI = true;\n\n    // path to imgui.ini settings file (auto-saved by imgui)\n    std::string iniFilepath;\n\n    MediaAssetsMap mediaAssets;\n\n    MediaAsset const* currentAsset = nullptr;\n\n    void SelectCurrentAsset(const std::string& name)\n    {\n        currentAsset = nullptr;\n        if (name.empty())\n            return;\n        if (auto it = mediaAssets.find(name); it != mediaAssets.end())\n            currentAsset = &it->second;\n    }\n\n    bool audioMuted = false;\n    float audioStartTime = 0.f;\n\n    // various filters for the scene selector\n    bool includeJsonAssets = true;\n    bool includeObjAssets = true;\n\n    std::array<char const*, 4> formatFilters() const;\n\n    std::array<char const*, 5> folderFilters() const;\n\n    bool showTimeLineEditor = true;\n    bool showRecommendationWindow = false;\n    bool showHelpWindow = true;\n    bool showLodInspector = false;\n    bool forceRebuildAccelStruct = true;\n    bool enableMonolithicClusterBuild = false;\n    \n    TimeLineEditorState timeLineEditorState;\n\n    std::shared_ptr<donut::engine::LoadedTexture> envmap = nullptr;\n    std::string envmapFilepath = \"\";\n\n    // DLSS\n#if DONUT_WITH_STREAMLINE\n    using StreamlineInterface = donut::app::StreamlineInterface;\n    StreamlineInterface::DLSSMode dlssMode = StreamlineInterface::DLSSMode::eMaxQuality;\n    bool dlssUseLodBiasOverride = false;\n    float dlssLodBiasOverride = 0.f;\n    StreamlineInterface::DLSSPreset dlssPreset = StreamlineInterface::DLSSPreset::eDefault;\n    StreamlineInterface::DLSSRRPreset dlssRRPreset = StreamlineInterface::DLSSRRPreset::eDefault;\n#endif\n};\n\nclass UserInterface : public donut::app::ImGui_Renderer\n{\npublic:\n    UserInterface(RTXMGDemoApp& app);\n    void BackBufferResized(const uint32_t width,\n        const uint32_t height,\n        const uint32_t sampleCount) override;\n    void buildUI() override;\n\n    void SetAnimationRange(int2 frameRange, float frameRate);\n    void Animate(float elapsedTimeSeconds) override;\n\n    RTXMGDemoApp& GetApp() { return m_app; }\n\n    ImFont* GetIconicFont() { return m_iconicFont; }\n\n    ImGuiContext* GetImGuiContext() const { return m_imgui; }\n    ImPlotContext* GetImPlotContext() const { return m_implot; }\n\n    ProfilerGUI& GetProfilerGUI() { return m_profiler; }\n\n    bool CustomInit(std::shared_ptr<donut::engine::ShaderFactory> shaderFactory);\n\nprivate:\n    void BuildMemoryWarning(int2 windowSize);\n    void BuildUIMain(int2 windowSize);\n    void BuildUITimeline(int2 windowSize, float timeline_width);\n    void BuildUIEnvmap(ImVec2 itemSize);\n\n    void BuildMissColorUI();\n\n    bool BuildTimeLineEditor(TimeLineEditorState& state, float2 size);\n\n    void SetupAudioVoice(const std::string& wavepath, float startTime = 0.f);\n    void SetupAudioEngine();\n    void MuteAudio(bool mute);\n\n    void LoadAsset(MediaAsset const& asset, std::string const& name,\n        int2 frameRange);\n\n    static MediaAssetsMap FindMediaAssets(fs::path const& mediapath,\n        char const* const* folder_filters,\n        char const* const* format_filters);\n    // 'iconic' open-source TTF font (lots of standard icons to make buttons\n    // with)\n\n    static constexpr float const iconicFontSize = 18.f;\n    static uint16_t const* GetOpenIconicFontGlyphRange();\n    static char const* GetNVSansFontRgCompressedBase85TTF();\n    static char const* GetNVSansFontBoldCompressedBase85TTF();\n    static char const* GetOpenIconicFontCompressedBase85TTF();\n\n    ImFont* AddFontFromMemoryCompressedBase85TTF(const char* data, float fontSize,\n        const uint16_t* range);\n    bool FolderDialog(std::string& m_filepath);\n    bool FileDialog(bool bOpen, char const* filters, std::string& m_filepath);\n\n    void SetupIniHandler();\n\nprivate:\n    RTXMGDemoApp& m_app;\n\n    ImFont* m_iconicFont = nullptr;\n    ImFont* m_nvidiaRgFont = nullptr;\n    ImFont* m_nvidiaBldFont = nullptr;\n\n    ImGuiContext* m_imgui = nullptr;\n    ImPlotContext* m_implot = nullptr;\n    \n    ProfilerGUI m_profiler;\n\n    std::shared_ptr<audio::Engine> m_audioEngine;\n    std::unique_ptr<audio::Voice> m_voice;\n};"
  },
  {
    "path": "demo/gui_fonts.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include \"gui.h\"\n\nuint16_t const* UserInterface::GetOpenIconicFontGlyphRange()\n{\n    static uint16_t const range[] = { 0xE000, 0xE0DF, 0 };\n    return range;\n}\n\nchar const* UserInterface::GetNVSansFontRgCompressedBase85TTF()\n{\n    // File: 'NVIDIA-Sans-Font-TTF/NVIDIASans_Rg.ttf' (170280 bytes)\n    // Exported using binary_to_compressed_c.cpp\n    static const char _nv_sans_rg_compressed_data_base85[138380 + 1] =\n        \"7])#######<.*'0'/###I),##d-LhL,6VC$@=cd=:J-QBP__Y#:v(##Ye=t82@VlUn[D>#/\"\n        \"b$##;x+h<9:O@ebJ>>#];&##(b+e=<`9ZRC1^Y#s6+##6-we=(Ds*6^i(##`a'L>*vA0F\"\n        \"bm)O7w0&##sj-Q1YN3'IQ3_;.EM###nWu9)n[_-G2]FH26s$##U/\"\n        \"H50u2$=Bda:8%0r:kF)0wE7;1HkEQCmae=f%##m**##0r?UC&%Rud]A4_AwVf--f'TqL<9I&\"\n        \"OhoZw'Ip_w'Y3n0F\"\n        \")V/BCWT'##=l###k-YUCUJa(k$M%##lRQEn9LrhF4nGO#ZZ###-x$##/\"\n        \"%X=BLY`VQUV&F7H9%F7?4^=B6DNV.mv@>#QMe--5T`8GQvRVL*1J(#0i9<#2H_(Gw]M]&\"\n        \"pCA:#DIm##9+mlL\"\n        \"Rc$##Se@J1sbjRAeI1=#pNd;#PiocjJealL4+i@kPpM.vJ21V$75%#M_nv##UE31#Ru*\"\n        \"O4E9Z+M(-srZrN@P/*vFX:Mk^e$fq73M7x_5//<G##Z;4gL;Fa$#6ZhpLk/L2%jlch1*M(Z#\"\n        \"F6B2#2fLZ#EcM:Mgw/\"\n        \"K1jdw+;S<JW$8(Mv#VKNh#EJ)4#*Jn)#iO(F.$)JfLxvxv74+LfL^OH:vpX/\"\n        \">5eBv.h*Cp6*amo-$Fpf=ulU2F%wN02'Kt&)33=C^#QlRfL,4*YlD.;.$S]-F%\"\n        \"^I7]kMaPJ(bK]F%44H;#9Ph/#ii`4#EdOkL1Mvu#m@kF%?b%##NBs-$Dr(##]'/\"\n        \"F%VOr-$<Vq-$]`l-$)iB#vm'>uue/AF.i]>_/\"\n        \"&jp-$f%m-$P;l-$Fsk-$YVl-$WPl-$h+m-$RAl-$\"\n        \"UM28IQ&BG;$twl/\"\n        \"4G.&4:'NJ:[S1*4MG_]=cgJ8%-A?v?;7E_&'S7C#Rrn%#4^0B#tbe@#s1r?#*8%@#vOI@#:%\"\n        \"4A#a=.@#%aYs-1:V_X4Cg:dgJZw9t`E'#L4?$$'w,?$6gRU.reK^#\"\n        \"_U5s-0n'hL9RFjLuxYoLi/\"\n        \"BnL.+m<-NM#<-N5T;-s5T;-H5T;->5T;-`5T;-8;0,MGSMpLjh?lLfs&nL_eG.#K%jE-/\"\n        \";eM0p7YY#pcv_#OFqhL#$T@#:jIH#mj0-MUV9M^b7:?H,1hl8\"\n        \"[<v+D>L^.$M<2'#U]'##mvLcM.i%(#JB$(#;EV,#/..m/\"\n        \"T3xH#q0qB#>E3#.NM$iLiTNmLaVOjLwFrB#qMNjL'fR$$4xv(#wTi2M<P$2TIpNTS)pQ`W?\"\n        \"Ld(js#sc<bLfS8<U3>df*IYm\"\n        \"k;pM(Ysj/2sdmxOMt:T/o078RX:l%lbNx4plT0,NOoUPKOVXFFB'729H2t1^D<(/\"\n        \"LmU)>l0s+8Rh:E:A.?^]=pt,v,U:88@1f[J(6f-F%R6xl/\"\n        \"SEc%XD1I;@7f-_>e:S.qc4S.q0/gS.\"\n        \"%</\"\n        \"d<`]Yw05C1^#9E:@-2,'Y%6$'2#ssx=#Lx%5#.=p*#ia]=#Bi($#IYl##?)5>#]c39#SXx5#\"\n        \"Bqc+#$ka1#ESP8.8/98.+RrS&Z]>;)>G24#R3>)3`OHC#x[v$$$;WD#w`''#`'mlL\"\n        \"K.L0Mf2kxF:Yl-$C<4AF<R(58lV+##ZuR.q-W6E0A#YxkbjdS7b</F%v,w(N)nH`sgAZ/\"\n        \"rjmW;$dPx^]lnsoIQjmuY=*'W@#/lQjT_A(MdR0O.`t_Z#)rO*t/C629rMh`3qQg1pG=+#5\"\n        \"FWXd3-T=;$a)/j/\"\n        \"3i:;$FlV`3=htuZ%$$ZZ7b%J_@b3>d;Lnxc@$KVe=^E>H<T*#HtDOA>'AD8AP[6,a>\"\n        \"PTw9qu#<-f<up7:W=_J*VY5T/0b`<C:wN(.?&2^*P[J(I*w>@)A2pLuD%f$\"\n        \"_3_fL=;xU.o2-_#.p:]0Xvrs#b1g*#p%$&M<(i%MSgC%MLp$tLK,@tL)TOh#_Dg9M./\"\n        \"4'#cYj)#vWWjLQv->%tt5gLNA*>l4@:58&R<-v01(^#v$O-Q=`F'#Fop:#UwV<#?wEf-<\"\n        \"70kX\"\n        \"KSF,M,PO5&'4xctOd[+N(5urLmlFrLn#%]M2l<+.P4')N^L$2O37A^Nc0tnLl.AqL,r%\"\n        \"qLF8'vL+SMpL&MDpLdOcOOf+-TQgpon.r$S7nrGd]NiJ(]$kKrxB]]j&#sNxZN^D5O-Ux6a.\"\n        \"`$###35T;-A(m<-@6T;-gxhH-<$cP8whlS/\"\n        \"?V(Q8'W+a=+xxkXe:0dkU%88@A7Gd=s]>4MH:*>P)$hJjuAIlf^hU-Q>LlxuE#+J-oGuG-j/\"\n        \"PG-*O#<-?6T;-FgG<-wh;a-vEp&$$U#;d\"\n        \"8t[:;#URSsYN-R<]ao-$V1Ek=0qXk=21i58%/\"\n        \"[ihf+fMh+EfY#=k__&B9I,Md0QJ(]A%,MUS./\"\n        \"97]'^#twG,Me8E,9Xfj-?$$+F.amo-$7Ek-$eW%#,*q>PA^UFD*pIhl/F/%:)WQo-$\"\n        \"7[vu,C/1F%Y4%5pb9?GjWn/,NXLEm/-r3]#`DRw#NY`=-;q-A-(N9o/tiCC$4/\"\n        \"FE$4bP[.U$Ox#KA;=-iZlS.qTA&$$gUu8Jw&p9>S2)3)xPl]%Otr?`$wl/\"\n        \"uhXA+FTuf(Sf?>>PR9v-\"\n        \"^rZ#6)'h1prpVG<e[o88MaNJ:s_)q0?#W+rtV_207x),2H/\"\n        \"W]+jrNk+;ex+2_YP`<SwE_&jVs^fgB4F%:Nk-$7XAJ1^&,F%IkIP/\"\n        \"tt4R*`:$_]IM#_]X6Bk=7Wt+;K=22011kwK6Jh9;\"\n        \"/B=J:H*.e-9(._SlW+R<7Cc-60<v9)Z%<gMW:qrQ(q9>Z@4`G36pa-?\\?41875/\"\n        \"9q'k3d.#YXx5#b[VmLLA*,#PCF&#M@1).P1]08G?srn<F1wg98#5]$T-E+G0%29fDd9MIwl+\"\n        \"#,V*rL\"\n        \"i,T)vUVUpLe#*q-k0bw'eYUS%t(KV-.?0,.UNUpLlF/[#.U^C-@i'-%Xn3F%D4KfL9/\"\n        \"6uuk_O9`,mZw'L*),#t&>uu+?pV-0a9wgn-slKbm8R*sl[+M/\"\n        \"IKO0Lkp>$pb.-#WF')Ne2#&#\"\n        \")(MT.tP8=#-.MT.G`_;$cmw6/\"\n        \"4)[0#gq0hL$Y3A'6`3>5ihar?S?t4JGu[%X53F8%SVa]+tmji0AIpr6dS',;+b08@e5?MK$\"\n        \"sd]4dMDVd`J^;$=e/m&P)%d)u^*m/RA*a*',<#5_]YD<\"\n        \"C/&jB2h`VQg9d`4.UYiUa&%T&0u,g)FBx],VMQ8/\"\n        \"rB_G37P-p8pHoo]1pdf`HaBv-Wi(V$x.Y:vYLVS%)m4;-8lNY5kt&8@UK9PJlB>]X:TB5&Y%\"\n        \"^Y,$<gf1Gnlo7k1Z`<10-5AWTq`*\"\n        \"tEW]c6cxJi/\"\n        \"l6s$H4QeQrXH##K.-eQrL-eQ5ju`<UkiYGO&cV.lV&2LVrrjqpP7p&6C)d*JZX>-5^o/\"\n        \"2$*WA5k%KM_^FK&#atGB#o`+##/lH$Th+m-$bol-$.Jkxu-5xq.pe(##g<9x'\"\n        \"-7dru=*8;M`5fO]M&J4].n-o[@LT.$n].*#BuS-MS-VF_gN)##A8+##dRO(;@)$rYCHLP1[\"\n        \"I(D2v7]C;A/A>,$HF_/S^DuLtgDxL&NJ#Mkxm6#Y=R8#;ZWL#aWVO#3&cQ#8RZ&MX6iU#\"\n        \"Ovis#Qd''#(s80M,vh`3N'ZS7[0Ph#3]8a#u7G)NUlZY>hdorHxoHMK*JafL&HHDOwU'^#.#\"\n        \"]&#EPH_&1v,%0Fmoq)%`X&#4QXe$j@$v#9_/2'6L3A=4JG]FTAq4Je6exF0ptr$DJ>;$\"\n        \"OrYV$6Ps1B:d;MB^U'DNu`q1KS/)8@#Zno7fwL`E^(XiB=_NP&vkix=(4/\"\n        \">>wB%8Rjr.Sec=t.Ci*(Pfa-*>Gw1Yf:3Oc]+l1F`Nc@(/\"\n        \":xJi(Nn46D<)s55&1_$v,.wgo@jZ$;ZWpEuu\"\n        \"4tZV-k9WlS6*wr-mEs1TmAMPA;Q85/\"\n        \"4EdlA$1:JCXoOM9S(jr?A-oi0;#h:mHY-,2F]XcDu2[rZi@%m&Kodc2*ZLcVM%*)3,gh(W)\"\n        \"l>DERLAA4L+U`E(u]lAfkdc;ppZfCZ.xu5WY.AX\"\n        \"3R->PlBG]XBThc)v;GG2BF.G`w:8GM#GScMRU=ip5_HYPDRIc`Da-)*%So(NnNcxXF_e(\"\n        \"a6boi'TbX.qCYt(E7kduPFmHD*aFuFi1*8`j8n4/(.#^S.^?E;$[V$5JCh=lo<S#p.`KaV$\"\n        \"^c?PJ0[NJ1Ah.]k/8YP/.1]lJ,1rl/^'.##Zfv1K[(L>#]r;MKFkxOoxo.2Blir+MdN?VZX/\"\n        \"lf1oGYP&CS.8@>KLS@+E5JU8x8;6In*Da1FhxO><1,)@HLG)=llxFo8s4]$x(YuC9`Y,\"\n        \"(u+&+t_V]=-k@SRh]FVQ@%,#,5*iCsi/\"\n        \"j@t6)QG2wJ1)3TGn`*o,lc2X`NA+_(0#,:S.&40UhY,BrIA44S*#5qE@5/-8e]4tT[P/\"\n        \"#q<20FF'v5?&ui0u&TJ1w2pf1#?5,2sp8/1K$F`a\"\n        \"M0b%bfl#)<4`WY5h*g%Xhx>D<_.9>,aB&;Hhp=SIjpx7ITO-AFposu,t'+;?\\?mPxbP9X%\"\n        \"bTQ9]b5FE5&s4pr-h+BP80QC_&.Y0^#_e,58i'R@-gelr-k'MS.o?.5/sWel/wVa>nKI@FO\"\n        \";e_xO?'@YPC?w:QGWWrQKp8SRO2p4SSJPlSWc1MT[%i.U`=IfUdU*GVhna(W%Cjd+e:\"\n        \"ceWpH#AXtaYxXx#;YY&<r:Z*TRrZ.m3S[2/\"\n        \"k4]6GKl]:`,M^>xc._B:Df_FR%G`$hHP87#KmA\"\n        \"hiGN#&1PY#PmIH#[-mN#H=,F#(KDO#=JoP#cVQC#(0oH#N_ZC#*6xH#3bbI#ROGF#.^`O#\"\n        \"v2ED#.B4I#5bX.#=ol+#6H4.#fN=.#-]*9#^Y_R#(H^2#W_^:#dCg;-)H5s-R#1PM+]>s-\"\n        \"@=?&M8E.;#wb($#kl-qLi_iS%trf(N@*gi'p9go@e&0]k*eASIqZ?igtRFlfUT_lAv[&\"\n        \"AXcNw+;Rsh+Mi%;`aSM0AF)*%MgF`Yc;(@ou>2;UlS%(3Wf=Uhl8OlMV?J>s69S(jr?CCqFi\"\n        \"xw7>>:g1W]1$L2sAx#Yuqq3T9Ioa#A8s_D?sQ&J=c_?1<RlXn:B#sT9206<8x<O#7hIi`\"\n        \"5WV,G48_:O1n%ns.VsO#-qVr0(T<B6&AF[s$1SuY#@G_69c[KE-aiC^[dI`2:+Jfw'KoipL\"\n        \"?XBKOgZC^[M:2=-;w=Z-Y8Pk4AD4O+9OkV7UQuiC;OkV72OkV75i#D#:CF)#.,r?-]1RV[#=\"\n        \"v>-3$H/=/G@6/r?/Z?gxIaMvS>OM$LR5/6*S5'I:'Z-UrjX%oR%h*K@SV[?wI59NnfSD\"\n        \"BJuv8/ESo[Ih=?.X(JK1aT)H&0`><-7Lem/:gJ&G<J&w6.L[m/\"\n        \"xHna54tQS%'NWN2AC3p.42k^[cFG;9pg'Z-5PVX-6[%:;Ae)Q8UR#:;*6@_[;8t(#W?sA1_>\"\n        \"]=_K,)wA75kB.H;rD?\"\n        \"fehpLx7`.$ZvjRNl>aW[e`DZ[ox%#+'0UQCh9Hp8hxi<.U^M/\"\n        \";2vB<0TNEp.V.HDOX0jT`C%S/pLx5Y(<adX(-P'3(d9q0,FZ/</\"\n        \"Td,Q#7vG<-o@'wLHpBo-u1>kO(b+h74>n31rnR3=\"\n        \"<I^01lg(h+EPi22ZiIm#@ir=-P)8Y-*sw3+ebG4+mg9pALe7w^d<xsAMY5wH*oVALTxk31-\"\n        \"l/KM)q*W[`pP?.<qc2$pj(tB:v:T.B(sF.Iei.09ph5#lA6g7i]GW-R+5N;Coiv[4ln92\"\n        \"3$cpLpB]qLdCl-=);8=.&CB:.oQ8c$'xR$9q>mjD0e(73*k1691.;Z[nV?3NZYf>2K`/\"\n        \"v.FjG59EV]b%h@r*%kaU)=iv?D3?#Ss3E%CW3[:EV?WsqV[WCnoL47u59&%7qCoemg+d^+pA\"\n        \"s6$g+94;^?:Aba?^_3b?e0Hp80BmD0n-uC.5tqY?\\?kv34=UUY1Pl?6s-[Wp.PS&d#K(X][\"\n        \"562hLflcP9D:g;.<nQn*cZ_/%^I#hL6Z@U.c'd]+S8SjL_w@/MwB7p..#b$']j=T8LJ<#.\"\n        \")PEjLcZ-6:;8voL(.CMTw0=fMdVne-MNDFR^rcERRT;UM^W0F[_ZVX1/\"\n        \"$w502hLw8d8fw'NGuDO5JGHM@H4HSp)^fLTEOJMR]g88b?%W%-mV59)kuO-(fG<-[?+v-\"\n        \"a0cfLSjXU%D%a69\"\n        \"oR%h*DaT`<$=wMC+[sJD/\"\n        \"8Fg<8F4g7^2WSCVpwv8q[Le==sB%@dhjmL1s5X$qHY+PJ<S>-0t[+N[&Hm#omQ][.16R*mj`\"\n        \"GMt/^NM0:mS1i6J>-8RJ>--P,hLh7HDOB@Le=vV1H-LDT42\"\n        \"mX$Y:(*`C?W0v>-,gOb3L,Wj0)C*Q9NLF9BA[r<-d9*9_;.OKO5ImGOL:HQD,,r?-)%_6C'\"\n        \"JsC?JbqY?>L)E=9;k7J1pv5/1Wr+;[U&>80<Qm#:S)K.Z=/x[@Jwk=IkeB('rlcN.mRm#\"\n        \"BFwoL5@&h*K/\"\n        \"l-=YjG59Cf6g7L?Zd?%.^a?%*.i.AHGm#YTh]4N$@8.j[Fm#7j6Q#=aET[MCk?8Dof88seN;\"\n        \"7seN;7;b?v$BEN<%:p-Q#>oj31D[Da=JrN%bP1$gm$WE>1KHlV7_UC-M\"\n        \"-4RV[pnnoL1BXjN0W8cMQ.l(.b':qLHHJY-u_i<C*)r92FHBl+eqt20m8,5&g[F$-k0:%-;\"\n        \"J;4C`f+;Cw-:900A]:CdG_d+:n-+7[Z%=CT&H_&0E`9CTir3*+b1[81DN<B5dipLr/FG>\"\n        \"R4<G>]5IY8dR_.=:k_=8Bvm6:rw/\"\n        \"nLJH1t8oejmL+Zvu8Gg^S8BTB88*2rxPGIYa#g*:%-JhW_U8=*O#M4^WU.ML/\"\n        \"(3T<<ULK^S7W227Ux'tpa3T*wT&&KD<<?\\?+Uq9sN#e0ui'QhM$U\"\n        \"0#qN#+rD`amr>9M.-seT&SdN#IR1##k^)k#EYE?T`ZC###3wj#w>Sd<^uk2lOO1C&rjJ?\"\n        \"Tnx=m&r0:/(7rtj#/pZqSf5b,TM-G9)k'A;Rhm=;RA[kORvIlc[13PwLxtAa#s?0##%Sv`[\"\n        \"><1H$Uj5lI[c44LWWonKUKSRKfDZQ<8=?*3g[N)3-N+Z#umc83eBT*3o*0*3gha)\"\n        \"3WmUfLao=T'[kBf'<(v>#?9-Pf5'U/%fU$?#85<Q/\"\n        \"o_oX%Zmv_#6=(R#+hHo[80uQ#9dcr0i?CSn\"\n        \"$vAg%+g.Q#*]La=.^Zp.kpd][c9k5#1vhD?f2DYPsY(MP#Zv2;G8sC?pE+XV'UH5;rxO_&\"\n        \"1Po6*Md?n0A)orL.]1,HXYj+M5Y:GH>9gV-=/Pk+?bw-$Z(frLUrhcHf'uw0dIqZHLhkcH\"\n        \"C`mi0'CTfLSbai04uJe$Q)l3+GQn(#r`]d#ZU1Z#C*rZ#6wU?#-o:$#JV3_N.;*>5q%(Z#\"\n        \"R1`PK%g45/d[P+#;[=oL12&F-g;^;-'A(&Orx:1N>t'*#)++0N=%q;--4S>-E_)iL>(8qL\"\n        \"%PG8.OrFGD+)U#?IU.;H2hi8&PjFF%.B.;6lo8_8A@28.0uX&#4ld'&s;;D3)#ml&jg8G`9#\"\n        \"^>#t'aJ1/fxr6#M,DE<bh--9SL;$+ICt8ETx<$f=C%.Z/i+MC&e+#CUs-$v9k$'IW7'1\"\n        \"R@N`3WxU:m$9`FrkVMcsBKAT7NH9p^.?svubm_$jI$5A0NN*##Q/\"\n        \"YS%IA*WSNr[f-pNNL#qXCP8Y^TlJinU(j+Jsu5%O08@-s#O#gjx4S4TFKL+9dr#pqZg#g+/\"\n        \"9$&Tb*$h`K>$+p;@$\"\n        \"L%#B$m0`C$7<FE$WG-G$(8CN$dQl^$MR-0%+oxx$ts?S%H-t9%mDm;%;]f=%`t_?%.6XA%\"\n        \"RMQC%+LPF%2%nK%n$Ro%p%sW%b$13&P^((&bQC;&*KPf&F$7W&aRS]&Y>ba&*;4i&uj$s&\"\n        \"Y9c#'cP0''dQ46'uC'B'Vm:G'JToX'L)`c'NlBf'p*E-(Kdcj'11Cn'`QLv'h1H$(ri*E(\"\n        \"MrY7(.?e<(%];@(4%BQ(-SJ*)n3fp(P;I%)8;r,)bpH8)&nF>)]6/a):HjL)VhrW)T-qf)\"\n        \"H]>r)/\"\n        \"C)@*3T8.*h,*5*-L0F*5h7T*:xNa*Ib7p*3eiw*dXT'+4%;4+x90B+nV8M+YAUv+Y(%d+]<\"\n        \"Fm+LQ=u+6cU(,?F0+,&^S.,ZC.1,9B-4,v@,7,V?)@,nu;l,8r2l,gcHs,]X5v,\"\n        \"=gO#-l(I%-^88*-Huh,-I@G3-$DL?-<b:k-m5.1.sKp#.(Y]...5aP.=v7A.pFZk.N@Je.\"\n        \"nC$s.Ue4+/U>O6/Y:N9/OpI=/Lq$H/+%3T/1q(W/$JqZ/s;2`/n-Id/iu`h/_Wn10T4Wp/\"\n        \"GdIt/?[jx/<Gx&0699+0+i+/\"\n        \"0vJ'30j'#70V&x90[mc?0HxtB0GvGG0MKnh0UL8Q0TJbU0SH5Z0RF__0U]Vd0b&k.1`\"\n        \"qxm0b1qr0f/Dw0e-n%1d+A*1d20J1f?c31nC?81W<5;1PR-@1\"\n        \"Wi%E1`29f1f?lN1lOZS1rfRX1#'K^1-IUc1=6cj1=O8q10Mbu1/\"\n        \"K5$2.I_(2.J2-2,E[12+C/62*AX:2'9#?2&7LC2&8vG2x,@L2%7&Q2-Y0V2kW/\"\n        \"Y2a7+^2J0w`2&>2d2(Enh2w0&m2\"\n        \"$;bq2#95v2x6_$3x72)3u,R-3pn`13nxE63ldS:3hb'?3gcc_3gj6H3c1dK3$lgR3q8GV3*\"\n        \"hd[3:C+b3HoGg3Kmqk3VE8q3dnKv3tO.A4(^a*4K(k24IJI94BaA>4J$:C4>DpF4s6]I4\"\n        \"fraM4ep4R4dn^V4ZPlv4UE$`4XO`d4M#Ih4J&Pn4L1bq4'J@=5OE215oPSU5Le9?5mpv@5[=\"\n        \"VD5&I=F5.<ZS5]CIt5V0xa5RPUk5xaBv5&&u/6B^+R6&SjE6jI3M6<Cep6D`/b68C^j6\"\n        \"*$-s6%Dcv6WOIx6Bgm%7@cj.7b69R7K<b>7j$BJ7Cr4V7T7<e7VI<28]Vt'8Nqq88=e:@8+<\"\n        \"+J8QA]Q8/JCS8$?b^86@:o8bNY(9=>m89FGS:9gR:<9DZYg95khW9%ux^9.=Sq9.mw&:\"\n        \"5#_(::4L0:D[f>:DMOJ:%@CS:o;%*;4b&j:7`On:<vGs:HH[x:JF/';P`',;u@+3;.Kg7;/\"\n        \"UL<;1];];^Tu4#%/5##V,>>#nN9:#-Mc##Bw.?#1`($#-?Ktet/oO(FaKZ-3i[C#VT`O#\"\n        \"%5Bq#Tw+-3NJ6U2_IMmLwO*4#d)n=.V]uuG<2$5p313*4bC%<AC[S:vG-`B#Jg''#(,>>#H`\"\n        \"]87iAdU7=%Is$.r?s$wjlF#M;/r0mO,87Y),##s1C/#,.eQ'=EYQ#P*3)%Ds>6p.Deh2\"\n        \"$#,G4<@k=.NGVM)0*tD<)jMO''e2'+Y*7-*tOOi(?r3;.BfqKGadmA#5%>G-C4>G-/smx/\"\n        \"C.[S%MXwo%.AcDNdA6##@)RW%W^u7nd[lr-<5Xp'`9Bm&JU1,-;$80:/,h2#an8B3@@Na(\"\n        \",bDM2J59f3JX@lL:Y^:/\"\n        \"<;Rv$fV&J3_R(f)Ztw8%Yrse)QL8e%[.=m0r9x&6jw?n(MWrb&0`4&7`pB>A4';@67F+O:$\"\n        \"swl1^p.%B+BeB7Xv^Z-:F(UC%/5##($###Jh:*%laC3$UF[5&\"\n        \"d]*87mp6eH)&E%$<x&kC?eAB=^4iV-h%<44x57D*@tm=ug.,Y.H;0F*VC$t-?M6G*tpk4S[-\"\n        \"cf(^T6&u0YP4(RnAH*+aS+4u,5%MR+0i)xh6w^D=N,*r?8E4Euh8.=PWF0*?O+?9#w'=\"\n        \"h(]-dvVNhL/\"\n        \"g$r@'&,8(C+N#Mc-E501+,b33>i_,bLV6N+$DS.>F:kOel68%?NcQ#6*ooRno*87cZD^2]S<\"\n        \"F3;,o>/0RwW&?jDCM(cPVHXaDB#pv3v#>-rQs_356#I1K9r&[)22j5LR/\"\n        \"r:iS(;)2v#(^EuuDsbZ-U+?_/\"\n        \"&Xkl&*c68%a5aVR=apO0h5YY#B0f-#%)ofLY]XjLXpT(M)F5gL)gC%MUUHuu>i9'#dR<1#q^\"\n        \"mI3Dk_a4%F$`4`W^:%L>*+3<EYE,pW$R9=WZhiCnI(,\"\n        \"huHP9l<C69M);`3$,GuuHXjY.$Vj)#c',s/L*KR/\"\n        \"lW%%u[ER`%maB#vMfm_#D*V$#`P(k^Z]CW$R%32':]EQ7xak[$+aS+4elr:%Bj1T/\"\n        \"QY^j24Rue)un65/%29CJ`jZqM`3)$>Xxe4M\"\n        \"c,VaWIuqSWI5Z]RXfSdH=58?K4I(#KL(T&#2K3j0IPf5#@,,3^;5Y;$UiV;7^IT[-mP/\"\n        \"J3q.r`FQm*)E:F+oL]_Zi9o)=p&<K%]-<5Xp'ZX_w,4==Y-Z3%-D5uT58U5[B5?Z*@5FTNI3\"\n        \"BakAdBKihL*T_58.2-E3&nxjM@3)C6rN<O)T2oc&;6(V8N>AR0*cOxR6v&c=xl*m1(AR=1Q-\"\n        \"o68XYMK*R-O/:)Hk,3+XbJ(d[La3ijxW7DtC3(Ywx?#]DW]+4rqx,CIL,D2rC$#`k8B3\"\n        \"an8B3d>)CGb@O22V_Ds-XkC+*swkW%`IE(&Bk3Q/\"\n        \"*[Qn*%XXwT^C^&=pCI1_Lri*<eV5b>6'jnD8#E=8_^jS8e5OY&.D,M29CioK.j9G-A1IE5L/\"\n        \";99)>+b3`5k]5m04f4k$EO)-ru1)\"\n        \">t]Y#KF$##d`AS[E@D'S1olV7IhJ2'LVR<$8AcY#-GG##X.i>7P0^p(Q/\"\n        \"%D3k^e[-Z%xC#Lu]=%>=UN(Zgjhus8?3<-NXl;E&]06-ii(3=UDV##wY3#`4W%v`T$iL&9Z&\"\n        \"#U@BL(n27i(\"\n        \"l5%)*ND`;$*d*p7@.c5p&d<I3Oj41M@YOjL.YW+Mq/\"\n        \"SX-?3I&O(lr=.gj@]Isx<pJ*PZ]@ch']7Hh`uPq9cv5(@YNDWONx&f-q`=UB]q7]3g@#sH;\"\n        \"r,_q.N)T``NkUiPlLk/j^#T5=&#\"\n        \"fW]W71Zcq(gDw%+IEuj'Y`D?#Yxb;-Ibn8.)B:B38_V/2iE[+':@n5/]>Uv-kQ]/\"\n        \"NU1;')`Y`$HZL^^&HW-R(PvJsZ9cb;&A*T]6BQ`6&g*;s9(;q`?dq3'JL,Cj+,H7%+\"\n        \"q0SoBrq&QC\"\n        \"<]fu8P4RoL<4Ua>$),##RqCg%*I@S[@*gi'R>0q%CM(<$-<9B3LTNI3OM>c4XF3]-G1Rs$>\"\n        \"x8j.'<#PSoIIg.DLYAH)Uk1:`6,I8d6r*#ZWT`31YxV.>=pC,8(KV6;2Xp'+ud0DS5R]4\"\n        \"g&,Z,V9k5p5KW6p_ONU80H(a4MC(*'Jt4ND73e'(fso>eMqbZ.*`mi2])]8D@-\"\n        \"k825xcrJWkga<wt%V'f3aI=^_k3;X3D3;<3:F?-6gP)oc&4(5#?0=o6N4)-oc1)t=K6:<\"\n        \"xW4:(uBB#\"\n        \"E6jR'ruCI$I5YY#Gi`Kc[+=p^;u:/\"\n        \"DILM$#%i*Y$?M(v#R@r[#c4X_$sA:B3kq'*)[oJ0%0$g5/\"\n        \"DtSs$gc'gM'6b(&me8N9NCan9_&e:&2.Va6dR*I5snqW&lcjBJ$l(_?ljt/:[[99I\"\n        \"/B9)t&px'5pH]q7&WfxK5;xO#dRD6(V7gu8#MF(v1Y5lL^PEmLNaZ##+AJfL6``'#ivrs#\"\n        \"0Oc##j[-0#a/k/2osqFrb-Cv-j-m<-nJL@--72X-uDvw'=kM/2Um[v$peAFMTotjLCXlGM\"\n        \"Qc'KMGpVE-rMU[-RGD:2`T(aMdj'KiaG>)4pm6s.f^;p/\"\n        \",+m<-X(m<-I72X-'`]*@ut3xuj^YF#[)V$#[P(k^M[,hLKxsJLh6Aj94jhv[.VT+4k>Uv-j%\"\n        \"q6]R%YR:c6hk;wH>#>1<KY&\"\n        \"S#XB>_i2I$[t1>IaeD#0F*,##1-E6.4TC>96Hi.N$sQiK:i>8pt/\"\n        \"oO(mAoO(WICjKB*5`u>J;'5;,>>#ktH0#I_;V7eUL[,d[rlK,cW=(jJ#D/\"\n        \"aWO=7Krq39#>P3D&oox$==Rs$6t6(+\"\n        \":h7G*-D#Z,OG?D,bD[r8#DZ/\"\n        \"*2O.r4v:5Q'o>[)*5F)D+;=_X%[gJx,M9%Y6Aix&#*Q]X#)8&05fdoW7G&tbNHb13(sVV$#\"\n        \"7W'I$`%C##5r9-%wZ)22b@O22lX7B3iv=u-8[4n/Hv8+4\"\n        \"+87<.--qd%/AXI)MH:=%>%5N5e0*5(ZpLp/4/uC5Sp_>$^0no'-)O#5Xr,(+(/\"\n        \"'<&RpKr.')b&+Ww'x$DZ]E+*?322vq46')D[V%BC[Y#af*hYcY-;H:b/\"\n        \"2'7pw8'k6-*N>r(Z#kc&E>\"\n        \"]2*22]FAT%':k=.-g0i)@uGg)U5J5fiZ1K3fqY;J7-wErqSM9Vo*,##w&wT-VLj$7a2PX7<$\"\n        \"naN+7)D+)C^U)AjeY>0iqV$Jd[Y>X4=GM[CKw%IEwQs*`;t-/gWjL0Ew;%LJ=,MuRM=.\"\n        \"'_o%5?$Es-JVIZ$@gTn/x69N$qG:u$'*(C6ebe40/\"\n        \"Mg#,o&`K(6TM^%X)nU&gPd,OXj]-s3L=MKlZ&/\"\n        \"1?0Ed48%n$#&C@=-cYNI)'8)C&HH10(>.dB?n,CTIH<PN'k,;?#A?9O0l&:TI\"\n        \">Cns$#R<:M(lDQ7P>[BGuF1I$-R-@5&&(7piv=u-0&_29p<T*[J,7U%KO];;<Cg/\"\n        \"+EucG*bqjH2n7[79fEgW$e%I<.EFS60[GW_>E7mI)ngCH)ds?u%VQ-;/\"\n        \"60TV.6ocv,J),##5,>>#\"\n        \"ORC>9#;ImKjDPF%Hafc&Ww&>.@EL:%Yrse)E<:i:Ih/6ADcrT/\"\n        \"GU:H6Mq2#6rbvu#5)]CsTYjv%>nF)#B)m9%j7ZC/\"\n        \"^7.fN83;J9`#dG*b^S29ou<HX,@o79^j_bX?^0&,+.E'64@6k2\"\n        \"@B1X8.^NjLqBY#%g(ws.;J6W.63gV.l/4,4+IYq&vc_:%K/\"\n        \"422,iNo&T2e&4'-'a+*3f],)R*>-]:Cu$:b;D+]gx%#i?hhLf^-lL7Ykb+MGhx=s/\"\n        \"BcNib:w,ppAw#cCho.FCBe$8dx._\"\n        \"fm@Q(=%H'?^K=8I2u6s$_HP>#3OM=-C'+r.[E48I@@m5L?Q.N%Aka;fHpe/\"\n        \"&*S@j:f(Qg)vkh8.>Qm,;o-x@5g5uX$,.H^$%*cb5O/\"\n        \"%7:sfL50=x(W-qQEN9&v1[G$]GK3SYcdXs'WJb\"\n        \"6ocv,ADnV%e@Q:v'MSV6;pCYcadT`3m_(/:V-QW.n]RuYvZBX(f0cX(RO)/\"\n        \":>)9F.g3cX(@2or64IOX(h6cX(NK&F.=6$44i9cX(RW&F.UOOX(j<cX(Gu<X(7$$44k?cX(\"\n        \"bFf34]&9F.\"\n        \",SqF.&/\"\n        \":F.mEcX(gUf34d$PX(UlnqVFtUuYEU78%An=X(tw$_]Ms5MTo-2s-d)Q]4KV2H*<uF>,\"\n        \"eVYD40+c&#$ZjjL?D5h#,7>##CWT5&hi*87<oE=.1f#Gi%qcFu)CJPut;Gh3?W+:$\"\n        \"DB(7#AF>F#l####DXYF#a9wx-P<fnLDHC*#<tIv#:m*87iwGcMU*IAOEMp34JwcA#TQM#$Q-\"\n        \"GHO'GZY#,>fB.Kk*8I%D2T.H]wMKT5o3%?xkh2TX`u6v7f+>Zmad=tZ#9%tuV&=^Z%o1\"\n        \"%0'N<5>%H5cI(sL`cpG.DI=Vd(sW3bRk+_JO,7A467qKPgn@p.oBN5]x>80ulP[w0$b+\"\n        \"x0hxhw0q:r1TZ[g%O2W%d)ZD5m'epSAcbEl+MQcm-N#Q<UMTi?lLZ$[lLd5E%$Cm*87[>uu#\"\n        \"0jf7Ru*6v,qdai0<(ku5sm]*+nxi<%*_$c*g@]T(VS@h(U0As$12gfL9i?>#=3+ve6TaRuv>\"\n        \"XDcY`skLCR,@5H-k-$q_k[$2t`a4309R/=/9f3QGg+4BYB+*9)MT/AgB*YixId)GO7lL\"\n        \"vu/?/\"\n        \":JSx$h@XI)ZYl3^Mx5nKG[WoBZ4PIFkZ'aF*J6#JZ$GF5Nr7V8[2&YBJ+R)F;IET%nc`E3[@\"\n        \"qiBilI$7Fh$Hr<R-3<BT<b$soBs6&30%6qu#uoFWdq&hU70usmXoL$b1$%P='&+\"\n        \"6_Zi9X&%;%_Anh(YTId)RAe`*WLr?#PrgY&I'TM'+$7W&B*n`*ZT'q%/\"\n        \"`($#j=;D5YJMV(L*'9((^o23^s/K2&uf:/9<UUiv8=c4MCn8%JTrHMMuO/\"\n        \"*ojHKFd@*D5Hc8^-l]c;7Z$[c4\"\n        \"=:Va><rbC.sg`s?DX7/37qnYuZ/j&-;AeY>L@u'&:1#K;+*,##mO/N-e_`/\"\n        \"7gp+X7hu;?>(Dcv,=LET%]CcC+d[XX$_Xg-#gW:j;E%/9A^XT=cN73u7qj1T/\"\n        \"N?(l:d$8I*APq;.vrse)\"\n        \"BiaF3?U(=S<=A^--4N;.b0o$7poO:A='R64ThW@&_dgs$r]n^+5v.W9d^D3(h]K88IPWO'$<\"\n        \"leN'suB6Krd@-wb#m'Euv1:%a9m'=,$97uOf5#iU[Z7vnCgC@K'#lA?:g(f[/CbSv1S&\"\n        \"<=M?#02G>#q3wK#oCY>#P22o&A:@<$6Bk>7P4_t@3KV#A_?;DkSQ-Z-kB8?M,sUD3*VE%$\"\n        \"0P2g);EWD#LWPa^[lNGcW,rgS/W43QF?HD#:e$S#:ohxL3;:2##?Yp7FNft#wB(7#<t^]7\"\n        \"A6=.DUw35&IRE5&C=)?%sF5##C:DZ#`<2gC9jb>#'V=;-9FRw#O+eS%;$cf(-jcW.<0'\"\n        \"CmgcKK-v>KsMl+T.cSo'D(D5T)N(v9a#'^B.*GEEb$-rK)N+4qW$kh6J*Sg'B#%VW*%*q#<8\"\n        \"n/\"\n        \"[X9UhxO*Hj=k2+=-&%ls*5n+[):'IvFn8UBet&bnsx?ZH;D7h-iC4RYMG5qJ%vAG8Ql9(G-\"\n        \"k%)`$##c]AS[+u'58G6hQ&Pq7X'K9Y/(I+YJCIlP>#d'&@#=ee@#G(7<$5g#X$4R-@5\"\n        \"nl^t@VF+F3e#vC3>91(Mb]d)MYvdh2NaZ&4lJu-$r-h_4rp&.$6)TF4GIM8J+nmu5d5ptkt)\"\n        \"KZAkR_?Pd<A#,YA/.,Co%SYp+'PDY@Pdj+9/kKT*,##ZQ0K-fxMd%I8/;6KEh6&2[G>>\"\n        \"UpBU%<)Q##fK4]#F1>^$:'Y/\"\n        \"(G<0Y$PIR<$7DB,M2<,GGS9VRuVZVFp'xG]2Vm$&461F]-nS6<.]H7g)QiaF3h<Ss$lFH>#\"\n        \"c_Q+S9jkReOO.QSg&AvNKBVn:*W-P*5LGA+xqbcFo*#o9\"\n        \"gOlcul15##(w<J-qGZc%/,pu,_KFa,IApu&-a-W*8xPZ*b(F**glK/\"\n        \")M?xw#p>OMCji*87FT-@5fD:A&sjrI3Z]B.*9tUhLTF`hLK48i))wZ)4K20J3I/\"\n        \"9f33(+k:`1H&JQK'rbR%Lk;\"\n        \"r=xJQ:3CE#9jU5)wLbA71*L#@g,`XH^V[m/\"\n        \"@n1c#tM4.#t>$(#k$mI)x^]N0YE)4'a0C#?cSL>$gVUY$Tww+2SY15/\"\n        \"+vn=3;=&(Mnjr6p`>TK2nl)T/Y<#;/$m(b4$=_C4(g^I*86Rt/\"\n        \"n:+E*%Oi8.a6;i%#ZaHZunBB>-M#`>/\"\n        \"Zg*%2fux%-]ML3]Un0#2Q`q)Jo-9%'Fn>%=vcR2&rVl2T/\"\n        \"gj2(Ddk2*Gp_>cuCv-Cn#]>nK979(wFL<(ZNMUfrao7*rSD*=<xr$Dp_N$;7@W$\"\n        \"fc*87&[)22]4=1)Jm4:.4$&^dt#]]RQjAE#$VaL##l#;#[%EW-FlZg#X2_'#3X2V730M($)\"\n        \"9>>>BM<5&fBx+2g^^GuhBGb79,B+4L7H&%JU'$;1i;F#^dq&_m7`'81Zd_#J7C/#7Na5]\"\n        \"%$&##4T#LGiH7g)-%cQ#19V&vugu'vB#dE6De''#MOtA#2Tr,#DGn8#**F?#:G,##C.rr$U(\"\n        \"se$HnP&#8KKC-vL-X.8w[H#PJ-X..%,N^1+Oj$G3i#$8P,AFIb[D4<u^;%E[J-*:k+vG\"\n        \"PpbvGR3[a.SALH()6rH-)[AN-.VjfLrHo_sg[X5MS^VCCQaR9Cc,2o%3%9PJPw<,2F;8>,s]\"\n        \"P>#7%r;$0c68%Y*x+2&[)22^r+@5vMeL2)Rm)4hX.W$#Bq7@0]hF`Id/W_Nf<.O,jqjt\"\n        \"wB9PJIge_8Vf1Z#*AY>#c&N#$p9b^-WDp?9vX57A$=*c`A(`m<CD-o_a5:a5O?Jt#N_+6#f]\"\n        \":k^L[f'-_uY:.kf5HpovHo>v65H3ae_F*oKOg$rUXEe3lh8.a%NT/nS=t&EZnb4x3<W-\"\n        \"SCd?,vr]>,evt#7PnAY.*.uM(UkLiqT+sJ+,PKf)Z(5I)bM?Y-?u&(4K:@w^B?U*#i'>Y#$\"\n        \"CP##=k.Z#W1x+2@ojh2H$vM(o#rE#0&DMB$KrTdpL^IENE(MpDnNP&@Gt5(=hAQ&@q/6&\"\n        \"cbQp&Ej,emVD0l(.(v'cb:Y@5?gh@ns5H<2a+u<Rcj(T._xbwQl5)X-`C(qrqb2v#H)\"\n        \"KYJrY/J3(jE<@OMDE%DklA#+E?9/Z5o-#W:4gL[+kuLPNa^/\"\n        \"BHvFr?9&v#RY(<6*TF;$UD:Ip\"\n        \"$l0(4O^rFr0@D*#WSZ$v>J)s#&(*)#,eoW7(T751_c[)*w`>##5jMD-L%@s$(-###iim9%\"\n        \"RZNP(CQYO-L4.#'/]im9X`Ua4=/9f3UPkQ1PXRb%Nwg*#?A>d$OBD_$B(WT/1veA464Vj9\"\n        \"X]&B-ascx64T8Z9WMmp8vjngL$t0B-a#v=75ZJv9Q,$986FH/2Xv<[?8/_'/\"\n        \"8]VC,gV[v$?+$$,u(XV&x4ek0eNIg<^*qv-./\"\n        \"42(Jb-7*(6r6*Fuwx4+lev$ASfd)+xwv$*@3$#;K?D*\"\n        \")F>fqE/QM'.B%<&2F/\"\n        \"2';YaP&O%087's'*)B9eh2=CLwgi5oO([&<;6diu[JM@a%<h4G]X]nNN*BaJ:Z$e,.q%/\"\n        \"5##MsZ>5<#O9#^3=&#%0k%,Sh&/+l.'j(B7/R&Z?3L#'?)8&R:D$#\"\n        \"m]*877p5k(I?%12fX]Ru7qme$2TNI38+*iM.U^=%iJl-$0vv;%UYlS.R1x;%Pd#P0^]\"\n        \"4QD2Nr.,c[DxL*ocW-6OY_&64n4,CIX)cY2u0#:7xI8#`tA#OSG3km>+GDXCh(j0135&[te+\"\n        \"M\"\n        \"B4'N2ZG*F3&,0:T$lpXPV$Z&5vucN#9D(7#-4QU7=CE5&xJ'<$Xww+2WABWMT/\"\n        \">&94?HO(x6Er<3K_e#`kUUb2'*vOS6'%#HAVq%Cx*k'ECIw#39x0:0MY##=lc/\"\n        \"2v-MkLGhdh2AVD:%\"\n        \"cYjaGkcv3'rC[&YlB,oXZT0L>.TJ-:H(wE#i1u^]uo1#v#kOm#hYt&#A]&*#pmE%,5U7X-m)\"\n        \"T%#Euj)3vH%*+?fQ#-op5<7Rk8B3tZ&m2`B3mL:eRC>[Fu`4u%q;-=x6q$%3*E*X5Du$\"\n        \"kTW:.$Ah8.9O]s$Q'=W-2+X#R#YAg%x#qf(Uwi0<:sG<.1)E*<0)3T]Hh(C$J#FtCKI$A8)\"\n        \"EHX@Y+,b3o+=#.`-q9&l85Y-N2^;'Ft(9&igp#.r)Af=iQGh4C&$UBYMBu0Q_M02tVjO0\"\n        \"V5Vu7W(aE,:(O8&%.9:/\"\n        \"Bl)=.$),##<%8P#[M9:#>3&W7KXNT%@wSQ&Bens$=P,##8uY##p0g-9/eo>GgE>V/\"\n        \"wwGH3*>`:%:0`pJE-`m*Mwat&#3Dj=H*`xXcXIl9)B-63c+m81;6YY#\"\n        \"d0XxbW12G`Ej?D*J]8*'F`;bNkc*87r]^a2Z,d8/\"\n        \"K)'J3?DXI)dYSF4+87<.uiWI)Y*Ko9t<Y4:9v68/AQk-$`PnC:qG7q9be7e4X>H8/\"\n        \"9a6w^o@-##_D',;]$j;-5te/&;Y#v,f8r3;\"\n        \"T:k(Nr*/\"\n        \"s$@&'##P?`3=vX0^#)Lm@.JH7g)7EDn<6$Ge-$REL#cbpC-qaFo$bL?Db_3mA#b8.I-*K^\"\n        \"4M`OH=M;ru-MQf,a4D(nK#E85##I^/Q]OKcf('f'N2F;R_#E91T%:$0[#xL0+*\"\n        \"L$MH03nnY6K;t&5k'pu.]EMxT/\"\n        \"FeqTVeu_P.`YK;`^L1MrU8wub:u@N`Gfh(n%&:;PFr9;f1NT/\"\n        \"C;q9;YCn8%0mSK3L,FU01eR>6>Fx_P0fug;gX8c4V3=&5o]];%[Gt`=j8E#$atG#$\"\n        \":wU<.gHii<KCm-$%ux>-MMQp/raM1)uEE%==^FZ-VK]KltK3X:[X2X:dt+/\"\n        \"(5;DG#g)l4MX]DQ7I^mP.J#G:.04X2CikXvLct'EuIsc-?xqNq2c=sq2OZ[`$F%Ar2k;oO(\"\n        \"I(.m/fOkGn\"\n        \"=,v@t%?vpLNQFjLpE:sL]JQ&#KjB%#NCKfFcLJ-VVsh0(aaBJ+B,X]%L72mLYS,@5]>T/\"\n        \"=x$(+*x@XI)(OE'/+aS+4Q@]0>H$,S9Ii>g)Z02O+K7eMTW;Q-5]B<2<Qi`p/E7Ed4B/PS7\"\n        \"1/3QT7*1H>.plQ1A0px@DsUE/\"\n        \"b_AN-`-%I-u5e63?^X1:F]lN+OqWA+L)`?>wt0DNj`wE.*gE.3rW@x[[WL9Mq_IIM5YN'/\"\n        \"PFn4CnA<qMm(^,=:qSa+6T8/=f#5G-jPG@0A-d>1.BEAR\"\n        \"1;0XLRu$J_Wa_xX67RS%07###,mjh2`B]?&A*Rt3mcK+4T`I*9h^PW8%[:*#J(-5/RwD;$V/\"\n        \"^V$8*,6#,G>##LYKwL=9aa4#?ttZdbg4M*E4'i+Nfw0r:Vw0VS?x0O8-x0*:>V/CoeK>\"\n        \"_6%JO(F('iRF<L>jn%@0H7g+M*c68%F?^;e#)P:v^X3B-_a`=-ei%Y-AZ4R*f3[Y#1Tt.1m*\"\n        \"9DWs0br?ipb&-_O<.;wnr@,t*c9/V@5J,/g=B,t_u%?fKr&?%)V$#`k8B3IeJe;=D?4(\"\n        \"p%2F39O<HpV_Ds-iHR+41el[$2Mte)bK4kM%6^,(uvrI3Ya`I3nJGd$^Pq;.J#$X$sh0?>;-\"\n        \"/ZPY?3TU7Gq>I`OHqIw8LC>$wj$6hR_h3CEsl2D#]Y%Mvx68].5d3=H]vCQMF+AAkTU(\"\n        \"K.@^,r'_V%&_b^6J]@q98[1%SU/QpU6r'HYpcP$ID=>n0&88B7fJu/4w)IbunT:x?`1b/\"\n        \"O3MWRFS[50?nJJj2/,R:9tZ;.=g&AK<f(058Lb&GVvnOMBJcA,3ZTYT%`UBS@IH*H4hCAW.\"\n        \"G6,2*]a+)+P.jB#ekfQ0x>DB>c4sB#gw+n0/H'X[v(i]#eRfi1eUiY&`$32<@#m6/\"\n        \"1S>3pQe^#8[MBj(%V$B?(W8f3RJg+4YQpK'Y:&8/I=Rs$;0..M1fDu7w,B&?*N,x6#e/\"\n        \",5bh4V9\"\n        \"@HP]&?W>/\"\n        \"*%]^<?91Ws-ne7WAT;OZS0Iun0PSvw7I1H12F:&r86a8<&LClx8RfF%?FnGx7Xic/\"\n        \"C$qUs-04Kd<i/5##45YY#mC(7#4`($#Rf7r%4OJ?#_DbE&;ZN5&7%@8%8i1?#N.i>7\"\n        \"2[)22Z#Q;pdIfC#LZ1T/\"\n        \"NYD]-O8*&+5>0Y?c35e8qU)bN$ed19tH>X#06>##'1QD-?Q^S1Ke[%#Ow`V7]V/\"\n        \"B+h8D_&GkeY#.ETS%:]EQ75Z)22-r%Q/f7K,3x@XI)h6+=-6LXg$inr;.\"\n        \"M=fuGn_JA6S&RMeZRZh<5qKL<*67G`K^fRA@R/\"\n        \"c59E%t98R?`4frTf3RjA>#W.'cri[AVd]fw%+80iK(/\"\n        \">iv#j00F,7MWlAC)#n_ir*?%B`WBQUOqkLf*Oc4j@h8.hKtxT^c-<'mT1J;\"\n        \"5.hs&.8h4E:,66)nB2CA]MV[1&YwC?^])#75bbkL<5AD,q3]Y#`VtlT&R%t%FB,/\"\n        \"(cHbWqrI9XqKZde<fZaYJpeK)vJZ/\"\n        \"(Th#&AJh>sl#UfcZ17(5nL$4DWLF^5TA:Bqc)(o$U&5jw8'\"\n        \",vGT%Pr_3=&%=W8(0Z`-2'4L#%ej-$xSJH#xNVO#5=B1pFT4G`Ejv,t8$tw9m[V'Sf)Xp'\"\n        \"3J2U%u:eS%keX,M,),87d:+22Ahf=:jGJH#xZiO#7[>.qaaHAu%Qr/8<>vlLfWVV-OI2K*\"\n        \"M=>A+WqeS%e<02'<>mT%J;uh:@$8?&lZ)22(Lr=/\"\n        \"PNv)4ea$M;rWXs6w`E.3?3<a4t[Rs$_1RvTGqeb@0fBL,0Vf%'Hhb/\"\n        \"'6&5H&mtdQ<ZPr[1#,Df=6ki.P#6l[tRaQaut?--NrFetL\"\n        \"4a3$#@j#=$[oH?#D[aP&@CIW$)55##]JWj$*REH3Df*F34vKe$`@5G#Jdobua=(nu#Y?\"\n        \"CuQGa`u,QT_-NIm9;T135&c7T5qVgdh21/<u.?%4G`_.Ge-R^%crYkLe-oEGJ(B$M;$T/EDN\"\n        \"fFgt@V1xJjrf9x6R7UMqKnbI;jDnq&,enCHFg#+>j$nqAC__nuZ9j(8ToUPKO0PE++oNp%\"\n        \"dXOGM4mh`NNmY@5PCPB#&j98.pN_hLnWJPD1j*v,tl/sLMBRN#p.QrulduwJxVck-KDGq;\"\n        \"]R/\"\n        \"2'4O1v#gLQa$*'vY-P`XG<m%s`O:-uZ-[Cv?0%Esx+0:BQ&7O&m&D[*9%MhCd-uto'\"\n        \"J7OBD3ldfw#eC,RA</i?#0c8Fu%U<[9NN4,;7N&p..C%.$sG3^#KUIu/.FmU7;oLZ#&ng_-\"\n        \"fFAhc.%:hLT<QJ(WBJ]O<=U+M,>[Lgs:5[9_gwhg%/\"\n        \"5##YNft#I=I8#YKb&#RCei:8Bbh:vMuH5a]Nv%>K+Q80`1DHnJ0>GIU_6:KN'*N'sFEE.v;\"\n        \"x&/`A#'O=w91%Y[c>*#:B>Rshp2\"\n        \"<eRuLY(t:MGZVpLoMpnLoJ3$#8:ZU77Wn<&U2Rd)+W,eQXpxD50C+BZ/\"\n        \"L1N(NXxV%-D7RIE;OPWrELD?*j6G`lSlOB2UfX#jtfd+RX-_fOe1_f57EM09&=T'mI^g/\"\n        \"8:-G*PmIL(+U*-3\"\n        \"U%vC3JCr3*<A#u=jcLa4$=_C4gkU`$xrse)h]6.m7s#j3,1Sv&[g0IE?R^]Q<$ve@?r-[/\"\n        \"*9$p2GV)-QkODpLUma$#BX2V7>^Rw%YN9U%,?N+N[AEU-S8,4/dJ_#$Fdxkt-,6NJ9NvdX\"\n        \"5[(n&Yc<`?lwxj#hdEN*1Z([umje[uTDha6PuD#7Qh@J1[`&p7roGJ1BS9Q(tgGj'Qrhi:Y=\"\n        \";v#evrl82`9N(dr+@5Pmp<-)gS3%k(xU%r8wx49j8h*-;]^=u$S5Lu@3u9'/O0,&28%'\"\n        \"8IJ]69+qMNb&r)/fRrD5Q/\"\n        \"sV&Q3CB'(0Zk:*a7b6p*2I3a<2kB@Z@5DS-5f%hl###g[2G`9kfi')t(/\"\n        \":0rQS%JNGDu'VNI3XL3]-gk6'tSHx?l)_9q9_.r92:BjWhb#q92/C,/(OpE5&\"\n        \"-9M_$YT<8pXjeh2OZ'u$]]d8/\"\n        \"X$nO(DBj87-@PLOi6fNO;t8Z&FF4G`BYU_SCpBYYJ`8OEO##;ZWH8m'djMAG'MGw$Fr5N)\"\n        \"4wt2$^DUD6)CYcM;^u7KleW#A+e*XC[ZCD3(DeC#3aVFr\"\n        \"2.JU>*a9G`$pg`<4[r9;j4$##^nm9;6SHP/\"\n        \"34b5&@eGj#$OuY#9[aT%hL+gLDE;m$+VLm($BYs@FEeh24nanLv[M8.o2Tv-s,*r<O=wLj`\"\n        \"d-Y;>JYHa#@WCaTH`19$1[F`=*gG*CL%pJ\"\n        \"D6+V&6s<T'>Xs5&.P(Z#5(eS%ku*873hoG*Yi^wLNkxw#?hV&$'A1lgH'G+rq%\"\n        \"3JAAUVM0bV&,DaJhRn=wY:Z;=n3=x1`3=ldb3=jECG)ur1Z#2Y(v#t1,dM%I7eM]00@#Lf-\"\n        \"adD)/Y#\"\n        \"5A2ku$subu^$8D;AVX&#f>wE7iQ_c)v1@8%Qt`M/\"\n        \"r;5##ZaokLmHwQ2i[xC#ZH7g)05vk0(*o=G/x>`EV3Pa#3nPDu8d[Y#BD&cr[]1p%f*IP/\"\n        \"uuVs/M8$9%r1OE*)<gN0%<Bw#Gq82'\"\n        \"XE:P'N*[S'>Fi?#mhq8.H%OP&-ps6pP8V#AS7V#AM/\"\n        \"hA5U2r-%eqWq.=XTx$?\\?hauFsp4hc1>R:'=kmAXV^LC@,STCsal)61orl13n*39q^VR:'=`\"\n        \"^+pBk'-YRk<Bm;+Q2&0Na*N[om0\"\n        \"v(;nLO3/d<AucP/\"\n        \"ktfc)hOTY,6A+x>NBbT%W['#?;]Y##q%+87jd#[T9XrkLYDDs-STF:.5s]-D,6v#@\"\n        \"b3t89oIeL:on8G5[7[H5k?o/*r]')?1QC69;_PB41Ce(jR]*h2AOCx.b;.89\"\n        \"XS5W@1hu[7NO>,#X`m$vUEvr#7K&C%afmT%aq=x>R1/Z$()=qM$Z$RCI#q_,_r//\"\n        \"Em=esArjv<6JD#'?L)eg1rGl/*W'egL'WbeFE+/`,#9#a?#XvY/\"\n        \"'&_[6x0S'Jm(iZ#iN7%#P_;V7\"\n        \"[k%p(_Z#7&TR9#?Alc##*>[;7Y?^8%Ldks-k+2u7f;u`49(kT%1AXI)3f^97]R]oBL6w(>\"\n        \"AH0L*u^Yp%8UB600x_p7mwL?552?jL[a>70tH.P/%io79NZc&-1dj#?X-pD?gHDC#(cu##\"\n        \"aKb&#fa3%,oC(hLE1,9%g2.)*e)aF*FwNP&GX$u@@6Qp:x.u/\"\n        \"*)%(f)?8kfksnlV-q+<T%UhseD,M_w8[uUK4RBAD<=dG=7FMBlB1l;5/JFZs6$/\"\n        \"N*IAt#G6E#m^6q:av-XR+D#K^.O;\"\n        \"w),##F,>>#@mg:#9LvU7B>tx%7a5>><SY##V.i>7?P,M7=ubo$h]A+4IsaI3.;Rv$2+^=%\"\n        \"Ncc7E=HlG>73Hq]?rh+`HNY=A[5^F4ToJ*#/x,Q0CmpdmoJ>&#o8YX7%#X&+mB-$?cb0#?\"\n        \"ZM***QevY#D_<2*O_mY#gBZENY,&7pn9*K3@SHM&a9Ux$FfeQ/\"\n        \"_;oO(d#_#$aO4U%MqCp3B;Ud4gau$HZ+%h4Zfx#%9/\"\n        \"hBHCY;N9icBw%:E%ohGQCZ-NBe`6DVaM:4KH*?ml9(IJWag=\"\n        \"MbJZABkkXA$),##<M-U4cL9:#:@dU7Zdpm&C)8)$1>,##$K4mLOeKK-oX4W%<[w9.3q9H<p@\"\n        \"_s-@jT1*huav>^*l#7l][T0)1hIhO*mqCmi5/10[cg;u?P)MW9p(<q1[(WA6&v#A^?D*\"\n        \"H,N2K?([S%88oo7G4W;7US.F%i9rI3+3aI=vmxtuA)-C@`MN>.B01>5&7NV&L;<A+RcA3KG@\"\n        \"[S%`@Vs$2MMM':]EQ7iS4<%I)TF4WO6a$u;4[9eAd8/.F6h<u6cf8R'=20@^eE>+Rj3=\"\n        \"uj4g)he$T&,%.U&?1rv#=:wS%gRbGM1YKm(?kPwpf<_=%d3S8%[UK&uT&JJBDe_Nk1p,>\"\n        \"bH5_P&Puq,4@.&GVh2E#$n`Cs->[I/NxI(i<$Z4P/OLc>5tG,p%lnPV-QJA9&g^,7&Qxim&\"\n        \"Q]8)$^K3-vM+$K3s$#)NxRKC4GaE.39Gg;-Dstw&l9LE3^1we4<O6-*eSe#>H1/A6[Wc;/\"\n        \"jUjZ6OT$S@(j1o2.fa$'*cPdM&nlgLC;Ag'uN7vns.>LPb^jR:fJUv7VS&UD8Nc(PBA_l8\"\n        \"r&4m'FvQ-XZI1#&@m;,#Il@%1igPjDIU:@8O:r*%bmPT%X@9#8txp`?);l4<@tg+6AC1x.\"\n        \"bG@89U5BZ?&B^;-Ak6a.X5a`#spFs1_^''#LL5e)^']P)JK&s7Af%D?b'N/'_]d8/]'ER8\"\n        \"Zp;w$xL0+*+I6x>?GU5%2<3D>W,BWor[xo#N$(F4&QPp.h?(loWsWso]j*J-0k*J-_p*J-\"\n        \"187Y1`e9#?bAe)*6S&Q&,_j5Cp)&7pt_k[$opY9)T2&'H,'Xt*Rh[29^bA<7v>$qpnV@90\"\n        \"k&2bR&Doc)7llKP9f'fFoJo?\\?_<I>#N^uA-]=QjL8ufD?Ca;a3;vS>#A9GJ(O&XT%vYu>#=\"\n        \"s>5h+bNb.ZH/i)*TOO%_#jR:IOL32CGFG#Qjj[6wUlcux+,##BEJX#(^EZ3]9/W7r;(u$\"\n        \"fbU%,X$C:%0Trc#+Hcg:.ZY)4bs7?\\?&8JduAo-L*$Mte)bg'B#A=rX7u(tV&KwR]87*9;'&\"\n        \"pTU7;kpa3#L1$Htk=wADmlP063sP85FZm8OKIg<H;HP9@uPv,d<Tq%PEkj9[/)Y45v=Y#\"\n        \">e,3#8FmU7S[.5)>dP>>jR4gL:woG9k=HZ$]p*P(n65T%+P0N(_g`I3`kg>?C(3l<[cY@1S%\"\n        \"WA=)b+98D),auLnvT8Wqe8.tco=uN1W20Wh<2*Yslj'Y1@k=YBk>7@J&3%YtIW-]+6k2\"\n        \"UgtA#1]Gk<xg=Q/\"\n        \"%gjR:*SKs&.c>>58qPk;(tPD#@d-s?j&R'A9.rZ^b:X'AU_Z##F&?=$Z%5v,A<Av#kl0K:\"\n        \"1o-s$m%+87Hl@92X]^:7V*cO:d]cHju)9BT<%n=u$ws1T=-;b7eww%#\"\n        \"@Wc)MYq<$#gag5&j<a=lT:D%,gxU>#3=WP&F[ws$ZCUp$,6g8.XiZs0w%j013KPaIXBKQ<[\"\n        \"rO&Jvfp81$7Kiu$1Biua)9iu6v;M-Wlr6Mc.3$#>,)U%EWV2%WFE5&f'N90u20Z-2cg/)\"\n        \"w9$`4r_@@#*Vq+@u5'NNqE%^_mSjY#QR/\"\n        \"rt?Z<[9Y=7wu+9p9;up$)*)cws$@Lns$<quL%9'4mL+XB(4KG>c4.Q5n%#AC8.3OZh2dWRS>\"\n        \"$tD=f6Y^j2@bY?6@s+uu#j`OuVJc*,7'rS/\"\n        \"vO[AYmh_f:*$:B>>x:Z#xF7g:i?fU/\"\n        \")[F4o+DmUm.UkD#vXP`uT6xE7flT=uLg4G`UX`6'?'iV$x`l##^6wNOnA3I)&S:hPP30@#@\"\n        \"QfRDrO3G`u1Xc:G@s@k%/5##*D&&$6?Jc4CF.%#\"\n        \"TViU%6PuY#A6p2'ioe-)w0E[#-?`)3_*Yh(k=H7cI<Y@5AWQJ(xMQb<H$tY-d?5T%\"\n        \"x5FD8YQLa5A(Agu37:5/\"\n        \"'3ED#(99U#vtdK#$$sW#$<AX#3ox_u.+>>#t>CT2Alg:#n]W5$*Mc##\"\n        \"ltG3#6e`=-uq`=-?#&Y-cHLR*k?t92;qc7efY.GM'86Yu[WM#$>fS>-GSoY-pZM.-K1p=YX*\"\n        \"qJN(N?>#C.$##,9.k`uOQ7E<ElG>S6T6gQ;/k`*w,D-T1H`-HVu3)7;?X$3:@R(6T%>/\"\n        \"&</l1i4,lP]17##-?-C#l<I8#d^''#6dTa+s@c/\"\n        \"(P9mR*=#x'#grD0(I4@h*FtJ6&Gn:C&vq82'vf*87xJ46pE8Bk(rZ4N3v5)@5'u/\"\n        \";%Y&Qq.2tSs$gJHD6'^qr]]L.AS<s?h*Eume4\"\n        \"ZP:(6(($RKk;j50nVcX7rh1d4W:]@#Z/[o/:NH-t)MLZdu?@A,3Y1-4%/5##M7aM-,x/\"\n        \"(%U3`v%ki'##KVD(&EQ'5p;@@UMLp-B=`g[^l[2&128g.E3S]CkL<>)H22IC=%+$;E#g)?D6\"\n        \"U-no1F.OB,q7h'5aJCS8?lEB,UF%##9uclLpv3ckJ].,);h?M9Ka&E5Y0k:8'LBE#Rr)%0[<\"\n        \"0V8)UKE#Akvd3Xlk]+oQ*c45xZH3V`OA+eXG12M%720;P2(&k2=1#kE+)6`;*22DQN@?\"\n        \"tXlb%t[e[-PG.*&srGs-fOim9*evv$xL0+*pwS+6R,^O9>mT.tE%L:DHON=S(Nn6MlN@C%\"\n        \"8GoA8_qAoLPf1u79lD)?A:R]%S^s97in=^41k6>%jqPW6l-m%?DK(LCZK,;TD-,tT%=89K\"\n        \"`qWuJW#asL*AB?9T7KK+RlGi2YAd<8>aL'-idr*4';G##v,hB#]Mk,%51J(#8V[Z76:f=\"\n        \"1dE&:3;=;)3xCEX.dN#'#(?L^#^3VS.Yv4B,jf'##EK/(8w5-O(I?%12E:?3pB3)@5ZKk;-\"\n        \">(^)*L?'t-aiO@?1>DH*^Mh;.AG@['T'.E38nBs56Q=@?JT&&?n/-)6nSEd=SR;p7m/\"\n        \"C,;q$u/2(cdY#^(WT/\"\n        \"OXC*4RrTZ5FD$Y@.Y2<@x(=;9PUEPMmpBx70c-;%RPFl;YbKV%:),##\"\n        \"^mo=#[5?;#H#gm]T[R9%6b:I3O$lh2+;gF44;6J*Km?d)hZD=6rk^F*$Z24'l0oUH1`x1('\"\n        \"5E4'oE)OBeB></C0j<#o;a'MVc63^HW=vGk]*87MTnkL,0&6KrD-E3art.)g_1^%FQ;fq\"\n        \",v3e)xg@EH1f4M(#)>>#tU$0#t3=*$Qf<9#SNN1#9'=8%L)X,4=ImQWbX`mLi')'#I%###(\"\n        \"J,W-$e@R3;9/X#rFuG-vY_@-gnm.N08i4Jc'u1Bqb-:;6ut3+aOEk=X*GQ#+:-L>>98X#\"\n        \"UelF#QDUl1.U4Q#Y,Y:v`=5'vRGeYM[Ud>#?AFR-7V?]/ntx=#9^/Q]6aV)3t/\"\n        \"oO(^c'i<$81;$aIR7n4qg+Ms$3$#Q&mb,Yddh28nbd;KfUO)SIQ:vVh+>5;b=e?D8pFi4i,<\"\n        \"%C.X.q\"\n        \"ZPmTAh#*Ds^^35/QtKR*@*UJ(-B<,<4S4vQ-Q;R*6nLR*NC&:2G/\"\n        \"5##.h,K1GXI%#Ji*O'*U=1:`^3L#U;I<?<r#k(I?%12uZC3p@%3g)8ne;-phT`$j-Ss-%\"\n        \"Vlk>i#4nKlV(lB.Z308\"\n        \"][+<80g,W-OKjER)Icf(K1Qj)B%xN'Bbes$*;.<$38mT%.I>b##q4T.6KW6pm`sfL`ZMV?\"\n        \"so,[-cM<^4xer?#*vA2@Sv0^u`9jmH$c$s?Y.-ZZ?PgoIe9^?.4ax._MZ'O(_@5;-?H`k'\"\n        \"a7cgL)nMK(;$&0:C<oY-wOf$B#78m8F&,rDI6x9.xr?m/\"\n        \"liC^S`gij#>>](7eTvAJ`ecZ1NNmppHLY6:PsY?KRUr0#.dI)vt$<'MNd)'#egW[,ECk0(=\"\n        \"C7g:r'6?,EcZ]#+v*87GG_c$\"\n        \"+IeG3lI@X-4bX*I;o0N(7%x[-L#IhEhfU+tZ]8d+JXrp7^t79KDYL;9cV(K#d<'=AN/\"\n        \"?w&S#es.#W)H2=?9w6Ck4cuD6<6H(f#Y+Q*e=&483:B'U::#Z2:=-OitK&&N[=/\"\n        \"Rf>@&7OG_=\"\n        \"%+0:BNRx(WnMZd;>%Jg:P#h#vgNGb#^J6(#NuJ*#31t:8_hOP&1I75/\"\n        \"$nl<%ZK5&-+ZLB#d7'$5Y3h:%N$I8'V%^B+J0pQ&Df>w5K9PN'[-^M':0gx=s&\"\n        \"gG3sgNN0OGaRuoZ7B3Gd8(>\"\n        \"wPqtSi)-]'b]d8/$Ah8.:7^CQB0Ej9axJGjG=fCd^42^6[R-;@^:w8B9n/\"\n        \"M#Ni]*3vSC,+sN]68+6q+E#dYP/\"\n        \"KFZs6Rh7l5rTH:&koQH;O>B%%t@6u:0EVS0G0dE=O#7^5rPjF=2i%?5\"\n        \">,N20Jg[?\\?p5>##OW+:$Uau##0&*)#1W1Y7#*YA#.se].4qD+*%V>;-?/T&?p)3%?k9/\"\n        \"N0X'/LafpdT-SkWV$_U)huV_k[$l63BOo(V-%kAshLx:Uv-Z_qSCvQGa=QP@&,gHOp.OGu%,\"\n        \"8.vWqf^uX8$sbK0x1Bw8`Pl(,D68Ik#_NM0V])Z6`oqP%6d<.3@kdp--mZ=]7WOn2P'0O&?\"\n        \"rdg:o$A>#dR'crGWp7nnb<'op9rN(48%@#Z9aH(h<Rt-&DFx><@oVRVC[8%B6M'oKqj>7\"\n        \"pZSj(i`HsK`gl8/\"\n        \"I9`U@nAc)4oPq;.%$*gLv7Rv$oYe^$3]q;-K'G<%6)T`E5c;;.Ko@;%)#/\"\n        \"U9,nnx8Qbg99YVQ%&9]e>-&Du[?AMgF*t0Wn8_0qp9v;8%5F'ZDu.RpU@pDw.+f62f)\"\n        \"t#5V70MKfLY%R+#;[R@#GG&e@?%/\"\n        \"9&?:@s$8gsb$u$hU@$],g)9X5-24_kv]%.e_sWJV7e'&H/\"\n        \"XvM_TA4kBJLHJMJ(urS039cRN'<O[w#8DcY#ku*875YQlLO,eh2F>eC#7.5I)G_jl@\"\n        \"%Is@?*gM:BaV5G`$S;,2lk<8CQ7f]#CiV^MuQ^S1;`u##ER)V7O1mr'C=hI=+XPgLd)#\"\n        \"K36k&Z$3@Es-^T<FNaW]+4IQ6B=xJq2:5dm8/o-TZ$4b4Q/IZJY8+v2;?$xRT./5YY#>8eY3\"\n        \"Kqn%#Sn:b*6S6A#.9F1:+_I0:jY1<-qaME%;k07/\"\n        \"N#G:.85auAIs[&cojEW8kG4)Bv:1mpg)P:v@hd=0jhwRn%5YY#]WEqKsQVH*pM?Z.HxIR&\"\n        \"EUEM*_gTr(AtF#)Hv$1(QQUg(=@%s$\"\n        \"V99U%6`:Z#KCjs-R6*NE2fTdkC;Tc$&q1FZ6q1N(2=@X-2wEO<H_:JLw]c8.$;<JLuIX]\"\n        \"ueUr0#:Oeu#$$:uc)%=E@6`Ho'Gu5N),$r?%@YPV'5:%@#/JG>#Dd.n$g=$:A^XT=c0jcp%\"\n        \"mL3]-D6;hL`N7't@]`r#UaqZU?ELE#bJVau5lbIh?'fY%3X]M'D&62'BS9Q(9oU;$+5VZ#*\"\n        \"SUV$xXu>@E@%p(G^aI3S7C.3?TNI3Rj3c4ZkB_4rt?X-nMV9C$:YP#xh@O9jxNV?wm*Q<\"\n        \"10>.:ff*87BV+8[9N)&+t#BJ1K+6N)->Ij:?w<]#464b+Qx[3'ukFgL'9aI3T>uH?fIRF%\"\n        \"jVTI*U/mg$7saI3igfJCbh(T/d7ZpKthXI),<ug$9g.)*.aL&?cx6w&83qfNus<?#2,Gt&\"\n        \"D4].'4UC_u5JDs/YJJ@1NvXV0d#i#A'jaj0kUDJ#s4;`JBfHjPi$l59t(Q&#*(0T%ZX/\"\n        \"m&&gU`3faFa,lg^>$m$8W$8Zqf+%%7p.CieW$h3_fFP8Yw0>.x+2Vocd(@q'*)WE&7pv_9D5\"\n        \"]r+@5&3=1)/Mi[GMJMcY,=_F*HlE<%x@'T/YN/\"\n        \"Z&9VBZ>>^*f3Ux1<7S`_iFhRe-5b+YZ%n.e=$7_I@/\"\n        \"@>XJ2KkEd=Yq`T7cjQon=QOL:,F_.a5LkjP)^?e+:>ko0;/c&/pu-Z&lT7rQ\"\n        \"3f&j:Bj.C-OEe(N45W[?sg?>,2;r%4E1ux>)J0'+Rrr?>`HBq%a--$?%$=?#PqN%b3a.0:\"\n        \"sr*87x%(7p0/F3.ZDa1@9R>)4K_Is->eHv?x$`>ZhY61<PUBU%*LCx.JxGM2%mx%?kAC._\"\n        \"%io79M^uA-.WN^>VtJ)?$),##1<>O-M.1t3nW3@,R0=9%2jF?#C+=x>(Bk?#/I&N-2#Y,/\"\n        \"=VP##EQ3_]46RLN>#0i)OG'A[Y7r;.gm?d)&qZL<8VH;*h%kv>dYrE%fO&_#P,@:2280.E\"\n        \"VK*T#ZF+-<KkM<#^&lJ)Q4-W6W&>X7V/m>>LwO/\"\n        \"(NbDv#>UsP&cw+8&JfPV'4iKi<^VLt1LxXRuNak[$+aS+43LRLMTsaF.@.OF3^7TuA&n:\"\n        \"RDT3`[[.TZ+&*]Eb$uG=sJ086*8EHh->\"\n        \"c0-?$+i?8%+qeT&:)=T'c7ffLK'WZ#qCb&#IfeZpgBuU%v7@&@'SlG#,LOb5[.XFr8b5`\"\n        \"uP6>##%WEp#gV?(#e^''#i^fW7N,>>-[mn-)x&(H2muSD$->5##Jd[/3.Fp['@a(T/(?ekE\"\n        \"m'/V8hDcN`/\"\n        \"t60)mc#<-ce[+%NG.$$AhDV7=?[12b^#jDQW(C&^=<C&Otm?gewUp7w8Dc%2f4O'?tp^+H_\"\n        \"gB-,lX,<_SEC&Ttk,<RK>L#([F&@SPX`>CSV-*O>,%#(Cx1vEk,tLpxg%#\"\n        \":W^<$4cT;-4b+/(Isl$,,sM:k8`u23-h%4Ckp($[<u3+3,]&6/LcW/\"\n        \"2vwYZ,r*`D+a6Z)4OMKj2Os&R/\"\n        \"]P71(a2k*Ecrnv$ROKfL_Rg1vx41$^01bV$G'Jq&(G'j(#_)D+vt?lLtoF_l\"\n        \"#GAt%.#BqD`8VI5)uwv$&iQ:vQn1;-F8FqDD-iFr^:f+M@3k>dG:220gX$v#8*,6#TEP##\"\n        \"OH7rejfn]4N_HS'G,1xtYTC>#=4d'/(u`)/8%X?>$s`m0?Q5+#6FZ;#8Tj5]h#R&#HnYw.\"\n        \"j<);&@hDX:FH/\"\n        \";-f1=A+R;@D*F>9f).`^>$ABG3'JmwJ35*t8.vDp;.)?hD<jCSj0_g`I3m6mm'=_8j00(0Y.\"\n        \"sSGb+h`/#,m@aN'Eop>,m9<D+Q?[0)XDMO'<V0#HVFu50UD@D*B<pG*\"\n        \"peE6M4Vum'JK7[B'n6H3Of8W.-9r-M6,4w$p1ZB,X:f21k%+Q''mVE-)*8'.MOVmL)1LW7=.\"\n        \"hT7elk9'Kh70:ObC[gpW,/(Xb73(soQ-3ojW3VQQv)4i6fI4c#X?3&CQK)nfwl15_UK2\"\n        \"KLHG*$_(hL*U<i2eD.X/\"\n        \"af)t.5HI)*W4PG-DA1).Wn.nLqGA;1a+p^+^=L<-YPOt&rB?P.Cp;&+H5-s@N5Xp9JWA=-\"\n        \"eQ3C5/bCk1[9<D+bS<.)MwU0G.LsM:dPZo/K/m=$bwivu7NU3v\"\n        \"a+1/\"\n        \"#DMc##j<p*##)>>#-Ct]#iN*p#+qs1#+Pc##vM`,#9#,N^@*)E4i79r0sKx>-lKx>-,Rx>-\"\n        \"4Yx>-'tt;.mdxl/Y(*d*nGxM1VCUw95dOF.fGKF.qZb]#I#:W.`vOu#<,kB-(fXv-\"\n        \"UY_pL5uDK_sA<%tMetJMtHtJ-LeF?-<D9C-0rF?-gK$t.AciN'P`c6<HecA#/\"\n        \"JtrRwcbJ2RN[v$eqGAOq8nTM3%m8NeTZ1O.6qm&+gb_/\"\n        \"kEkEI[j_csjvg`M?uw0^FlaBoV&-d*D/g_o\"\n        \"mcw+M3&cZ-SrwZpVPYm'$q6AOu-KDO=8a`tc2lEIcH(n#g>S%#*2ZX$H:)F.Vj8F.%0FF.\"\n        \"cgCaMC(9JLXt35&Eq:#v@9F&#ISx>-)Vx>-kT^C-<I_%/oqRK(lg4RaZbi9;[<S_/8)U_/\"\n        \"Cv4Yuuv4W@W4d##t`W'#x?bA#ktUZ#ubhXM_SBX#qpXH(hA'F.kR?F.MSGF.v7tCN/\"\n        \"Jdt7n:lw9RW&F.aaFPSp3hl^C_lA#F#:W.a8wZ$R5q8/\"\n        \".66_#E3,)M@08YMdrpX#$[Rv6Ge`Y#\"\n        \"f<hv%[e?_+fZQI2R*3e3#)>>#qvOu#B:a'MNH]YMlf^X#,gtKM[%A383d8W7kxHcVI57;[\"\n        \"LTev$>7q/#BJ_8.1?Fs$PlX,Mx'MHM?oX;NVAdt7Z9)H*1P^p7S&i50UbP]OVXJbM,__D.\"\n        \",EHk1cA/XUV&>_/bs:GsAn`/\"\n        \"2p[Xwp;i,;-H,l-H5@j-Hn`[fLuM45&1cps1ZCmq7qxi(t>?#&Y=7g%u;Q.N:*_'m&K?r#v/\"\n        \"9F&#8+Io#%1IYMPt+(N.UQuuuW9e)]sN8pKik@tu)f'/\"\n        \"uUHX:u32Mg-(35&w&_K,2+L-M&rHuu<Q5+#-b*9#:d8Q]8-s7Rg8w/\"\n        \"aIW#JECPic;:vFh5lME5$cM>/$h$1<.AmklS:[x'5h2h,$gOY/\"\n        \"#=lVxbQ5N`NxCc<%9M']t%5YY#-UT'AO`j'A\"\n        \"bkfi'a'J/Ldah?B`5?(k]4=1)c$s8.7gpj(<'PMk.w)8MLvO(.*$HtLfRB_M&B1lL)p/V/\"\n        \"uXC&I<=PDH%&,@Kq'g?Ks4&@KgC)v#V/OJ-YEe*.U>0sL+5c1#,Y(?#d9]nLAr.U7h@4U&\"\n        \"._j`<+3BE<X7o$vcO&&vlNgSM*8^7NM].mMM30/11JK>,G&:W-RhaSA.I/\"\n        \"g)*g`]P.x7#6()YN1GR35&,Dsl&D>9)=S%E)+.5xuQHhP&#(1uZ-jY%`=dhb?K8f'crKj-\"\n        \"wp+*,T.4N#N#\"\n        \"W,:w->eQ.MMxSfLw(Voe=QfT&NuDA+6L-)*g)M3k(s3^#(gDE-#3qs0F[D>#G6dv%5`:;$\"\n        \"XnCv$,LQ,MV:nTM8rPsM8-kxFfQw8&b.xfLdbhp7^16;[4lpoSk@C;IRT%_#G;%$$5jgS%\"\n        \"H/D6&-3I^#l1:9N)1N.N>S'^+E3Ta<s/W+NHM.$v5_%NMeCD9N).<iMM30/1V*xl/\"\n        \"cEqh1<K`M:[2ncE:LiiU$(+R/\"\n        \"T:d##31CG)oW_v-:Aq(a<_IJV92v1q.iq5'g:4gLU9.*N&(3$#\"\n        \"S#(PM@h%:Nx[d-NSFKt%1w/\"\n        \"a<aq^)Npu'kLp%LKMAn.:N-#6t7(^;W%e50p7I<oA,Gdx?0d&t5()S[q/TNpa<PLJH<en[5/\"\n        \"bOS(#^6`uP=DGA#XpH=.mjRe*QGJ^#fPlkMx9ILDb_lSf\"\n        \"Eb=`WpxQ)+HUl`1)9@W&X3^Q06T2c#D<]w^rc&cr9,*,MD*J:N]W%iLK$uZ-^mvJaSsX?-@#\"\n        \"Y?-F(Y?-gl2q/E+w8/.<2c#P]kO%&gf--Km0@0Rhj'&ns)&+V`+/(E%ji0Z&Z30H,ED#\"\n        \"@c*9#H?=_/\"\n        \"nwp#?TPO&#womt.Ev)&+52a2`[(@F<#1d68p5,F@PR+m#&1.#MIt0<.%gk@k4@Dd<2OA2'\"\n        \"Tdqr[HkcA#O?BQ/]kno%;kmD*<8LW-751@0P.I@0a3K'J%k6^#Ue$b'qDnA#\"\n        \"ZwX?-`FojL]&qk.iX`$#vm,D-/\"\n        \"HD].BEJX#qxX?-T(Y?-%vw:0TNCk1G,nF#]XFp^Ick@tT7,2_IqlA#r`Q9/fa>/\"\n        \"(UYKDN4rIiL2rDaMXMU<N$m8RNTe$b';W)>5H#Y?-Z(Y?-Ro2q/\"\n        \"&_9:%1ZB8%XwUH-b3S>-pu,D-[.uZ-k2K@06Zqm&(I(e&oc&crce.t-NBS88(rT#$HOFp^m_\"\n        \"FR-o'?L/7WB[#B;].M'kM;$Gc$Ok.a/f3J4G#v=P5+#Kjsd=#x//Lowg%%_;2T/Pnv?I\"\n        \"L=]nL?tr=G`vlx4nb9`$ag=-=K/q8//NI#B9hqY9u^/,Nni+<%_]>D<qG(u-O-27:SCeMh/\"\n        \"FpQ&jm49.#)>>#EbNt-%>`iMs:HuuWG#Q',`J,&Iwlxu0t.>-9qoG&eg`%OWTIv$,f_xn\"\n        \"UT1x$f_%t-bR,pM#PQuuhu+6/\"\n        \"i61t%Blvt-J,aJOulbI)x2j,M(Av*(xf[.qaIqE@O[)n8eC?X1wMPt-tx$FO0q'q%*Kj,\"\n        \"MLn[r']*UfLJN),)44Puu#DCN0DI)#&VKpdmq9Jt-UemeM\"\n        \"MDQuuS)2/9b;6X1`Tl)MRaB)9t)^<7Y%(,)3.Guu/\"\n        \"j`p7;O+KN?e'E#3rZMMF_Tt7:4j2M<e'E#n;(@-P^we-LbesAEd@=C_5*9B:e'E#\"\n        \"LwTt7pwAQC?e'E#Sf?X.Yt&-)>^x$&;W;*e\"\n        \"kwPe#c,)k#NIY##xYfuPq/O8RZOF:kvN4%$s/\"\n        \"Y:.WLRTE2L7L<D=]nL*s[oL4]P*$pF`0$tT:@-WlZe3Q6kNEi]F($WtuY9j)>>#[YPF#E1]^\"\n        \".'hlF#:o]O4/ped#eVik#nAO&#Hw6?,\"\n        \"/mRa*8ZbgL-O5HpHt>]2nKsR8:Pg;.=ZtR8oR)=.hkDH*`8ZR&+(1X6cjq$'v_%w#(^nc=\"\n        \"B9ve)eIG?,+5eTIFSH4(#VJ5'UAB>#*3WxtDr;5J7L[-HM*7*Nq5=1#6<Y0.@O.$Nc>WT/\"\n        \"aPq;.J?[eM;>hF*J_/bIM&tI3ci_2:;5sg(cfoY,9Kg5/\"\n        \")g<I)9_330M;Vs%vttq&'h8F%;)nN'#4jm.N;#F#ra94.WgX4MCT/\"\n        \"L:WE8)=JOes-0-fK:YWX,kQ&wF#G7n6%bK(k'rMLQ/\"\n        \"f-T1pJ+65/kgA<-O.4c%P%[8%4?FQ8(UA)=p&'S%;^:T/\"\n        \"4W^j9vTA)=QjL=%qlJF*QNpu-B6@BNOV02'?\\?f4,rbHWoV0oo%+Y]('Y(d;-ujbk%SMev.\"\n        \"3Z0r.aU`W-3x#ktL[,,MwRie$\"\n        \"s:_ktSMl%P:O/\"\n        \"Z$;4H_-<rSo:o^X&#s#H_-,D%k:i@4&>_IMW-Ladv.+;`N:kB4&>_IMW-,0dv.p]r#(L)ZP&\"\n        \"]C`8..fwm0Z+e0:b6&d<5tlL%;^:T/$J29.WY,]k)*9j(w1?<-.lP1%\"\n        \"XuCn8/\"\n        \"B&d<W>@>%[1`n8'3sc<%<gs-F2Gl9Ak&d<XI_P%XuCn8#B&d<XI_P%mD3I)Hm+q7?i_lgOM(\"\n        \"<-8Y=W%vOwR9_O`W-w4B3t8rx+MV@Tk$nok3tdpAs7uKIw@r.MN:N8o`=V/PW-\"\n        \"#qJw@+;`N:N8o`=E[NT.H.Wd2V*YH(p`ebMS'$X$T]EW/\"\n        \"Q?d)4cb.9.4fFfCD;IK1gHruPKo/\"\n        \"87Ii=]25l<bdE<9IEY@C4;a=]nLBSq?94j):2C@+;2@aXO<%YKLE9hqY959T:vIDWK<\"\n        \"T6d=9RT-(#&]s)#vM9:#:d8Q]RV0##w(4a3R<_=%nc/V0D@/BT$),##/1^*#&$IT/nPNu/\"\n        \"=A1,RG-M#vf5:/#:PR#MT,1Q]j6j1T(fe)M]a3]-*kpf(oC;Z7gI_vH-*@]O9tVF.5jbF.\"\n        \"@3mc)&$,<8p*/f.=?UJ#p8K$.9L:>9(:pY(D7n<-*Zpw%01t,<C`LmTqCC5/\"\n        \"_dc<Lj*N]F<-09gVbQuuZ0^d);es`<3a#+<h(w9gX<m=9L&]5'ai0-'SB&##X;b(jhUDFG>-\"\n        \"1SM2>-N^\"\n        \"-,6T%El9m0pA5)%%$64DV3Qx8Hj%T&sR5t&*bh_6l02J#U(3i-CHc[7$rJfLoucp'X:%)<8[\"\n        \"X&#k5)x7?W%iLE?qKM>JM9NSlGp8F3_B#;I%[#&6+v#'j?B*5JQM0WgF-ZIw339vKTJD\"\n        \"Up+<-T0G`%3HiR:hQOg$?FH(-S6-&8>:G(-T7bN.;e*x#YS19TWZD>9;]bA#.`E*<dg'@0(\"\n        \"4e;-0f/o%QYL4)@lRj'xNh`*Ql#T&o$vgL2pXl](pF5/vq(##H</>G`lIFuRXQ>#9]bQ0\"\n        \"vG-Y8.;ri'e?1-FF.&60xqM<-^%kB->))a%n5R&#E)m@.2_k(Wn4>m0oi>'<9LMDFV)PW-\"\n        \"aS[dFf<*mLQHko;bH`DF]GCE,OIHuu#Pej'3GW+&sFW-?HL3X-11KeX$wAJ1Ui]('XJT=u\"\n        \"%Ed3X4vn?BmEJYGU,i3XjvO.X+e%r'LNik;;@C58ucV^QA,-E9Pd*#Hx+-<-1+uw(ol*,\"\n        \"Mnc>d$?4Xp9UUn>'8tV<#T+4GMR4Tk$^579^k%k7'0M'crv>X_/F9f;-HFJ$&KYeT%G2Puu\"\n        \"lT=.#bbU7#-Mc##Cw7Z#rn9SI%rWRuLN4%$)`OF3Icu8JplFc#h;q0#+KLgMk+7PJVum:\"\n        \"2GO4;2gSo1<q$[iL&WW(v-kXm#cS1xLWi)?#cx2b$pU?g#LwAi#0n'hLv[J<$D?s7R?[i[k\"\n        \"bI9w-qVkL)'aE.3xfo876`j>5I#q&,^iYp8F_ni9N%'Q0NWdM;DKOrHXbcxOH1<(8`up)8#\"\n        \"g@U93P^^+l&Yb5FJgd4FBA&,aBF'S3Fri96Pa9M7$=SRJKO<-T1Vi%&Fac;Hcr20?Ui$#\"\n        \"%0]>#?mBP)VX`*%hND,MkEMm$jMwu5]#8_#Cg$h(*62c#,upe=(orq2VM###Y7+,Msv@n$b_\"\n        \"c%OU<m&#3SYY#TmwV%SZ%?$Of?-Nce'&vNKeYMESk_$r`*S:s$MS.Wag-+^r)q'-0cP=\"\n        \"Ett&#XFd##uer6(K-@,MB7QwLDp)?#0Iw#OQ3J-)3YMq2S<dF#Ot.(SO9@,M5*)]$^60,\"\n        \"2dP0t%5w/a<.BTE<:R;)NXKAZ$@?4<-*pXs%_&C.;_9kWq<MxWq;D,LP<PflA;OJm/xbR_#\"\n        \"CZB8%2K-.%@-f;-:v`^%YxWfLV-AD*##vY#1oR_#EcI/\"\n        \"(M=h%%#Bs$97[Vq2ws1F@DZMD*#m_m/*q'm&PlMm/YSuo$P-mxu:?oY-xRVq2XP?m/\"\n        \"-:'##7WB[#+$a;**#4^#/c@O:9odYQ\"\n        \"j=OZ%NK9Y-*Diq25ch;-PHkv%'`_@,,iB.==kkxu9*Q1%k4gi_kAqfM'2ni:xYVq2sLDs-?\"\n        \"N9DN;EZJ(GN^a<;,HF<Tv?#>(dcof=3+<-lVl-&C-L1;EH.Q0aFx+MF08YMj'3g?Jik]u\"\n        \"06K@64F^T%R2PuuLKW)#DQY##Kqn%#Ie:'+<'5v,<*cJ(^HZ3#ED71AvN4%$IsaI3UbckKX;\"\n        \"u`4Kg6pI1C`)Fn)HK1biuS.m8r,)Ao:p.,dEb4d2u-$ARM`+/F)D+5(C@/IAQ;#wbWVN\"\n        \"aSrtQ^&6v$w]'tQ^=(rDnNv)4eO>T/\"\n        \"I5^+4VD)i)+$5g1'F-:&M8+m1bFP_+-?9I$f(eY4ait?/\"\n        \"+AmF+BNR.MNI?uu:d$o#)ZfvLwj)?#i#mt&,s73;]jKP#q####2NBw#G)eH)NQxnL\"\n        \"UMCE<7E3e30hCX%t,Wv[K?KP#]JSn0=H-)*BbN`<:0CG))dJQ0EHIfh]%b8TM9BP##\"\n        \"pp98Es9^#s3oX1<R8X1EvPX1?9Fs-vR&gMxOQ>#pPpnL)x`@,^##oL5+3e3PdI+ic.HM0l*\"\n        \"tEP\"\n        \"rv70#$8x+%hcZO0=*-C@Hw^8/\"\n        \"n,x%#PkcPNEcsJ%qlvk0Ic#;@*3YY#;PFI#bu&$.wgfD9lW<jh2QFM0oq3R3hB(3$PEP##\"\n        \"0a^a2joG1M;8RxM<@f>-%*,Z-HcGe-3GMX-w69.=S>=c'\"\n        \"F;q<-C'sN*FtbjDpm@g#LtbjD>mT)9.:Bv-V,(x&P?&##Kce@.U^p5#Q%%<-^&Q]'#`i<-^@\"\n        \"Ru%niZ6/jAs`#@1O$MZ^L2#,Y(?#?j6;:ft0'#C.rr$Ux$lLt,e8Au:&F.soKF.j>:K:\"\n        \"7/V`b:/?W-IwfQhCn$q77.tDuoc,q8L.QV[^V_=-TCQd$<TIpg<^0d8/\"\n        \"mN#J<>HX$4eLYAB>)^:av^>$lB%d<x^<6'(pF5/\"\n        \"G0Z9r)E1,2['kG*Z'%gL4mZ'vJ=ow:*Qmv$Pjn/#.HarT\"\n        \"6#h8%_T_n#6Q2uLQ8tm$s64G`E^_c)@STm(>/6X$)mhV$uqC/:*27&'s/Ya2eNAX-/\"\n        \"#CFE.V?T.(<E.3::b=.$`,MguFsMHNUn%]#+:.[K=`Zed,^7Ao3In*Z;K0%N)1LGn+vKG<+\"\n        \"7G;\"\n        \"Mp,K`5R/2'jZX,MB$-g$H^pO(%H;ku1%%C:Xx'##A$=xu<m)'MYrW$#?_;V7J&o9*/Z/\"\n        \"U'=NVw(M;x+Mx3GDurN,N2)wrI3TX)P(HH/i)]H7g)%:Gf%lnn8%d;K=-ePGN@X@vV#$+Wm-\"\n        \"&f&Y+F8OE#qEkOk18Dr#L^TQVIPHL-)JHL-Qp`e.=>uu#a9]G6MdoW7Vb*p%VTl3'xu-W$\"\n        \"BhaP&87.<$3Sl##Tuqc$;AWRul_9D5mU=Z-26;hLr<M8.*bM1)@q3L#QVE%$bL]Rh,P6rh\"\n        \"^,r4@$r/cGuxcCr$IaCa:tPgDDYN4f4J(.$QKTluf->>#@>&cr<,@Vdxgel/\"\n        \"m8ds.?lR3'3P#G*R<(#$4<oO9mTj;7<wu#+.22<-`.#_$`&8j%PBEd*+%lU%5%;#np'7EX*\"\n        \"bsMWuwMK<\"\n        \"al?WQ=(%`O/\"\n        \"&4VLWLau7.sp#Y8YZB6-rM?76G`>7O,hE>u%FH2GJFX?0)FmD)k=d(_ONT%8f(Z#\"\n        \"huao7CWkL:$#<P(ccEPAG?Bx9AS?g#Zkjp#Cxxa#PS)BM@#8TAP`%/LCspl&Z3f),\"\n        \"D$^Q&O*^_#0QSuujxWS7^T?b#0c:Z#(*a'%lZ)227d<m$W00QC#@Ha$)BfY,77Cf=$q)pqn(\"\n        \"487Mt7G`$r*&4g4Z1TOGe#-='A>GE9X3(*0`v%wV55&XVXg3n]5Hp&xB(4J>#G4cawiL\"\n        \"k(tY-rkh8.Kx/K2mDtt=I7[YA#=L:J?[rGu25Cfue,K4#ak<m/oq2G`m,k4S/\"\n        \"=2X-?]rw'i(sw'(/\"\n        \"'crKZCVdoIG$K]4m;-uqfZ0GP(k^>FwS%`W258+_sJ2-U=IHtTM>,VqAi#@CJb-\"\n        \"ApiwBpZOOMdC`pN=RhSMR&'VMovS%#,8pKMV:e3#g-am'4[%#GQ5?P(0N@W&DhAQ&2`lY#v]\"\n        \"u##LeRm(W94mL&YkJM@OZe4L[M1)#BUM':s`L2AlJP'kkRP@*.BcuJf5X7S#kW-w=(s-\"\n        \"$g.Pf62fe3;5>##M_`8$-/b9#Y3=&#[KJW7d%.j:vvn=-HjjE*xe9B#0),##_SF$/\"\n        \"VT6$cmR$.$p_k[$V@S_#M;gF4N,tI3nGrc)XeRs$i5ct$.(MT.>V&C4ws,50(Qk==souI=\"\n        \"Zf8+%\"\n        \"ErFQNNWq-$Z*?'$]f2p(M:$($(7001I%dR$,;e*Et8'quMuls-Ple+MDF_'#aJ5G`x:@cV$,\"\n        \">>#BY'DE]?8G`Da$)*x+DZ#8INp%uVl##@0i>7e,p>7E]h)4D$nO(h:rI3bh-KurUp%F\"\n        \"TcnDSmDa=P$l^=PA3^Z.r5YY#c6nm1>_;V73O.g:V$8@#/\"\n        \"x,68@'-k(WE&7pu2=1)F0q;%8gV;.Y9cp.]]d8/Q#:`?O(uE=r'Zu/\"\n        \"*J9jK6vw^h'2Fk'XW`19m(3k'Q7NQ(FeX`W$'sv$\"\n        \",2dT%DU@c8,C4AuJ%dPMj;/J.R'95/\"\n        \"r.o%u6'hwL_(^jM1IZuup=6m'.>$R'-R.0:<%ogU>s,f;6l*wpC=`G3c6j<q$=aIN=xqM<U%\"\n        \"=&Pd3,tq4L$iLT^9(MZRE$#?X2V7?`$L*.`(/:\"\n        \"r5g4&xKOs-ee-lLm3g+%g7OT%Ta;T#AT;5NxOs1Zwp&j<8s?;:H.oXBX(cu9b5Yr7m5>##0?\"\n        \"uu#<USC3Qe[%#QeuE*Ge.<$@78%#LTABF+$G5pXjeh2]_0tAki]+4+87<.D2o^fUFR1W\"\n        \"sf3FIho*`?rHlk#C$4G`8vR]u-NXR-:sUAMpr,/\"\n        \"CSm,H*JM+,M:(exOX8p'.f`6?MoF0E^#$TJ=3uia-@%-ed5^1X8q;V*#a:a%vAtZg#dAO&#\"\n        \"Whl**G*$r%n?Kb,dW1k'6%kp%a7f[H\"\n        \"m^KN(TOWa2m3Zp0t?i8.pH6v]R[E]-xYOjLZ6A#.8bCE48p6>>jCXNM)VuHPLx&3UWGxI#$\"\n        \"Wbc;VlT$&/i71:1)kd#q[r[1#8.c>:kvPN8bv;LOSDi:ZIHP.lC&&X1qY,+q9MS.oXD/:\"\n        \"9Le`*^Z5V%Nrrq%FNkv#fc*87)`;L#ii)c<R*pG3*O^@>G,v8/\"\n        \"NBXq..IZGMwrue)U<Wg=6gVQLlsh5M:^wBAShq?901&Abq$mLFF[_A4W/\"\n        \"rRehr$c>aR9kCP_ExR/.0l6wCse*@tWs-\"\n        \"(j&kL+Q]_#E0`$#DeDV7Fm7[%:'lf(.A.s$bC=gLtZ>8p%d<I3>_e[-G^Tv-*Vg/)/\"\n        \"aS+4IsaI3ITr[%1W5,21ecQ&`^r[:hJbPsr*=`?;8kc)0saV-NAQ*,4U1cVft`>7k^(&\"\n        \"vAtP3`\"\n        \"V'/s$JUOfLZp/\"\n        \";6-kB,)vj$#,R;MwK5+*s'oF>?,Wt^Q&0H,##RcVCnDsH[H7kUa4)u[s$708o83lK)l5[Zo$\"\n        \"B?<8.9]pl1Zq`i%B3Y($5Mf#%3@<`58-vp%R%5r9n,J+>,Dco;5a`S<\"\n        \"o;Mw.C*t43I0wt91ITF625]C-wsjM<CLu,#NHxnLR5n>#.uC$#Y9F&#D22U%Y3+2**vtA>\"\n        \"85V?>@]Ua*);6Q8`P2<%x@XI)R]C#:E?s20k@&>AsacD^N;+lC0S[I3((4O#ZI1m(RCY]>\"\n        \"wNc804:AC6Ee.X-Ht_iO8AUlSLU_Z$@gqj$%>qKGw6u)3SBmF#d^o=uX%^*#7=\"\n        \"7wuecimL9kN$#=_;V7Hm@X):J5;'>HD[(#DrJ:V]uD4cBuO:#N_s-wpB:%N5PY-6TvN;0C<\"\n        \"A=>0_be\"\n        \"4e2P`jJL;OGs.8IOq#>AXris%)%TxXe:md.5>uu#_%](7U>cX7j2%H)G'^2'=rB8[*DlY#F*\"\n        \"0Q&>C@<$+I@K:4U)B4^XT=c/>O223E&N2kY?<.__j=.2aKR/+'A+4+3xb4sh[[#sS23<\"\n        \"3:rr^ofm]Bhr&j>BFm(MN'A8%vnu0#Nhv_s$),##GEJX#jbu##eQSW7AtC3(AVV$#x,QG,F:\"\n        \"=dtLP:]&b4)b*=cHW-+',<8Qf<4s&4TE#H.)A6w(b;&^giO1=Qfj15i9?5)ZUG+?l9<?\"\n        \"RgvS/\"\n        \",&6w&V,*t.S6xT.&>2$-P+*t.-jF(54wJX-c7Lt6?Q%k;Ih*T.))g=uDs^:B_Z=9%Ft1w%=\"\n        \"hd?IRUG1</>/0<Tdn=u$M8GMUZj-$V%=&vaGVG)+LWr&K1Qj)H0^Q&Bb'o#,IV>#\"\n        \"0r6s$#oQsA97pY%=3+<-#c^%%b/U/\"\n        \"[;GF4DU%kKe?vn=u[U%99dm]wu8KP)M_RW$#+$=?#6`/\"\n        \"6&G[xv$m?)?5k$vM(:=Hg)w$d,*.P`B#hDY_;(4>nCDPAtUA4J2QAH6L-eDF_0A22U%\"\n        \"5GY>#s'1HM3q%s$2q^8I4MK/)@<8_%p/v:gXw5eb2.5$pG0p.U?^TMLUH=p7(XkA#q8V=/\"\n        \";3&W75)dMTNi&N2G4k`@VNH)4n6r7[``/i<])>4M2:OBiqf-W]c/$j'6+.s$]Nh(NB[O%t\"\n        \"2oP<%G8eK%ALws$>$.gLK8:P%Cj%>YB9r*M:WZ>#K6H9K(c0^#3ra^-[<ek=Y0G`M(\"\n        \"niXM5qIiLM@'_MdcZN^;ou>#LVums6j,]MCTYr$7GjQ#O-2J_Gqe.:5wkf(J5`1p8clERmP&\"\n        \"F7\"\n        \")+x+2g.>P]+EAJ:5=Z%-*G;$#mOdV'o-2d*I.bP&r1+87NO/\"\n        \"R<NtqZ[i3l5T9>L?99,li<-Pxs8UvD:B^;6)B)l?]`#DYo;8TV`795im#c<,C&a`Na#PWG/\"\n        \"$Lqh@>k$0iuN.]H>$ktpL\"\n        \"3l@iLHt)M-pl2m.B]Hg#Ka'd1/\"\n        \"@dU79Zl>>7re,VxD$hcZGp0#.WqJ:-OM=.OVoF#`hO3;h@Pi9$wMi9H.1U;K,KkL;rU%MPw&\"\n        \"%#CiE0(7TF;$GwNp%@FAZ$FbJm/M$j8.oNv)4[o&0%\"\n        \"U,tI39;7j45;otBNTNZ%S&>Ib,%`SU?hOl(_.0p7u`'^#4_Vq9U<xS%hZIs%s:_g-C-OL#l;\"\n        \".4ixR]LNTd7'0'),##Cfd,$g%IZ:CjtA>?44-V0IO3V#=emL6N%.MJEK_-9OxU;c+g^#\"\n        \"?x###Sj+R<%q?D*<;t5(5m_h$.PP##-m9s$XK1=%us/\"\n        \"<-NN7g9,<Zv$P-ok9RRA2`JAC,@0K=&R`,V`5W?G*9(pVU'0&of(fl@O(t0@M9(8hiUH<oU&\"\n        \"QDw%+AVTm(ajP7&(Z(1$)fK<h\"\n        \"3@-*NnfGU:%PI%%FR6=0$$nO([`=,@.(&RE6e[lLxlNc<xx@P>u`C4F`Wu)MW0gfLniO`<\"\n        \"uSj@t[GDa+rfdqRlndh2-_#xQQA(i<wHSb-Hhx9DRhH)#Q5-$vUEvr#K3=&#Nlw3'922$#\"\n        \"^n[P)K1@s$I`is-k]`O:_pcG*9%39.*OT:/\"\n        \"r)2U%e[ve:`4Gv.(L%k;?4Lo$%h<KCdI7cEUNT$JHXM?5tq2C?[b7+>Rst_5a&x>H-&hO.?\"\n        \"e>Pf%C5g*tZel/)-Q>>UJr7[j+gF*`4''+\"\n        \"e%%t-4m(?#u+NF&*w(BN>?0%'`]d8/\"\n        \"S*Ig)'_5U%bbB1Ea%C?&:Fn(5kwip7*:acDa#Wp7_5OZ%YMfF#2$4@JA7uw.VE'\"\n        \"a5KbGV5OBIa=5>R20L4HP/ZiPxkawU3V[4>A%7tbf(=M7W$\"\n        \"d1rJ:%Ql)4=Gw;%Pfsx$i5oO(lT=U%*sse),MeC#Pd$Z#PXlQ3S?+am#3#D5lx0:b'+\"\n        \"uK3s7fbu9pOf_[Z0R/d33.bRxts#@h1$#:qVV7?'?_'ALnW$*,ZT%$%%Z#aF8<7gYF`(/\"\n        \"P1+c\"\n        \"F`DD3:#G:.qLg_4P*&88q<#F#`].Fu1gg(Wm8Dr#I,,r^Y3E$#ak'hLN'7tLUrZ##Gtr>#=\"\n        \"FwS%ojG@.%Weh2*x(E#[q+j'-t#F#W+6rl$ed19$8O7[&5>##YPr0$pQ459H,$D,UFD)+\"\n        \"=OgQ&T`1i:F.*p%JNE8cobHI$,)aT/\"\n        \"^W@XT#wtsDx&a,<,*FFe6RkQBv16YSDn&[ShP&0OV(779C;kYPmWbgPu,dReGl7t(39_Y#\"\n        \"Lc&crh+$Aku8>G24gw8'lYU#$VNU`-bN&p(0cWj$\"\n        \"`hH9iFqsD<X12H*&S'f)14MP%.aS+4Jq<iM.Ck%$)H6V'U53Y/l9Qi/\"\n        \"_(ixPQZCMDv6GNDbc;UBaS74eQuSu8*(4SCv?[?#uiR-)WCj&-@DRT8g*(2DBpr0E$%*4)\"\n        \"NQY5&c5v[>/cQ'?\"\n        \"R7/\"\n        \"KDS>uu#CR0kXm$p7n]L18.rtpu,;3Dk'1M6#-YLPA%CH*c<RqP7A&PZZ$]p*P(H(mKP(3a;%\"\n        \"NQm;.L.Hk2wnXI)g)TG;lMc/C*f7IE)3q?&W>O41<YLmEmVk_?LdWO;(Dhm:83&EN\"\n        \"Sg`H#X$VtBfh:F=8j-^7H^dC7^sFr7?EeN;b`5^6.<4mLMC)20B-MigW[)20BS9Q(r#mf(\"\n        \"DD-9%%F3Q/s$XB#%p?.%X2^7p.^9D5Wh`8.Mr_)43DQf*h73K:pCZ;%72.T%TaE.3#Av>#\"\n        \"RIO6Dqn#X.W_lp@TSghC5^RPDrt>t.A#$D.><TK:V)sl*'bw],hRVQ9P,w.<M^2P*,3'?-=\"\n        \"rwC-(jqp$-hbP&6+.s$d6+kr+Jc(jjT*7%uWCG)JBY/(7`'8[3r-W$t5#1'pZ)22^XT=c\"\n        \"$F[q$@GZt-nlGh:S;+QB$v@QYAYV6/P.KU#m(DR:/\"\n        \"+Uc+Iu4`s]3]f1Hrp2),Pj?>]DW]+$Aqx,6W0W-v9C1Lq-MoC/\"\n        \">*(*+xfU%O,kZ2`9_X8ov;M<#b$(7T>kZ%rY7-+^NFKC-UW*4\"\n        \"vT2:hYp)t.JUH[5#]2d4PI1a>%K1QC[=,.<1qJG5W&].#3Q_20tco=u9a]._>>H9gkiVG)\"\n        \"bxql&Gu5N)5b@g:mbVs%#%Vf:J5N;7-*g4&^Y?<.c_Rd+jxOl].f*i<I9tl`c?ur#w6xNi\"\n        \"?J@FM-4DhLH:,Xn(V$s$DmL3$7(r;$9f($#HVLm(j:SC#`e;v#$&#v,u&0BT<%n=\"\n        \"ukGp1T3Tj=u%T_p.4MY##[X/\"\n        \"eZCk%##CB$,M2ToYM>M-'vbPMmLpQg%#J.'L(VTIH)2N7W$7R?Q8\"\n        \"Ol]G3mf)<7xak[$:uc81eV]+4<1sg))wZ)4k6Dd*d8(t-k-'h*q:<R/pJE+@5+@>#Zeo/\"\n        \"19FmU7>aVG#E*kt$9Y`6&s[e[-:jV;.p.?m8b^IJ3oo%Q*NJs;?Kjl`%7_=Rqud<K=+@u5<\"\n        \"A^Xq'@K-'v(],lLB_2'#i]CR/\"\n        \"^>Dg(Jo0#&_t9#?:7iZ#K%2k)$Ij>7AWQJ(JHm;.R*S<_Y(_HdG0..Mm%tvN_;$$$Y:&+<]?\"\n        \"x#0&&xu%]m,f4p33Z6d0/A6XHP;/$81;$Mq,JEMCF(=\"\n        \"-VImg/[cg;LChhL%N-'vG]^sLo#W'#nG5u-#xnp.Tq[)*IEGj'8]/\"\n        \"Q&X:;Z@ns%7pbti^%x@tNM7Wlk-I=E&T)iO3;mn]N-/rQH.jJL;O2E@'#A&sx+D;iiK@$(/\"\n        \"*o9io.E71#?Nt=HV\"\n        \"I3,n&P*fW$G-wD*8r.rTjbM:8ucu8/\"\n        \"m_Y)4BE8C%(41Hkcm5=(@tV*8]l;:s#B%u%sCUF4+X,9%-exa5_tK4;?Nk+5=NA^dFKMb=\"\n        \"Ta[(>chMd4E39Z7=SMu7N6dM:`-Dv$.14M)jnlr-\"\n        \"A%ux>Jh+HVCk86&L$fW$VUQeQ'R-@5u8TIQ@.uV.Ogwc<9,$a4_?@W%t;lmLpvoO;OZ[G<\"\n        \"jU>^#,e6kOaUi(jd*el/Y6+E,iGrQ*;cW]F+^=:[l1gF*Iwb8&X[=_-Df*F3GL,?.KcGc;\"\n        \"uWb:.d`*87Rp^rJgarr-AP9Q([bNe+A4Fk'd-SW$%qI##9.&v$MKW6p.%cL2K3+f*sq430-\"\n        \"rP,*'aE.3Zpq5WP,FG4IJamA*92O)<L%3BB75>>6kXQ#P5`g:RV*I+X/H^O6*dh#R(6I+\"\n        \"<0:bIXX6+7u_vb>f5xHE'MQMEY;f4]?H4s$9(k+Mmm]wu/\"\n        \"?Y`MkOgVMW+:kL6&Ua#Y5S>-xS.U.@v=Y#_d%j17%###D6Xxu17rZ#%N$iL7:CW7lTT'-?\"\n        \"Xjp%0u(/:BrGn(8I`?@0mF5p\"\n        \"_PZh(uF)W-;8eG,wtT@%Kt@X-rkh8.,2D?[c#Z[$G$)/\"\n        \"IQ2),vV_[YAYX+8=+<u_?TR2VTs4J^$lU$gu^IqW$bX3TAEJxY,/Fb2)q,>G2$1j/\"\n        \":@;4X&*Ixu,<@aP&9@no%/Y1v#X9OM*\"\n        \",hj>7,&+22sS(9(?IVa2)TW6NDZ@ijh$Im%j9v^#(rjl=<kOi=`g>>ZTx=j*gS0g-XP+1P]\"\n        \"tbf(U8b(j-R#,+>At5(La.@%S5<&+Iees$rvwY#%qO=c,Zk02Q%cL2gax9.Z0fX-QYld$\"\n        \">tE:.We%q$jY2K(-Cxc)Up@9APeBWL*r6A%(7'_#`N]alwaHoDvPrh<NKnO]\"\n        \"BnmX0REAW7Pic3)>se.MbMvu#?+6W%T3<kFjc75D`B3mL&S[l*x,&HMs-Y823N@q&v:,@K=\"\n        \"w%0@-Amf=\"\n        \"/@VBlg@/=BWSoX9Pu'99I`@H?-b0^#3[X,1/@dU7,.;/\"\n        \":4cIgU-i)<-PcR:&nH*qiR@5G#tF@A<%]i8.?s(3#Hw0T%QJ(v#lMc##MgBT%vm[>#3<F;f]\"\n        \"leh2iRG)4HotM(&3FP;=C7G`\"\n        \"<p:m`evS%#ZHI#M47'%#Y&>X7V84X&859>&0oL?#0:WP&uOb&#*Cmq78-v]mfrVRun/\"\n        \"6r@l8,H3N#G:.fiu29ghZp..K8l=BngSGdPwla8Sf+>^hiV-ZI0^7'W3N*v]@w7MiD>\"\n        \"7NNeLC\"\n        \"5Ykr/\"\n        \"9,$,2;'a.32G+n0x9<d*Hi1k+[mo%#Ai=qCOh#@5cC*4h0YP4(u=S_>J.pG37XqQ_s<i8.0)\"\n        \"Qa4<>Uv-f48i$lsHd)1aQA4TodG*/Z9W-faIh,qNI1C_PSj0cx6w&d:qc$TUHZ,\"\n        \"AhQH3/\"\n        \"Thh1S$J;1h$3;7pS,f*1^4F+ek)x&[tG?>Vk,D'h>EM+x4fL(moZPJ(8#K+x7fL(\"\n        \"EaiMOFC08%pl6G`DN(,)iUr/:2MuY#-Sl##pt3>7>_e[-(1,.)&/ad%KjTv-4SY>#7bb6@\"\n        \"R;aq7s2B*+#`Kq#;W=Q#cBWq8fYYF`o)D=/\"\n        \"MEJX#u(%<?C^Vh)RRf@#O%-v&k-MD+ni:L3Ya(=%,=)dMqd1dMq1%E347&Ksm:3D#@2d@\"\n        \"JYHXT7NB^2)<$BK:,vd4&bonO;]C-n(AFjw&\"\n        \"a&x>H62x[-hX1M<7CmY/-sbC5;Wv%+Ge8T.-Il:Z1h*R/\"\n        \"B01>56RJ]b-0UYZ,oO&#C%a..0OFgLs^bQ^%5YY#4>t[tVXh=uY15;-Gu5N)7)@I%BHl/\"\n        \"()'),#U4s6&@#Ls$:]EQ7q2,X-\"\n        \"^Ie,cq)c,%nPxR1,DrU%%1*(RK4XB@t5Vh<SVR54(IUx$#@A#=&T9j_;uxL#xV.#%FRN`<\"\n        \"r07,;9.^vedgos$J6M>,5x=m#.G?WfX9Js$AG)(=%[=ZeXLI>'L?3@5p49%txd^S%s>nSC\"\n        \"bG%dRWc,,4?vn=uw(oI=d>U'#sSS=#>q*]#r/\"\n        \"4&#oaN[,R0=9%3mF?#D1Fx>)Ht?#VqN%b5a.0:U,3a*$6wj1]>Uv-UM[L(=p#k<k++c*\"\n        \"pEma3UH')?d6D-3u:D+i/MSk;&e5`uIgEF.\"\n        \"$.1#,&bZ9/UI6N)l(]@>V;ARN5[rTN*Oa8.9,B+4$:EN&0*EblSe@f$dVbDNMvEC=U2sI/\"\n        \"mO=9/1e2x5av&*>qETs249]_u8]HUVh*S1MPslNFD<h?TE_l'vAeoX#Q`Sn$UE>>>5l[,V\"\n        \"/EPY>6Tx4p2fAK1_kY)4=VoF#x0X>;Cn]+G,t$&OxgPd$7#mj#wkWr$Vgc>>*+/s8`Wo/\"\n        \"1SBmF#f3iX#-Dvkum/5##-5YY#4M9:#S'+&#H)QX$K4UY&<@W5&/s'<$+)d9%&XT=cYZ)22\"\n        \"%kINi`ZBd%q8,*IJCi?jClCtBZJ,S4xqxC#2D*=kxavR:$S+G#Eghn07`i'#Z`m$v+C7[#?@\"\n        \"%%#3>N)#D<n`4HF+50xe*'#&9C^#kaH<,^.0#,=atf*4Lsm8G?)s@p<=p8`-Q,*0O76%\"\n        \"1?8b*RgEa*Pk;W6#8xJ(u>[)*M<Ou-kk6X-<R@D?p&Pt$A9W`+>M*=(8$LA7*^ci*t5W#\"\n        \"5AC1x.bG@89.aM6&`vPn&>k%)?;dlj'9uus-`ofq9YCrD+cCEdtS(Y]%G6Eg=(p=8pf[F%$\"\n        \"U#4w5a7b:ToTxJ3NnvC+p:l=ufe.,2<o;0*aLq/\"\n        \"#C'$30le''#^->>#uwSONU50-#)J(v#onBB#pPq_'3*5T%(RvU75^9LPr55@0j$J@0Nk%##)\"\n        \"9@W&Ls1k'/3mF#8tvU7.l,g)_Q$:&\"\n        \"VCAPJ44G&#'Zve%?>Z]M3l0E#?'39&x5GZG5UF&#ef)9.cf''#R4koLeUUlSsMF5/\"\n        \"sSf-QO?Q.qKk_EnhfBQ(S#uZ-<D1@02'B@0@H&,Nqa<r7Xm8P<cXuVJZ6G&##p&Z$_(\"\n        \"drna2d;7\"\n        \"IiBQ(bw&k75f[n&O,Guu$2^*#IH0:#rb-d$eD5cHd9Mp7jR4=.t*]n9[*4/\"\n        \":fK.h2fY],=lv'9Ap+e(Wq)E(8CVwU.gQ?5;Bw?Q18%#v8P:V'6exH<.XL2Y&f'x5&Rl&\"\n        \"KYxn?q$B]C5/\"\n        \")Wg8.*51>G3o.<-s*<3&1=em'4[%#GLAqW.N.>>#KpX?-QjmT/\"\n        \"l)-3':U_g(vDFp7UIWfiXrcrn-+S>6o0/\"\n        \"9.x4im&$?I&#@D$<-K;C[->qaX1#f&.?xL:@,H,iT.cEJX#A#Y?-V(Y?-\"\n        \"m8:w-+nBvHOB2N9GHtxuWLs`#MaR%#[Y8`7cr@B#8Whm&`tC#?R?PY>K*jl2gD[1(\"\n        \"Dopnf918Y$?xn.2M54SA_$2lpxe1`+xn.h_IWj/\"\n        \"M64060Fp-h(I'T6&Gw6h(RmqK(ImwJ3Sh*u@\"\n        \"t`9t@2blD3oq@0h)/h;c)M/o0J3=1)Z0fX-XItG2,,6J*<>Uv-/aS+4@?**4`1A/\"\n        \"(Xm)f)1W$E3vx3&+$:9E=%TH=1qUC*'0snr/joOR*@Ax&4;GeH+173t6s/\"\n        \"h49Avj(5bV2-2*)NoS\"\n        \"*BnU&;LuK5R8vg102)(,#F>0;Z/\"\n        \"Zw-aG:;$_w2I$fnce#K[u##n2h'#q8YX7I_252[1j6&0c4:&`-NQ/\"\n        \"Cl3p%:+.<$V`KgLATj>7e?f#AK4&$+pL%9.Y@.@#)`Zu-l4B9rd3:g%1@_F=\"\n        \"]n%M33OZ4236`=%3B,<8_h/\"\n        \"g1[-xd#]w&q#Axxa#:B1w*]S-X-m)S7A:dxS8T-aKcT,>>#xu4uuLi''YFE/\"\n        \"2'XMP9iird@b-kR5'RLE/MCTK_&RTbe$C)XY,`Z:&PB-Se$^vbe$B?HG)\"\n        \"jfooSnxOPTm7_MU('CX(r+[_&)p(MTteZJV[xPe$9C5]XwNOAYp_Qe$gO7c`02bi_9TEJ`\"\n        \"NacofQ&`lgW+P`kD;CSoRr$5p(`bxu#YkA#/@DX-aSU_&/dU_&_`T-Q*i9HMM>FJMHDOJM\"\n        \"Ni0KMK%LKMKx90MCiji0FooA#[:)=-PGg;-n`xb-aVm?Kb_.NMH$KNM%*TNMj/\"\n        \"^NME6gNMKZGOM@aPOMG5;PMJGVPMEM`PM(SiPMMYrPMN`%QMEr@QM@xIQM4F+RMlL4RM%\"\n        \"RhSMW&RTM\"\n        \"W4eTMuD*UMqh5WMn$QWMo*ZWMj0dWMd7mWMe=vWM/C)XM/\"\n        \"h`XMgniXMm<JYM7BSYMLlh[MMe3^MPwN^MK'X^M.Du5vmEc>#Y<)=-`<)=-_*)t-Pg*$M(>&\"\n        \"bM/a^9v;hG<-1<9O0q.)0v\"\n        \"QCP##lQd&M@RE0vZCg;-(N`t-PHqhLhVv`Mdc2aMp`vDMQ@H>#)lkA#)Gg;-wJU[-4%W_&p$\"\n        \"X_&q'X_&.bX_&/eX_&K?be$v[SX(58oA#W<)=-d0Yc-8-K_&WLjl&]+Yv$e>^e$`1sRn\"\n        \"Y=FcMW*GDO]+Yv$GEkQa1Lr:Qc2N;R,w-L,loZ_&h>ce$Gj>-m?vCfUsO-/\"\n        \"Vw*mv$uAEo[8k<,WQYPe$_hf=c/\"\n        \")FM_obYv$mxI_A:1]_&Nn]_&K@ee$8Mx-6P3jw9lp^_&wj]e$%i)^c\"\n        \",McY#/\"\n        \"FZ;%;LlA#`8kf-pONX(GVV_&HYV_&H5_e$K>_e$o#m?KIvA0ME%KJ1]d*j1]Vq]548oA#+\"\n        \"xqd-&sr-6$6IR*i@`e$DK$44EN$44?I3@0@L3@0Gb3@0D4;F.'(ae$Lq3@0Mt3@0\"\n        \"B`BL,?2JR*.=ae$k7t-6l:t-6PARX(UF.X_r55@0]fRX(klZ_&nuZ_&iAce$c[Wk=d_Wk=.$\"\n        \"EL,)FLR*f@`q;gC`q;6nLR*7qLR*@C]_&Mk]_&J=ee$VOCXCce*MTUm%Jh`+9GjcF5Dk\"\n        \"sJhiqu/;R*XKjw9R&6`aw1/\"\n        \"Dt=?a<%?vR.hu+`+V&.Re$vnwUmnX),WoWWVns1D_&b-fe$pJVX(jgT_&uxn+MWw@8%\"\n        \"LqhP0M-mA#SlG<-plG<-qlG<-.mG<-/mG<-KHg;-/puHM/XEig\"\n        \"XA%2h:JoA#`Zwm/\"\n        \"Y<<-vkUl##RHg;-TC]'.-f)'MV'<$#0<Y0.eFbGM0-VHMU]3uLC(<$#k=Y0.Q+lWMfnE4#\"\n        \"Cl:$#4/A>-tHg;-uHg;-vHg;-xHg;-'PD&.fNwYM*a+ZM7g4ZM2m=ZM\"\n        \"3sFZM],d2vE'kB-7%&'.?#F$Ma,6`Mt2?`Mdc2aMxvMaMn%WaMi+aaM26aEMQ@H>#;LlA#)\"\n        \"Gg;-gbN^-w3GR*'PIk=.+_HM9<`-Mi92,)C,-g)D5H,*:#)d*l6>#-N-I21rDnA#d_`=-\"\n        \"RGg;-SGg;-88RA-_lG<-`lG<-G+kB-6jq@-r_`=-glG<-eGg;-x:)=-mGg;-oGg;-,``=-#\"\n        \"Hg;-*Hg;-A``=-6)`5/ULb&#p1nQM9@xQMNR=RM=XFRMJ_ORMPfXRMFkbRMd&RTM1/[TM\"\n        \"K4eTM^J3UMkP<UMSVEUMUcWUM1jaUMWojUMX-0VMn19VM]7BVM]>KVMEi5WMl<\"\n        \"vWMnH2XM1O;XMpTDXM9[MXMxaVXM7trXM&$&YM$<JYM2H]YM-NfYM@lh[MK'X^M?YK_M,0@\"\n        \"7v2Ik3.\"\n        \"YRd&M[2?`M6:H`M_DZ`MsPm`MGXv`Mo])aMei;aMfoDaMguMaM0&WaMo+aaMj1jaM#D/\"\n        \"bMoOAbM']SbMrb]bMtnobMutxbMv$,cM6pS+M^-E$#nU)..fFbGM0-VHMU]3uLUAE$#oxX6#\"\n        \"Po4wLQ5E$#^5UhLxaVXM6niXMusrXMv#&YMx/\"\n        \"8YM(ToYMrZxYM*a+ZM7g4ZM2m=ZM3sFZM2go#M$0E$#7%&'.?#F$MS-6`Mt2?`\"\n        \"Mdc2aMxvMaMn%WaMi+aaM26aEMQ@H>#;LlA#)Gg;-\"\n        \"gbN^-w3GR*'PIk=.+_HM9<`-Mi92,)C,-g)D5H,*:#)d*l6>#-N-I21rDnA#d_`=-RGg;-\"\n        \"SGg;-88RA-_lG<-`lG<-G+kB-6jq@-r_`=-glG<-eGg;-x:)=-mGg;-oGg;-,``=-#Hg;-\"\n        \"*Hg;-A``=-6)`5/ULb&#q1nQM9@xQMNR=RM=XFRMJ_ORMPfXRMFkbRMd&RTM1/\"\n        \"[TMK4eTM^J3UMkP<UMSVEUMUcWUM1jaUMWojUMX-0VMn19VM]7BVM]>KVMEi5WMl<\"\n        \"vWMnH2XM1O;XM\"\n        \"pTDXM9[MXMxaVXM7trXM&$&YM$<JYM2H]YM-NfYM@lh[MK'X^M?YK_M,0@7v2Ik3.YRd&M[\"\n        \"2?`M6:H`M_DZ`MsPm`MGXv`Mo])aMei;aMfoDaMguMaM0&WaMo+aaMj1jaM#D/bMoOAbM\"\n        \"']SbMrb]bMtnobMutxbMv$,cM6pS+MeEN$#F^_7#nqKHMN2ItL94N$#TZGs-\"\n        \"NrZiLjiEuLi5N$#`ZGs-M5UhLrn>WMn$QWM=@*0vH6)=-t))t-4[ovLFRE0v(UGs-DT:\"\n        \"xLI3B1v<UGs-\"\n        \"r.X$M2;uZM9H1[MNk<^MQ'X^MWpp_MDd2aMRpDaM'(#GMK@H>#/(lA#*lG<-/\"\n        \"lG<-`8kf-pONX(GVV_&HYV_&H5_e$K>_e$o#m?KIvA0ME%KJ1]d*j1]Vq]548oA#+xqd-&\"\n        \"sr-6$6IR*\"\n        \"i@`e$DK$44EN$44?I3@0@L3@0Gb3@0D4;F.'(ae$Lq3@0Mt3@0B`BL,?2JR*.=ae$k7t-6l:\"\n        \"t-6PARX(UF.X_r55@0]fRX(klZ_&nuZ_&iAce$c[Wk=d_Wk=.$EL,)FLR*f@`q;gC`q;\"\n        \"6nLR*7qLR*@C]_&Mk]_&J=ee$VOCXCce*MT^/&Jh`+9GjcF5DksJhiqu/\"\n        \";R*XKjw9R&6`aw1/\"\n        \"DtGKHKDDM0ci+Qc+VXnkJDMPce$'KKoR(FscWhAKe$tcce$ufce$vice$xoce$Wbu.C\"\n        \"@,eV[jGKe$)/\"\n        \"de$<*MR*1l[_&2o[_&3r[_&aN^_&[qee$u#=REms^_&h?fe$kE]e$uxn+Mdw@8%.b7p&=\"\n        \"K4m'4B0j(`dmA#=lG<-8Gg;->Pdh-ul?qVNA,LM_O6LMSU?LMWndLM-umLM\"\n        \"`$wLMfHWMMp/\"\n        \"^NME6gNMF<pNM#B#OMoM5OM(SiPMAYrPMG(SQM<.]QM2:oQM6R=RM8_ORM,fXRMQ&\"\n        \"RTMIL3UMXP<UMSVEUMUcWUMuiaUMZ+0VMh19VMj0dWMp7mWMsI2XM%O;XMqZMXM\"\n        \"usrXMv#&YM&H]YMmF/\"\n        \"8#jJFI#Rj](MK'X^M^.b^M:XK_M`&-`M#-6`M[2?`Mn9H`M0EI;#v>:@-qa`=-n<)=-cIg;-\"\n        \"dIg;-eIg;-fIg;-/Tx>-nnG<-iIg;-xN`t-t9j'MSD/bMuOAbM\"\n        \"T`^9vF6)=-wnG<-rIg;-;Tx>-$oG<-uIg;-JG:@-'%)t-JR2uLq9m.#:X*9#`&GwL3(m.#\"\n        \"0CRi.5NV0vYH`t-<Pc)M15lZMmD23vahG<-:nG<-8G:@-QeN^-_JL_&&PkGM)UY,M]P#v,\"\n        \"LqhP0fvmA#:10_-sSi-6Y+)2Mn7158,k(m9-tC2:3T<,<4^WG<;Gl]>@uH;@A(eV@B1*s@@\"\n        \"h9/DSQUJDjnVPK_Y:2Lw+TSSjx02U?UMMU@_iiU)C&)XHQBDXN2;>ZFI;DbDn3^cVZE]F\"\n        \"i_p'8erFL,LU2ci^+9GjmKEVnFrr.rO,A&G.oDk=ORr7RrbhxF<B^_&Xhee$GC_'8SFT.q0_\"\n        \"5R*S$HD*Go_S%5:lA#AGg;-eGg;-#Hg;-,ZGs-<?gkLUXFRMM2eTMi7BVMkh5WMr<vWM\"\n        \"pTDXM0<JYM>Y1%MpQ2/#m/A5#5<;0vAUGs-0>G)MnBdwLBB2/\"\n        \"#*R,lLKnW4#KAg;-$nG<-uHg;-vHg;-V@0,.p%7YM)6AYMB<JYM%BSYM&H]YM'NfYM(ToYM)\"\n        \"ZxYM*a+ZMUg4ZM2m=ZM\"\n        \"3sFZM2go#MM;2/#*2d%.*/\"\n        \"wiLq<d6#4Ag;-2Ig;-4Ig;-OAxu-t]u)Ml&-`M5-6`MB3?`MaPm`M<Wv`Mdc2aM1l;aM/\"\n        \"vMaM*&WaMj1jaM$?k<#-V3B-xBDX-'Z'@0&PkGM.wCHM:t1-M\"\n        \"2cTM'GqlA#=:)=-u00_-jJ^e$4N^e$Sa8F.Td8F.Ug8F.8Z^e$Q6@L,4YJfL7#OM0*CIYG%/\"\n        \"0fhOHE/2kDbJ2SZ&g2TdA,3%8$d3W)>)4X2YD4lru`4aV:&5a^XA5Its]5p@tA#^Gg;-\"\n        \"v(>G-.wX?-#.A>-u5&F-v5&F-(``=-mGg;-Z+kB-ulG<->wX?-W]3B-rGg;-Xjq@-Yjq@-\"\n        \"MRx>-0Hg;-2Hg;-^E:@-XwX?-5Hg;-H``=-1u,D-?mG<-ZeXv-%shxL*M3UM_P<UMSVEUM\"\n        \"T]NUM$dWUMViaUMWojUM%xsUMj''VMZ+0VM*29VM^=KVM]1dWMP8mWMSJ2XM+O;\"\n        \"XMqZMXMraVXM;h`XMSoiXMusrXM2$&YM&H]YM'NfYMj'X^MZ8u5v+xgK-YnG<-tAxu-LT-\"\n        \"iLkDZ`M\"\n        \"gPm`MLwMaMp1jaMl=&bMfD/bM$iJ+M<_i/#KE3#.i&GwLZ_i/#Y3H-.l8cwLY`i/\"\n        \"#:`Xv-oJ(xLT_i/#2B#-MF]i/\"\n        \"#vxtVI-WF3ki:9YY56uxYBd:>Z%hTYZ&qpuZ'$6;[(-QV[)6mr[\"\n        \"*?28]UrNS]2dio]3m.5^:D+Q^#M;SI^WXk=]/\"\n        \"G3kxm&J_=(vVIhIde$4Pde$M,FL,i.4ip&U2Al5-N]lBZjxla'EVn<mbrndBASo1`dooJ.&\"\n        \"ktXU9ip2);MqSCjiqcGW'8w]MX(uxn+M\"\n        \"vw@8%.b7p&;9S5'R=pA#T&uZ-t[NX(tAXw907qHM4E%IM5K.IMTQ7IMUW@IMV^\"\n        \"IIM9dRIMQa@.MPxNM00$BSI%/0fhOHE/\"\n        \"2kDbJ2SZ&g2TdA,3%8$d3W)>)4X2YD4lru`4aV:&5a^XA5\"\n        \"Its]5p@tA#^Gg;-v(>G-.wX?-#.A>-u5&F-v5&F-(``=-mGg;-Z+kB-ulG<->wX?-W]3B-\"\n        \"rGg;-Xjq@-Yjq@-MRx>-0Hg;-2Hg;-^E:@-XwX?-5Hg;-H``=-1u,D-?mG<-ZeXv-%shxL\"\n        \"*M3UM_P<UMSVEUMT]NUM$dWUMViaUMWojUM%xsUMj''VMZ+0VM*29VM^=KVM]\"\n        \"1dWMP8mWMSJ2XM+O;XMqZMXMraVXM;h`XMSoiXMusrXM2$&YM&H]YM'NfYMj'X^MZ8u5v+\"\n        \"xgK-YnG<-\"\n        \"tAxu-LT-iLkDZ`MgPm`MLwMaMp1jaMl=&bMfD/bM$iJ+M=er/#KE3#.j&GwL[er/\"\n        \"#Y3H-.m8cwLZfr/#:`Xv-pJ(xLUer/#2B#-MGcr/\"\n        \"#w+:sI-WF3kj=9YY56uxYBd:>Z%hTYZ&qpuZ\"\n        \"'$6;[(-QV[)6mr[*?28]UrNS]2dio]3m.5^:D+Q^$VVoI^WXk=]/\"\n        \"G3k#q&J_>1;sIhIde$4Pde$M,FL,i.4ip&U2Al5-N]lBZjxla'EVn<mbrndBASo1`dooJ.&\"\n        \"ktYX9ip2);MqSCjiq\"\n        \"cGW'8w]MX(uxn+Mvw@8%.b7p&;9S5'R=pA#T&uZ-t[NX(tAXw907qHM4E%IM5K.\"\n        \"IMTQ7IMUW@IMV^IIM9dRIMQa@.MPxNM01-^oI%/0fhOHE/\"\n        \"2kDbJ2SZ&g2TdA,3%8$d3W)>)4X2YD4\"\n        \"lru`4aV:&5a^XA5Its]5p@tA#^Gg;-v(>G-.wX?-#.A>-u5&F-v5&F-(``=-mGg;-Z+kB-\"\n        \"ulG<->wX?-W]3B-rGg;-Xjq@-Yjq@-MRx>-0Hg;-2Hg;-^E:@-XwX?-5Hg;-H``=-1u,D-\"\n        \"?mG<-ZeXv-%shxL*M3UM_P<UMSVEUMT]NUM$dWUMViaUMWojUM%xsUMj''VMZ+0VM*29VM^=\"\n        \"KVM]1dWMP8mWMSJ2XM+O;XMqZMXMraVXM;h`XMSoiXMusrXM2$&YM&H]YM'NfYMj'X^M\"\n        \"Z8u5v+xgK-YnG<-tAxu-LT-iLkDZ`MgPm`MLwMaMp1jaMl=&bMfD/\"\n        \"bM$iJ+MD'&0#uc;<#4=0[Mgo)*M3t@K#drcW-R[`e$5Rae$tcce$j?Se$e#-JUxa=PJR.[_&\"\n        \"t[@ulhTASo/(lA#\"\n        \"#Gg;-)Gg;-_e%Y-JC`e$(+ae$^7KR*p%[_&Qw]_&2]g'8N=j'M*980#nRL7#3I#/\"\n        \"vbUGs-`fHiL?Iw3#RAg;-M@0,.p8cwLjx70#RE3#.vchXMusrXMv#&YMx/\"\n        \"8YM&B8#Mew70#)Ig;-\"\n        \"6<)=-1nG<-2nG<-3nG<-6*)t-7HR#MY-6`Mb2?`Mdc2aM(wMaMn%WaM=>k<#]Kx>-rtcW-\"\n        \"qB6L,&PkGM)UY,MNPtl&AKS5'1'4m'4B0j((jnA#;lG<-7Gg;-8Gg;-lD:@-c)Vl-C]iKc\"\n        \"NA,LMeO6LMSU?LMWndLM9umLM`$wLMa**MMfHWMMeg/\"\n        \"NMP0^NMQ6gNMR<pNM)B#OMoM5OM#5;PMFSiPMGYrPMH`%QM;(SQMH.]QM^F^-#mnQK#8mG<-\"\n        \"6Hg;-C;)=-O)>G-E;)=-*/LS-\"\n        \".9RA-^;)=-RHg;-SHg;-$xX?-VHg;-d;)=-ZHg;-[Hg;-OPKC-E9RA-CfnI-DfnI-x;)=-\"\n        \"6Sx>-oHg;->xX?-wmG<-uHg;-vHg;-HxX?-,nG<-@nG<-O[]F-3lg,.W@gkLdYK_M`&-`M\"\n        \"#-6`M[2?`M%9H`M_DZ`MmPm`MbVv`Mo])aMei;aMfoDaMguMaM*&WaMo+aaMj1jaM#D/\"\n        \"bMoOAbM']SbMrb]bMtnobMutxbMv$,cM=oS+M+?A0#?9E)#4F^2#@SGs-ZL0%M?L<0vKUGs-\"\n        \"rp;'MAU34#OSGs-IshxLCbE4#9Ag;-0P:h.N8J5#cx(t-)YajL)ZxYM+g4ZM-sFZM/\"\n        \")YZMX?)3vx;xu-J>G)M[Gv6#7Ag;-BN`t-Zl&kLjc2aMHq3<#LfG<->MU[-e+EX(&PkGM)\"\n        \"UY,M\"\n        \">cTM'F#1j(rDnA#<lG<-]wqd-:N@L,=j?qVIvA0Mcr//\"\n        \"1etpA#m-A>-,E:@-YGg;-*xqd-ZvtEIb_.NMD0^NME6gNMHH,OMEZGOM:aPOMA5;\"\n        \"PMFSiPMGYrPMH`%QM@F+RMSL4RMV_ORM\"\n        \"J'RTMD.[TMk3eTMtcWUMiiaUMXusUMcj5WM&1dWM^7mWM_=vWM)h`XMgniXMm<JYMFlh[M>.\"\n        \"7]MMMA8#$uZK#R%&'.Ne)'MT9t^M`WK_Mh2?`M%9H`M0EI;#X9xu-OHqhLbVv`Mo])aM\"\n        \"ei;aMfoDaMh%WaMi+aaMl=&bM/\"\n        \"K8bMCPAbM']SbMrb]bMtnobMutxbMunS+M$?J0#L`''#3o3$M:/\"\n        \"J0#4Ig;-aIg;-bO,W->u_e$r[`e$5Rae$tcce$p&g@kA<Z+r#2/DtQ772L72Fp/\"\n        \"n8Sw9e0Se$.Lc+Mj[-2Mr`POMdF^-#d0wK#;mG<-=mG<-]Hg;-pHg;-0a`=-lUGs-+*\"\n        \"ofL1Af0#(Gg;-0SGs-rp4wL5Kx0#.H6x/\"\n        \"INV0v*<w0#pBDX-Yp]e$)-^e$_6?R*h-fNM)YrPM\"\n        \"K&RTM'7mWMQ'X^MQ$FBMcJuCjU[NJMm_vrn:T.XC+I2W%;<K_&+GxFMC@*0vLhG<-oZGs-\"\n        \"w8j'MGXN0v]UGs-5O]vLumMxL[X+1#Dj](M]jg_Mdc2aM1WZ)M:S+1#wh&gLIjj#.Y1xFM\"\n        \"Kx90M]hji0YQmA#plG<-qlG<-.mG<-/\"\n        \"mG<-pwX?-v;)=-w;)=-W0Yc->8NR*LCee$ZaUX(Y9^_&&)%FI]?PDMV78crXq&gMNrH_&\"\n        \"kSd+V+A.gM8:0wpbXxOoo?S3OePNcMc[Xe$_6?R*\"\n        \"h-fNM)YrPMK&RTM37mWMQ'X^MQ$FBMcJuCj`H#-NgnN4oZCvr$,I2W%;<K_&6#V_&<Z`9Mc;\"\n        \"'RERBCX(pk$_]niDcV=w4,N*NWSS-tOAY=tRe$mWJ'SU;l@kdBASo;,qoo&@5R*jB]e$\"\n        \"Ncc-6o*Ye$2Za'8Hm&kLniXL#plG<-qlG<-.mG<-/\"\n        \"mG<-2dN^-V[SX(w_SX(V?+LGHuV^M_-b^MN9t^M`WK_MeAHDMPl+Sn9HrA#sqcW-h@B-mVu#\"\n        \"_]W//DNbcdKcP>+eZJ[J_&mw0wp\"\n        \"3W^+`t)gSo.#/DNu<Ye$/dU_&Fh6L,2C-IMKx90MH4?xL^2wx4vE(m9xTL2:-:)=-.mG<-/\"\n        \"mG<-,9RA-vRx>-&a`=-'a`=-_j%Y-S/d9MPOJ_MfJd`M#Qm`M:8W*M62c1#E-4&#8HbGM\"\n        \"V*WvuQhG<-/Gg;-<:)=-[g3j.*g60#jk@u-8@t$M,/\"\n        \":.vUCg;-^Hg;-fHg;-jZGs-<BuwL&+?wLT^3uLc^2xLFRE0v(UGs-7[ovLCbE4#5Ag;-\"\n        \"jPKC-/Ig;->I]'.4e)'MFG1[MNk<^M\"\n        \"r$Y5vg6)=-QnG<-RnG<-]iDE-WDdD-o7&F-r<)=-V-kB-(ucW-kb=R*w1=GM/\"\n        \"XlGM2hu,M-bTM'A_lA#6Gg;-Z-A>-HGg;-d-A>->Pdh-ul?qVJ&K0Mj%KJ1i2+j1uIr]\"\n        \"5etpA#7k3f-\"\n        \"Z5IR*%9IR*DK$44EN$449i:F.:l:F.D4;F.E7;F.F:;F.G=;F.?2JR*@5JR*_v,:2`#-:\"\n        \"2VxJR*I/=eZp*#@Ko^<F.VV=eZ*nDL,uXSX(]%`q;^(`q;)FLR*``gw9acgw9A&,F.*A?;$\"\n        \"(+?v$0tnP'qO-NU>lF]OOVce$c#VX(jj^_&R?%#,[+L>#)lkA#)Gg;-MGg;-kGg;-)Hg;-\"\n        \"YsA,MqDu1#AQv;#k-]-#9Ag;-7Hg;-]Hg;-nBg;-#W:d-VprEI4:ol&ftXAPe>^e$7TMBP\"\n        \"@FsRnDTs7RD3Se$N+XAP=xLq;tu)GVpX),WwR]APjt[_&9.]_&)vw-6K@ee$]mBulh.*_]X]\"\n        \"=eZX]=eZ1r`S%rDnA#`o6]-lP^e$M7OX(RxV_&9>Yw9Wx(MMkg/NM&0^NMq5gNM55;PM\"\n        \"4SiPM5YrPM6`%QMu/\"\n        \"E6.b+dTM^ojUM'i5WMp0dWMq6mWM:=vWM.*jxLYuWuLg@b<#l_DuLF812#^Hg;-fHg;-\"\n        \"jZGs-/vo(M6niXMusrXMv#&YMI3B1vcZ`=-(nG<-*Ig;-,Ig;-p.E6.\"\n        \"E+kZM/\"\n        \"*>$Mu:12#:nG<-JQKC-#Tx>-[Ig;-r<)=-mnG<-hIg;-J:RA-rnG<-:6]Y-Zs]e$$,]+\"\n        \"MHPtl&5:lA#NW=Z-n%V_&<5V_&=8V_&8Z^e$ff1@0s*#44H5_e$OJ_e$j)AL,RS_e$\"\n        \"SV_e$Xf_e$MD@qVY+)2M$bSV6qnJ88NpH59Fp`M:#b$j:oNZJ;qa;,<F>XG<M(m]>J1iY?\"\n        \"NU*s@3l%pA5(]PBT6#mB21XMC4C9/Df2VJDg;rfDE?mcED)B;IxN82L2BPJM_wjfMS[/,N\"\n        \"X3c`O3(KAPba_]PeJSSS6(QPTFqiiUsO-/\"\n        \"VtXHJV1CefVqbDGWst%)X5oDDX6x``X,_x%Y$_9>ZVVruZkP*$Q#f/]XcUZV$3URe$.aU_&/\"\n        \"dU_&=U,ip:BoP'C'hJDOxH_&jZV3OS5VlJ\"\n        \"4'K_&KfDk=`_(AOq&d`Oh0:#QE+SX(aMZ_&[pbe$]sbe$pnKR*eYZ_&`&ce$4qDo[F:\"\n        \"YVRiJsrRdA88SeJSSSfSooS;165TnxOPTo+llTp412UQbecW+NU9i&]x@X%C]`Xv9x%Y#\"\n        \"OOAY\"\n        \"oV?e?(P[_&S$(44'fDxk0?quZ-66;[*9dV[0?CX()/\"\n        \"de$Nx6@01l[_&2o[_&3r[_&YA'P]M(#m^0LRe$pQGuc15XM_&.Re$oio'83Mde$@hTX(\"\n        \"5Sde$bvUX(JtOlJt0Q`k_N.&lEZK_&\"\n        \"lcNR*YfRe?[qee$(iGL,ms^_&h?fe$uTDXCr,__&9C?L,tre+M1LdY#+xB#$`dmA#&Gg;-'\"\n        \"Gg;-Fd98/'fi4#A^'HM9n(-M8X92''hqA##Ug_-t[NX(Jw?L,3K^e$[_Q9i2C-IMTQ7IM\"\n        \"UW@IMV^IIMbmfwuHKKC-uWHp-wmKe?fau?K$eq-6F/\"\n        \"_e$G2_e$Z-HR*OoV_&$;g3O[bOX(>Pcw9RS_e$SV_e$MqSk=%P2@0%Kv?Kk^HR*`IW_&\"\n        \"4lg3Os?/LG%,:F.Q2S1g0;V>6`rmY6\"\n        \"/=4v6'=K88EIkS80kG59'ibM:/0%j:nE?/\"\n        \";$r^J;vjvf;E5=,<@,XG<fqm]>ZU2#?2>hY?a6+s@8u`SA9(&pA;:]PBgm#\"\n        \"mB34O2CBvZ9M8*Y_&&-^q;X?4@0(3^q;HMJR*Uh;F.N$uKc\"\n        \"EvQX(_Q4@07a^q;pS-:2F0be$G3be$H6be$I9be$]_0ipP5:2LxUVML^nNJM9^lfMS[/\"\n        \",NTeJGNhNgcN[1/)OY6YDO80l34V)gCjW<(&P4*HAP*g`]P]W$#Q=WD>Q8sWSSeQroSNqQPT\"\n        \"K+miU#c-/\"\n        \"VtXHJVC$ffVpX),WKHFGWx'acWSm')XNdBDX$A``X2qx%Y$_9>Zc%suZ-66;[8xk?\"\n        \"QDrR1pK->low28VQZ@I_&i.AX(k[ZrQf/wuQiDN;Rle4R*4lvr$0tnP'J`:5K5]O;R\"\n        \"=DZ_&uK1ipcc#@0Tou7Rl@EX(>6EJMeg/\"\n        \"NM#5;PM*`%QMY:^-#RKx>-7Hg;-Y;)=-cmG<-kmG<-lHg;-&<)=-*$)t-bS^sL]_h2#VmG<-\"\n        \"RHg;-Brpo/r.(3vLMg2#2Ig;-4O,W-+O>R*\"\n        \"QSGLM]6<MMj<EMMnG,OM'ZGOMx`POM0+J6MU1v+D;_TJD7_5,Ei&%#QpX),Wst%)X0_\"\n        \"ADXiifsR:ZsL^qZ42'?K@&Gv?J_&3Bwx+JlL5K8eRe$4YH'SaCKcMY6YDO#i<X(Ml2`amI?/\"\n        \"V\"\n        \"XoPe$H,Y._pe;,WLJPe$47Exk2>ti_.FRe$:1]_&ANYk=@iT.hU8`lgR/\"\n        \"%2hck->mVrCSoU7w1q)lkA#)rcW-`,^e$493XC>6EJMKx90M%iji0(jnA#[H)a-<gCe?b_.\"\n        \"NM,0^NM-6gNM\"\n        \"3ZGOM4aPOM;5;PM@SiPMAYrPMB`%QM@F+RMSL4RMj&RTM_3eTMwh5WMj0dWM?7mWM@=vWM)\"\n        \"h`XMHniXML*jxLBi$3##pXG/Cd#7v'`4WMkh5WM>u63#8k@u-FHbGM.wCHM/'MHM0-VHM\"\n        \"U]3uLk773#PoM<#Y0)0v]UGs-/\"\n        \">G)MnH2XMnBdwL=v63#4nG<-9nG<-#lq@-M[Gs-hj](M1ZmDM&AH>#)4Z;%*=vV%rDnA#`\"\n        \"o6]-lP^e$M7OX(RxV_&9>Yw9Wx(MMkg/NM&0^NMq5gNM\"\n        \"55;PM4SiPM5YrPM6`%QMW&RTML3eTM^ojUM'i5WMp0dWMq6mWM:=vWM.*jxLebgvLx@(2#\"\n        \"mEluuQL,W-cJU_&/dU_&6#V_&LKY+V[C2pJhFQe$?4#SeOINJM3'N5T85Z_&Y8Z_&ERY4f\"\n        \"jmYDOS`Pe$e(SX(Zmbe$[pbe$]sbe$_#ce$`&ce$q8GlS.GXVRiJsrRdA88SeJSSSg]\"\n        \"45TioklToOdfV-kM5TU7[_&d^vOfMg9)X8eRe$$D[_&ufce$vice$$]Dxk2-Y]Y)\"\n        \"htxYDvquZ\"\n        \"'$6;[(-QV[A)nr[*?28]IMNS]2dio]3m.5^4vIP^xM*m^13Ww9B<MR*8+]_&3Mde$eRl3O/\"\n        \"_m3OW>]%Ou9m%l.h2Al`VO]l0$jxl]X->m/d>Ppn#Ylpj#:MqR@s.r)lkA#=3]Y-]#^e$\"\n        \"''^e$O.g=YrH&p%4t7p&MpS5'xVnA#ao6]-t[NX(<Q3XC07qHM3<`-M[:2,)TYhJ)Uc-g)\"\n        \"VlH,*:#)d*Ac<#-<avY-?&sV.GC5s.IUlS/M$.m0QHE/2wibJ2SZ&g2Vvxc3]9A)4^B]D4\"\n        \"f`u`4aV:&5m,YA5i%r]5xfh>6>;'_]l<PX(a(`e$&[/LGe/\"\n        \"Me?kF`e$(BIR*mL`e$oR`e$pU`e$Wxk'8MB,:2He3@0).ae$6HQX(2nX_&S04@00Cae$v&\"\n        \"SV-Af[MC9LtiC4C9/Ds`rfD\"\n        \"=q5,EPZQGE1ZrcEFH2)FE2^VI3X%sIGD>8JHMYSJIVuoJM%72L`cUMLW[NJM''lfMS[/\"\n        \",NVq]GNO5#LGNuVk=Vabe$U&gCjcWc`Ow?,&PQ-LAP*g`]P]W$#Q13D>Qk]SSSeQroSj)OMU\"\n        \"l4hiU$lHJV1CefVpX),WE6FGWx'acWst%)Xt.``X&Lx%Y$_9>ZVVruZ-66;[hrbPTLMGq;;+\"\n        \"wr$.b7p&/kR5'0tnP'^N(EOPQ4MT+sSw9(,.JU[U$QTWBT4oxkHJVpX),W(biPTjt[_&\"\n        \"9.]_&#?)44K@ee$?E2wpwB#PoS?mA##Gg;-)Gg;-#$H`-?@o342C-IMA>FJMX+UKMR(\"\n        \"C0Mp2wx4k[J88&X(m9q<C2:55l]>4PH;@5YdV@6c)s@W7VPKL#:2L^<GDO'>TSSp412Uq=\"\n        \"LMU\"\n        \":LiiU0-:>ZqUqmTd&%&+b[a(WhAKe$U5ED*V4l%lm3.mTFa^_&b-fe$&`>L,J)^KMM.\"\n        \"L0MIn)/:/GdV@7Lt2C(/\"\n        \"HiTnWae$eqCL,cSZ_&-wDL,t+I_&Q<J]Fp@C2ULB>e?4i&DW3Vd.U\"\n        \"9ejJ)`0arn%-'8oDkD2UEm7L,J&K0MUn)/:/\"\n        \"GdV@1(=2CYO62UBTl)+@(]iKuJ%#QwOLMU2L*,W#%RNUC1Aucq*[V$wtQe$.aU_&/\"\n        \"dU_&h&<A+808p&pGbMUe>^e$Hq?L,1X.ciN#&aF\"\n        \"?$Se$d4g:ZC2^VIF;#sIIVuoJe_M5K.X5R*DbU1gQUaJMD3Se$X5Z_&Y8Z_&A.\"\n        \"BrddZYDONPPe$b1wUm[a?>Qc2N;RbXI_&loZ_&h>ce$2O+FImMce$qYce$K/\"\n        \">i^:-BDXu0]`Xv9x%Y\"\n        \"wB=AY'][]Y,'L>ZB2'F.p,wOf'6mr[*?28]=)NS]2dio]3m.5^6,]P^5f,F.:$uRn0,=2_\"\n        \"a1Qe$tFhw90chCj7M'/`u]JJ`Ar^f`HNcofu;dlgX.GDkpi+F.OtGkXS/Zk=[qee$m.S1p\"\n        \"t#BSo.Z#5p)Q>Pp$HYlpj#:Mq&bXiql5q.r.rbxuA_lA#MJU[-aSU_&/\"\n        \"dU_&JV3`W#qxl&A_lA#'uDi-@Co34.+_HM?<`-Mn:2,)H5hJ)I>-g)JGH,*;&vG*Za_-6[:\"\n        \"Y?g>6EJM/EOJM\"\n        \"Gj0KMN7hKMT>qKMWI-LM'P6LMSU?LMT[HLM8odLM^umLMf$wLMa**MMm13MM(AO%vWu%'.]\"\n        \"Pd&M_BNMM`HWMM/OaMM'h/NM)%KNMq<pNM)B#OMnG,OMnN5OMvS>OM?ZGOM_aPOM`5;PM\"\n        \"VGVPMZ`%QM3r@QM5(SQMZ.]QMY:^-#ex=G-8mG<-p]3B-q]3B-r]3B-H``=-URx>-<cAN-E;\"\n        \")=-_wX?-7PKC-Bk&V-LHg;-X6&F-pRx>-RHg;-SHg;-69RA-VHg;-JX&7.o15)Mw&'VM\"\n        \"f,0VM$29VM]7BVMo>KVMji5WMZ%QWMw=vWMsB)XMtH2XM=O;XMpTDXME[MXMxaVXMMh`XM.\"\n        \"piXMttrXM2$&YM$<JYMcH]YM+</#MiUn3#ja;<#LHbGM0-VHMWojUM-*%7vK=2O#0rcW-\"\n        \"xu^e$e4`e$#r`e$*1ae$i[FG)Sd6,EM%72Li&%#Qk]SSSrFhiUpX),W0-:>ZqO-/\"\n        \"V=06W[s0[V$5[Re$#/xFM/XlGM.L>gL^Iw3#<:)=-5Hg;-Ntqh.5-L+vdrX?-EHg;-HZGs-/\"\n        \"f*$M\"\n        \")'A0#RAg;-)mK4.w@2UMQQ<UMYVEUM-gb-vu;xu-,T:xL,/\"\n        \":.v`Cg;-`ZGs-IxE$Mln>WMn$QWM==e3#EF`t-td)'MtmiXMusrXMv#&YMx/\"\n        \"8YMP^,2vv)A>-)Ig;-6<)=-1nG<-2nG<-\"\n        \"5*)t-(46&MN/cZMwF23vkhG<-5[Gs-Fj](M/\"\n        \"t$7vDBg;-o*>G-5lq@-[Ig;-%Tx>-7B7I-(0A>-#b`=-hIg;-lIg;-4h%Y-w#/F.w1=GM/\"\n        \"XlGM/UY,M/Qtl&A_lA#Rwqd-n%V_&i(U-Q\"\n        \"2C-IMCW@IMD^IIM9a@.MUQ#v,sQY>-5jR8/O6eM1QHE/\"\n        \"2qVbJ2SZ&g2W)>)4EO[D4`Mu`4aV:&5T9XA5Its]5-$rA#7f0o-_4:F.svHR*jcE_AaTTk=(\"\n        \"BIR*mL`e$oR`e$8f:F.QAs-6\"\n        \"Gb3@0Jk3@00hX_&3qX_&S04@00Cae$+ggl/)FCJC9LtiCk;;/\"\n        \"DmMrfDC-6,EPZQGE%6rcEFH2)Fc.C;I:<kPKM9_-6L=Oe?j*DL,RTbe$SWbe$0f&44Vabe$*\"\n        \"%`9ivinEesEDL,[pbe$\"\n        \"bKH_AV7Wk=Q5gw9g6Pe?r+[_&s.[_&<A6@0oSce$D(/\"\n        \":2w:[_&Le'44qO>eZ+(TX(vice$Z^v-6,][_&C,,F.G+@;$u6+GVcCrUm&+?v$/\"\n        \"FZ;%60oP';_TJD<6i`FYn`JV45uKG_D?SI\"\n        \"F;#sIKc1pJEWl34,RGG)PLE/\"\n        \"M0LRe$RTbe$m3DL,8Lu-6`JZ_&^vbe$>vT+`,YpoSnxOPTtwrcW&wBX(tcce$ufce$vice$\"\n        \"1'Q1p2-Y]Y.?QV[LKqr[*?28]7mMS]2dio]3m.5^6,]P^\"\n        \"^&u92,J1ci1;'/\"\n        \"`lE`f`keGDkF+AX(iT<REk`NR*5v)44[qee$x1OR*ms^_&h?fe$EP*44r,__&9C?L,w1=GM)\"\n        \"UY,MsPtl&5:lA#@`xb-n%V_&^ov922C-IMCW@IMD^IIM9a@.MCQ#v,\"\n        \"sQY>-/WR8/O6eM1QHE/\"\n        \"2qVbJ2SZ&g2W)>)4EO[D4`Mu`4aV:&5T9XA5K0T>6l@nY6#o3v6qnJ88gcI59_caM:)t$j:\"\n        \"oNZJ;pWvf;AYl]>DuhY?HC*s@3l%pA5(]PBN$#mB21XMC3:tiC\"\n        \"a)rfD>$QGEJOpcE@62)FV`B;I(b82L8TPJMe3kfMS[/\"\n        \",NUnfcN+K-)OW*GDO^J.&PaWCAPtA`]P^a?>QQhUSSNqQPT_djiUsO-/\"\n        \"VtXHJV7UefVqbDGWrk`cWIZ^`X&Lx%Y$_9>ZPDruZ\"\n        \"-66;[7tWgV-+O1p=G?SIF;#sIKc1pJtX<X(XlvUmPRjfMtwrcWJjK_&tcce$ufce$vice$\"\n        \"xoce$[5IG)@238]2dio].dIP^35XM_=5<R*)#T.h2DBJ`aXL]ln0ixlg^=PphgXlpl5q.r\"\n        \"#YkA#.Gg;-/\"\n        \"Gg;-1Gg;-5Gg;-7Gg;-8Gg;-J`xb-81W_&RS_e$SV_e$gQHR*_FW_&Zl_e$#ttEIY+)\"\n        \"2MOaSV6xW_M:m<$j:nE?/;C#]J;ws;,<@,XG<S-^PB<CxlB7Lt2C3u@cVo)Y_&\"\n        \"R_;F.Sb;F.BmQX(O1CL,?\\?Y_&QQbe$RTbe$SWbe$Wdbe$Zmbe$[pbe$1D.:2t1[_&oSce$&\"\n        \"oSX(w:[_&x8I_A#<I_A+(TX(vice$$pPe$G1@;$50c(W#*&F.CCwr$/FZ;%60oP'=kgJD\"\n        \"WQ2L,?WBxk?^w]G$(Re$E-be$F0be$E?/ciT.;5K-PQ,WEGZ+VU[NJM_wjfMYn/\"\n        \",NZwJGN(NZDO]a2L,Fjfw9^vbe$/\"\n        \"Ch:ZpxooSnxOPTtwrcWh9BX(tcce$ufce$vice$^5o'8(P[_&\"\n        \".c[_&)/\"\n        \"de$6ITX(1l[_&2o[_&3r[_&qgCrdr?$m^B-Se$u^)qrLA(J_9Nwi_?mCX(9.]_&4Pde$;_\"\n        \"AulcRGDk=sOe$d&VX(]89_]MNbq;[qee$Q6jw9(iGL,sSVX(h?fe$&>OR*r,__&\"\n        \"9C?L,w1=GM)UY,M;Qtl&5:lA#_j3f-4,(@0.+_HM?<`-MO:2,)H5hJ)I>-g)JGH,*:#)d*\"\n        \"wTqA#(]3B-5OKC-HGg;-OGg;-pQx>-RGg;-SGg;-JOKC-_lG<-`lG<-YBdD-H+kB-G'*q-\"\n        \"u]r-6B+='o]@VMM/OaMM'h/\"\n        \"NMs$KNM.<pNM)B#OMoM5OMpS>OM9ZGOMXaPOMA5;PMPGVPM0`%QM3r@QM5(SQMZ.]QM^F^-#\"\n        \"XSVO#8mG<-p]3B-q]3B-YwX?-H``=-URx>-6>aM-E;)=-\"\n        \"_wX?-cRx>-LCdD-d``=-RHg;-SHg;-*F:@-VHg;-8A-5.tOc)Mk&'VM-.0VM$29VM]7BVMc>\"\n        \"KVM^i5WMZ%QWMk=vWMsB)XMtH2XM=O;XMpTDXME[MXMxaVXMMh`XMxoiXMCtrXM2$&YM\"\n        \"$<JYMcH]YM+</\"\n        \"#MntE4#*Pv;#oMpSMF^$TMrv70#BF`t-U)ChLumMxL5dE4#1Ig;-2Ig;-4Ig;-hSx>-LIg;-\"\n        \"2)uZ-Yp]e$)-^e$$&1F.Z4DMMqYGOMr`POM^F^-#4Y`O#:mG<-5Hg;-\"\n        \"C;)=-&kq@-cmG<-pHg;-5/A>-tTGs-RB=gL0&O4#h$6;#YsKHMh92/\"\n        \"#GfG<-EHg;-HZGs-a15)MU3B1vhCg;-=tA,MGgN4#qsxcWX>)44K@ee$+o>L,-%\"\n        \"UHM6Q7IMutDi-CD>qVnet+M\"\n        \"+sjO#k``=-G`/$0x?cuuX_V4#(Gg;-8L`t--Pd&M;'MHM0-VHM$:2/\"\n        \"#Y4)=-EHg;-HZGs-GEQ&M9mx#M_uW4#MVl)MaD23vgUGs-H,,)M4G1[MTk<^MX%Y5v]6)=-\"\n        \"QnG<-RnG<-P41lL\"\n        \"w:.;#cJ6IMb-w1MgMsu5S?mA#wlG<-xlG<-:mG<-;mG<-'xX?-)<)=-)?DX-PX/\"\n        \"eZM0pl&xAYDXe>^e$2QSV-BV?SIF;#sIHNcof^J`lg#U=aXlUdi0k23L,E;#sIV@\"\n        \"r5K5OTxXrE-L,\"\n        \"IUwr$,I2W%I5DX(n%%&Yq17&Yx9GR*B[t%4Ui^VIF;#sI#OOAYSSDX(&V7&YIO?F.Xd[Y,G@\"\n        \"Mq;t]Pe$#/xFMRem##=fG<-6lG<-W@xu-F:3jLVp$tLi7'5#9)ChL)N=-vNCg;-TZGs-\"\n        \"6JY)M$niXMusrXMv#&YMx/\"\n        \"8YM*a+ZM,m=ZMV,d2v3sX?-n^3B-6[Gs->gHiL)+c5v#Cg;-RnG<-SnG<-/\"\n        \"G:@-[Ig;-gIg;-hIg;-T(hK-rtcW-e;^e$'PIk=.+_HM;K.IM=W@IM>^IIM\"\n        \"QI-LMRO6LMSU?LMXtmLMZ**MM+4*2M[aSV6xW_M:m<$j:oNZJ;rjVG<G_]\"\n        \"PB61xlB7Lt2CMHq=Yo)Y_&M+CL,<6Y_&IPJR*?\\?Y_&QQbe$RTbe$SWbe$iS9REaMZ_&[\"\n        \"pbe$1D.:2t1[_&\"\n        \"oSce$&oSX(w:[_&sZPe?%G[_&vice$$pPe$MnWS%eum]YATx?0ic?SIF;#sI%[bAYgOXuYx#\"\n        \"brn.nIp&.FRe$/?^e$.QSV-ID^VIF;#sIKc1pJ*(=X(ckCL,rq'GM,3m2vt6)=-0PD&.\"\n        \"d1tZM4G1[MHk<^Mj'X^Mi(NEMwaTM'6TgJ)]Vq]5A_lA#^Gg;-qGg;-rGg;-4Hg;-5Hg;-'\"\n        \"xX?-#nG<-tTGs-_ZbgLXBB5#=rA,M:>JYMbAB5#A(4?Zd&%&+M@s1K9cFwTn/ElfQ&`lg\"\n        \"#hTYZl<^YZO;VYZO;VYZc@7K)%'v=YBvq>Z5n1VZp]^e$TY_e$lCMe$((l+MknTrZ2Tbe$>\"\n        \"NQ1p42+2_#YkA#/Gg;-5Gg;-:Gg;-ZGg;-[Gg;-aGg;-pGg;-3Hg;-:Hg;-WHg;-^Hg;-\"\n        \"rHg;-%Cg;-i'hK-i'hK-i'hK-i9)E/\"\n        \"1X1vu@97#M?6O'MV-avu@^w]G'PD_&E-be$F0be$5TWY,T.;5K>;-qrXYGi^LX'W[\"\n        \"wi2ipBibi_4DBJ`HNcofjo`lgj#:MqA_lA#0Gg;-6Gg;-\"\n        \"%X=Z->u_e$qX`e$r[`e$4Oae$5Rae$'W5@0#A[_&rVPe$q]MqVq]MqV$1l+MoIA#M2SP&#\"\n        \"QnLAMoOdh-OA>qV$1l+MB`p5#pcrr[qcrr[so.s[TIhw94Pde$qi%s[jOdh-OA>qVq]MqV\"\n        \"%7u+MCi5Q#pcrr[ri%s[kOdh-PD>qV%7u+MCi5Q#qi%s[kOdh-PD>qV%7u+MJ%$6#b0H;#\"\n        \"aHbGMRh2vuk$)t--Pd&M/'MHM0-VHM$:2/#Y4)=-EHg;-HZGs-GEQ&Md2m2vZUGs-MVl)M\"\n        \"0)>$MFo#6#H,,)M:G1[MTk<^MX%Y5v]6)=-QnG<-RnG<-P41lLsThlL>qA,M^r>Q#\"\n        \"FDhLM2PJ#M7M#PM2PJ#M6kG<-A?L1M2PJ#MBx<<#52#O-6;>k-jpvW_&7l+M]l,6#5o]S],\"\n        \"6>k-\"\n        \"jpvW_&7l+M]l,6#5o]S],6>k-jpvW_&7l+M]l,6#4iSS]6o]S],6>k-jpvW_&7l+M^o,6#\"\n        \"T5>k-jpvW_@prIqRpQS]5iSS]6o]S],-#O-52#O-52#O-52#O-52#O-7DY0.=orUMa-#O-\"\n        \"52#O-heg,.0O[#McN,W-%iSe$+OGG)-tOAYG<Se$Z^li'EoND*TdA,3nE?/;+Z.5^xxf/\"\n        \"`3t[V$B-Se$#/xFM/XlGM6-VHMM^_sLa$?6#Z;)=-VmG<-TZGs-/vo(MHniXMusrXMv#&YM\"\n        \"x/\"\n        \"8YM*a+ZM,m=ZMY;d6#VpX?-4Ig;-g<)=-[Ig;-gIg;-hIg;-lO,W-e;^e$1E^e$7W^e$8Z^\"\n        \"e$QP_e$RS_e$SV_e$Xf_e$U0Kk=]@VMMr;pNMmA#OMoM5OMr`POMG(SQM6.]QM2:oQM\"\n        \"5L4RMHR=RM?eXRMQJ3UMRP<UMSVEUMZ+0VM[19VMnH2XMoN;XMqZMXMtmiXMTEEU-s@:\"\n        \"L0cBxUm;apl&0LRe$/?^e$?@x(3ID^VIF;#sIVq]GNNDDX(afFL,K@ee$U$HD*9Hto%e8Ke$\"\n        \"4vCk=$YWlJP:eML%o<X(3C$#,Js$2hipt1q+&%jqE5^._c`4Al.q[5'#YGs-Ck&kLKWqSMF^\"\n        \"$TMHk<^M[kw&M`Md6#73u6#hHbGM0-VHM^UG*vM6)=-,8K$./NpSMF^$TMFWUsLE=d6#\"\n        \"NZGs-l[u)MAN=-vhCg;-RHg;-THg;-rHg;-tHg;-uHg;-vHg;-xHg;-*Ig;-,Ig;-*2d%.=`\"\n        \"K%M44k^MZ,6`Mn2?`MguMaMh%WaMj1jaMuIk3.T+`EMcNtl&0tnP'5:lA#7lG<-<lG<-\"\n        \"=lG<-8Gg;-QGg;-RGg;-SGg;-XGg;-`Gg;-lGg;-mGg;-oGg;-/\"\n        \"Hg;-0Hg;-2Hg;-6Hg;-9Hg;-a&^GMaKl(N_Ec`Or6GAPba_]PnFHJVoOdfVqbDGWu0]`\"\n        \"Xv9x%Y&qpuZ8cTj_>Yd@b\"\n        \"96xr$0tnP'?Qe]GuZvi_%-be$F0be$YIO1pqjKGNHKtcWDWK_&tcce$ufce$vice$xoce$*\"\n        \"2de$,8de$_E(44aN^_&[qee$g<fe$h?fe$kE]e$+oBHM.t1-M(kpi';^K/)=p,g)>#H,*\"\n        \"QHE/2RQaJ2SZ&g2X2YD4ZD:&5.4oY6rE_M:m<$j:oNZJ;/\"\n        \"l[PB0uwlB21XMC6UpfD9qlcEQINJMRRjfMS[/\"\n        \",NW*GDOMuEAPba_]PnFHJVoOdfVqbDGWwm<wpd`<wpw-%#Mp`v6#];r$#\"\n        \"c><-vXCg;-TZGs-m=G)MxaVXM6niXMXxQx-bp$YM(08YM*a+ZM,m=ZMZ,6`M[2?`MguMaMh%\"\n        \"WaMk4aEMVNtl&1'4m'7^,g)8gG,*QHE/2RQaJ2SZ&g2X2YD4`rmY6l3_M:m<$j:oNZJ;\"\n        \"/l[PB0uwlB21XMC6UpfD9qlcEQINJMRRjfMS[/\"\n        \",NZECAP[N_]PnFHJVoOdfVqbDGWX^b?gEPb?gw-%#Mo`)7#73u6#kHbGM0-VHM^UG*vOH`t-\"\n        \",5UhLEWqSMF^$TMqsI,vo6)=-NZGs-\"\n        \"l[u)M)N=-vhCg;-RHg;-THg;-rHg;-tHg;-uHg;-vHg;-xHg;-*Ig;-,Ig;-*2d%.=`K%\"\n        \"M44k^MZ,6`Mn2?`MguMaMh%WaMj1jaM98saMk4aEMcNtl&0tnP'/(lA#7lG<-<lG<-=lG<-\"\n        \"8Gg;-QGg;-RGg;-SGg;-XGg;-`Gg;-lGg;-mGg;-oGg;-/\"\n        \"Hg;-0Hg;-2Hg;-6Hg;-9Hg;-a&^GMaKl(N_Ec`Or6GAPba_]PnFHJVoOdfVqbDGWu0]`\"\n        \"Xv9x%Y&qpuZ;(Qg`WKCD*YF@SI\"\n        \"F;#sIRLE/\"\n        \"MNDDX(RTbe$uSDX(pgA;$Nmuof^)^e$0B^e$f+wUm2uu7Rc7Qe$3W=F.<6h?KfdC`\"\n        \"W6qADXu0]`Xv9x%YxKX]Y*9dV[jJu92)/de$6ITX(1l[_&2o[_&3r[_&4IAulL%,2_\"\n        \"^>JDklwI_&sFGL,[qee$u#=REms^_&h?fe$0J.FIq&U_&uxn+Mjw@8%*=vV%`dmA#@_`=-($\"\n        \"H`-n%V_&>W3XC2C-IMCW@IMD^IIM:j[IMl>FJMM.L0Ms6,,2e2bJ2SZ&g2W)>)49+[D4\"\n        \"`Mu`4aV:&5HkWA5:XoY6s[3v6k[J88l3_M:#b$j:oNZJ;pWvf;55l]>*>)s@/\"\n        \"l[PBBUxlB7Lt2Cm;@lfo)Y_&K%CL,<6Y_&IPJR*O,1LGEvQX(R:CL,0NH'oJ2=eZY]RX(j*\"\n        \"DL,RTbe$\"\n        \"SWbe$0f&44Vabe$US=eZmeKR*[pbe$[kOe?>_u-6e5ce$lJce$0*EL,oSce$8g=F.w:[_&\"\n        \"46EL,%G[_&vice$07TX(,][_&-`[_&:cde$78VwTSXee$eMvRn-#g]l&.Re$5qm3O]tee$\"\n        \"qrNR*FA_'SnDVX(c0fe$e6fe$f9fe$/\"\n        \"L@F.nv^_&iBfe$v]VX(mNfe$%mVX(qZfe$r^fe$tdfe$ugfe$7;6LG%BL_&qjA;$;;@5g^)^\"\n        \"e$0B^e$@22`a3xu7R$(Re$r]ce$tcce$ufce$\"\n        \"vice$xoce$*2de$,8de$e&w-6*SGo[1qm%laXL]ln0ixlg^=PphgXlpl5q.r#YkA#5;Ab-\"\n        \"krU_&X`v92.+_HM;K.IM=W@IM>^IIMA>FJMvI-LMXO6LMSU?LMXtmLMZ**MM4IWMMkg/NM\"\n        \":<pNMsA#OMoM5OM#5;PM<`%QM/\"\n        \"(SQM<.]QM^F^-#ooWT#8mG<-6Hg;-C;)=-?mG<-xE:@-WmG<-RHg;-SHg;-o)>G-amG<-[\"\n        \"Hg;-7kq@-9F:@-lHg;-*a`=-oHg;-2/A>-wmG<-uHg;-\"\n        \"vHg;-HxX?-,nG<-@nG<-`nG<-][Gs-`e)'MJFZ`MgPm`M%e2aMsuMaMv1jaMmC/\"\n        \"bMthJ+M%@G9#+&6;#w,w,v`UGs-B)ChL=@*0v<UGs-UxE$MnH2XMpTDXM@*jxLT/\"\n        \"G9#r.X$MmD23v\"\n        \"(Cg;-:nG<-8MU[-Yp]e$)-^e$l)Xw95R6.M>P#v,R-iP0fvmA#:10_-GQHR*B5b'8Y+)\"\n        \"2Mt71582')m930D2:6K@/;9g<,<:pWG<AYl]>F1I;@G:eV@HC*s@@h9/DSQUJDjnVPKq:;2L\"\n        \"w+TSSjx02U?UMMU@_iiU)C&)XHQBDXN2;>ZFI;DbF$F^ce/\"\n        \"wLgwnT.hJs$2h.C:GjrGq.rv[Smgj3K1pR,#s$0tnP'Y6YDOF,DX(;ZX._k7_MUmPKe$\"\n        \"RpHG)pk`cW6qADXu0]`Xv9x%Y\"\n        \"xKX]Y*9dV[^3.F.)/\"\n        \"de$6ITX(1l[_&2o[_&3r[_&*x,eZ'xd._[28Dk%ZvlgRFGL,[qee$u#=REms^_&h?fe$0J.\"\n        \"FIq&U_&uxn+Mjw@8%*=vV%`dmA#F?X7/i$6;#6-_HM9<`-Mi92,)\"\n        \"C,-g)D5H,*:#)d*l6>#-N-I21rDnA#d_`=-RGg;-SGg;-88RA-_lG<-`lG<-G+kB-6jq@-r_\"\n        \"`=-glG<-eGg;-x:)=-mGg;-oGg;-,``=-#Hg;-*Hg;-A``=-2ZGs--:3jL*<oQM9@xQM\"\n        \"NR=RM=XFRMJ_ORMPfXRMFkbRMd&RTM1/\"\n        \"[TMK4eTM^J3UMkP<UMSVEUMUcWUM1jaUMWojUMX-0VMn19VM]7BVM]>KVMEi5WMl<\"\n        \"vWMnH2XM1O;XMpTDXM9[MXMxaVXM7trXM&$&YM$<JYM\"\n        \"2H]YM-NfYM@lh[MK'X^M?YK_M,0@7v2Ik3.YRd&M[2?`M6:H`M_DZ`MsPm`MGXv`Mo])\"\n        \"aMei;aMfoDaMguMaM0&WaMo+aaMj1jaM#D/bMoOAbM']SbMrb]bMtnobMutxbMv$,cM6pS+M\"\n        \"%4Y9#nU)..-IbGM0-VHM05(.vc$X9#^EQ&MraVXMtmiXMusrXMv#&YMx/\"\n        \"8YM*a+ZM,m=ZM.#PZM`t$7vVeq@-_nG<-ma`=-[Ig;-gIg;-hIg;-lO,W-k83XC+oBHM4t1-\"\n        \"M.kpi';^K/)\"\n        \"=p,g)>#H,*Ac<#-v`F/\"\n        \"2XdaJ2SZ&g2X2YD4ZD:&54FoY6k[J88:K`M:sN$j:oNZJ;#Tk]><u)s@/\"\n        \"l[PB<CxlB7Lt2Cq`W.ho)Y_&6Uae$CpQX(?\\?Y_&xl-:2W2Z_&RTbe$SWbe$o42LG\"\n        \"aMZ_&[pbe$7%'449].:2lJce$*ILR*oSce$20EL,w:[_&ufce$vice$Hf6@0,][_&@C]_&`\"\n        \"K^_&Znee$TOU.hH0jumg9EVn%4ESos,>PpvG:Mqm>6Jrv:+AuUcENh+>?ul$-q.L=tRe$\"\n        \"7hgf(k7_MUp_Qe$JDU+`lFHJVpX),WBd:>Z/\"\n        \"5jMhDO7c`Duti_^(Qe$:1]_&7T(:2v+4GM)XlGM)UY,MN^ID*M-mA#GlG<-910_-p=\"\n        \"Yw9QSGLMn-w1MmMsu5`dmA#-Rx>-2Rx>-3Rx>-\"\n        \"6Rx>-9Rx>-:Rx>-ARx>-FRx>-GRx>-6;)=-RRx>-SRx>-orUH-```=-eHg;->F:@-?F:@-x;\"\n        \")=-GF:@-HF:@-0<)=-B<@m/Xg[%##%k5vICg;-'lq@-YnG<-BxiQ0Z@cuuWZT:#.+W'M\"\n        \"(RcGM/XlGMYn;vunH`t-77j'M/'MHM0-VHM(1;+vsH`t-_15)MwA+-vcCg;-#xX?-2kq@-#/\"\n        \"A>-qmG<-5Sx>-`W)..'F:XM*CdwL>mU:#Kkq@-rHg;-tHg;-uHg;-vHg;-wHg;-xHg;-\"\n        \"$Ig;-(Ig;-)Ig;-*Ig;-+Ig;-,Ig;--Ig;-.Ig;-0Ig;-5Ig;-LdAN-msUH-QnG<-RnG<-]\"\n        \"nG<-#Tx>-^[Gs-M6UhLxXv`Mdc2aMok;aMmuMaM*&WaMk4aEMK@H>#;LlA#)Gg;-*Gg;-\"\n        \"@_`=-/Gg;-1Gg;-4Gg;-5Gg;-7Gg;-8Gg;-9Gg;-:Gg;-x7RA-2pc`/j?b=c.S6/1d)F/\"\n        \"2RQaJ2SZ&g2TdA,3W)>)4X2YD4Y;u`4ZD:&5[MUA5_iQ>6`rmY6a%3v6eIJ88o1+m9p:F2:\"\n        \"qCbM:)t$j:nE?/\"\n        \";oNZJ;pWvf;#Tk]>RUI;@S_eV@Th*s@;:]PBHhxlB21XMC3:tiC6UpfD8hPGE9qlcE:$2)\"\n        \"FKiUPK'XslKS772LW[NJMXejfMS[/,NUnfcNVw+)OW*GDOY<(&PZECAP\"\n        \"[N_]P^a?>QeJSSSP-32UP4QMUQ=miU$lHJV1CefVqbDGWrk`cWu0]`Xv9x%Y$_9>ZJ2ruZ-\"\n        \"66;[__:EkJK-fq9`bxO?$Se$^vbe$f8ce$h>ce$$%xUm._ADXu0]`Xv9x%Y#OOAYBD<R*\"\n        \"(P[_&*2de$,8de$(XXk=<*uRnH%ci_H[LDkp0]_&JEbq;#(@F.[qee$rPVX(ms^_&h?fe$J.\"\n        \"#.6r,__&9C?L,w1=GM)UY,MHPtl&5:lA#NW=Z-n%V_&<5V_&=8V_&8Z^e$ff1@0s*#44\"\n        \"H5_e$OJ_e$j)AL,RS_e$SV_e$Xf_e$MD@qVY+)2M$bSV6qnJ88NpH59Fp`M:#b$j:oNZJ;\"\n        \"qa;,<F>XG<M(m]>J1iY?NU*s@3l%pA5(]PBT6#mB21XMC4C9/Df2VJDg;rfDE?mcED)B;I\"\n        \"xN82L2BPJM_wjfMS[/,NX3c`O3(KAPba_]PeJSSS6(QPTFqiiUsO-/\"\n        \"VtXHJV1CefVqbDGWst%)X5oDDX6x``X,_x%Y$_9>ZVVruZ^Ic`k;8H_&]cbxO>Be`k=vbe$\"\n        \"f8ce$h>ce$:m=F.\"\n        \"tcce$?Be`k[I[_&#/hCjXr<YY0Q28],Qio]/\"\n        \"mel^U3i`k&bwKG5XEf_%Mg`kp0]_&JEbq;#(@F.[qee$rPVX(ms^_&h?fe$J.#.6r,__&9C?\"\n        \"L,w1=GM)UY,MHPtl&5:lA#NW=Z-n%V_&\"\n        \"<5V_&=8V_&8Z^e$ff1@0s*#44H5_e$OJ_e$j)AL,RS_e$SV_e$Xf_e$MD@qVY+)2M$\"\n        \"bSV6qnJ88NpH59Fp`M:#b$j:oNZJ;qa;,<F>XG<M(m]>J1iY?NU*s@3l%pA5(]PBT6#\"\n        \"mB21XMC\"\n        \"4C9/Df2VJDg;rfDE?mcED)B;IxN82L2BPJM_wjfMS[/\"\n        \",NX3c`O3(KAPba_]PeJSSS6(QPTFqiiUsO-/\"\n        \"VtXHJV1CefVqbDGWst%)X5oDDX6x``X,_x%Y$_9>ZVVruZ_R(&lvQ-L,85rl&\"\n        \"4NOxke>^e$O70ipNr:5K@pIk=LxFlf^D;5g@><R*Qw]_&R$^_&$L/\"\n        \"@0Hp80M)fdi9(,H;@jx02UYICAl[#^e$(*^e$>B_Kccn1XCM6k=l>DWe$B:GR*:iDJ1OV^\"\n        \"VIF;#sILlL5K0?CX(\"\n        \"7<tW_8U*J_IgK_&;M0XC:ha+`BQK_&@hTX(C<)GM.`e&M[.%;#QnG<-RnG<-Dr6]-gA^e$\"\n        \"6T^e$<Ti-6Y+)2MtNx(<x&WG<:U9/D;_TJD_Ec`OA6')X*LADXekL]lq.2pobY#s$amw]l\"\n        \"ihnRn2t7p&5'S5'0tnP'A^w]GGN6R*E-be$F0be$6T.fqRscofd]`lgoD$jq%.EulN)kxlN)\"\n        \"kxlO/txlkTg_--JPq;Vsm+Mv67;#N/txlkTg_--JPq;Vsm+Mv67;#M)kxlA@eooiuRq;\"\n        \"=]x-6=]x-6#x-F.oPh:mm;tW_+e8@0+e8@0rXtIq'v6jqrn`:mlrL_&3I6IMVc<uLaPm`M@\"\n        \"Rm`M@Rm`MSQm`MSQm`McXe;#B.E6.$GfVMhR*0vFHd;#/>G)MnH2XMB_W0v[hG<-4nG<-\"\n        \"GB#-MRRm`MR_e;#hj](M%ZmDM&AH>#)4Z;%*=vV%rDnA#`o6]-lP^e$M7OX(RxV_&9>\"\n        \"Yw9Wx(MMkg/\"\n        \"NM&0^NMq5gNM55;PM4SiPM5YrPM6`%QMW&RTML3eTM^ojUM'i5WMp0dWMq6mWM\"\n        \":=vWM.*jxLbVv`MRYv`MRYv`MRYv`MRYv`MRYv`MRYv`M$^n;#Q[irnTh%sn>%Mq;]/\"\n        \"n+M$^n;#RbrrniJaq-1:,qrRS=qrRS=qr]/\"\n        \"n+MRYv`M%g3W#RbrrniAEU-SPaq-2=,qr^5w+M\"\n        \"%g3W#RbrrniAEU-SPaq-2=,qr^5w+M%g3W#RbrrnjJaq-2=,qr^5w+M%g3W#RbrrnjJaq-2=\"\n        \",qr^5w+M%g3W#RbrrnjJaq-2=,qr^5w+M%g3W#adX9oWW$&+Ujk.U%XX4oXm]e$(*^e$\"\n        \"J;_e$saVY,G?6,EJ`:5Ki&%#Qvk),Wl)GToC1Aucjl^V$wtQe$.aU_&/\"\n        \"dU_&h&<A+808p&8#USoe>^e$Hq?L,1X.ciN#&aF?$Se$d4g:ZC2^VIF;#sIIVuoJe_M5K.\"\n        \"X5R*DbU1gQUaJM\"\n        \"D3Se$X5Z_&Y8Z_&A.BrddZYDONPPe$b1wUm[a?>Qc2N;RbXI_&loZ_&h>ce$2O+FImMce$\"\n        \"qYce$K/>i^:-BDXu0]`Xv9x%YwB=AY'][]Y,'L>ZB2'F.p,wOf'6mr[*?28]=)NS]2dio]\"\n        \"3m.5^6,]P^5f,F.:$uRn0,=2_a1Qe$tFhw90chCj7M'/\"\n        \"`u]JJ`Ar^f`HNcofu;dlgX.GDkpi+F.OtGkXS/\"\n        \"Zk=[qee$m.S1pt#BSo.Z#5p)Q>Pp$HYlpj#:Mq&bXiql5q.r.rbxuA_lA#\"\n        \"MJU[-aSU_&/\"\n        \"dU_&JV3`Wr[%m&A_lA#'uDi-@Co34.+_HM?<`-Mn:2,)H5hJ)I>-g)JGH,*;&vG*Za_-6[:\"\n        \"Y?g>6EJM/EOJMGj0KMN7hKMT>qKMWI-LM'P6LMSU?LMT[HLM8odLM^umLM\"\n        \"f$wLMa**MMm13MM(AO%vWu%'.]Pd&M_BNMM`HWMM/OaMM'h/\"\n        \"NM)%KNMq<pNM)B#OMnG,OMnN5OMvS>OM?ZGOM_aPOM`5;PMVGVPMZ`%QM3r@QM5(SQMZ.]\"\n        \"QMY:^-#ex=G-8mG<-p]3B-\"\n        \"q]3B-r]3B-H``=-URx>-<cAN-E;)=-_wX?-7PKC-Bk&V-LHg;-X6&F-pRx>-RHg;-SHg;-\"\n        \"69RA-VHg;-JX&7.o15)Mw&'VMf,0VM$29VM]7BVMo>KVMji5WMZ%QWMw=vWMsB)XMtH2XM\"\n        \"=O;XMpTDXME[MXMxaVXMMh`XM.piXMttrXM2$&YM$<JYMcH]YM+</#M[p3<#.Gg;-7:@m/\"\n        \"Xq$8#7h60#>SGs-[i](M#N=-vcCg;-RHg;-2)-l.X1)0vCH`t-H)%#M@RE0v/UGs-?6cwL\"\n        \"Y>)3vWUGs-9-W'M4G1[MNk<^Mk%Y5vU6)=-QnG<-RnG<-OQKC-P-kB-otcW-_JL_&&PkGM)\"\n        \"UY,MuP#v,LqhP0(jnA#Rm`a-;dCe?Y+)2M68158,k(m9-tC2:3T<,<4^WG<;Gl]>@uH;@\"\n        \"A(eV@B1*s@@h9/DSQUJDjnVPK_Y:2Lw+TSSjx02U?UMMU@_iiU)C&)XHQBDXN2;>Zr/\"\n        \"55pW]&eZ9&$s$.b7p&/\"\n        \"kR5'0tnP'`Z:EON@]1pPK1ipk7_MU:kRe$$CT4olFHJVpX),WR(75p\"\n        \"jt[_&9.]_&#?)44K@ee$amBulR=&PoS?mA##Gg;-)Gg;-#$H`-?@o342C-IMA>FJMX+UKMR(\"\n        \"C0Mp2wx4k[J88&X(m9q<C2:55l]>4PH;@5YdV@6c)s@W7VPKL#:2L^<GDO'>TSSp412U\"\n        \"q=LMU:LiiU0-:>Ze^=PpA#WJi5V+qr%H,qrAi72'l(`Pp`FxFMI)Vl-[2hKc/+:fq/\"\n        \"?6iplUdi0C2^VIF;#sIP.r5K(0Q.qcQ1_ADfrl&4'K_&/\"\n        \"?^e$.QSV-ID^VIF;#sIKc1pJ*(=X(\"\n        \"ckCL,K=/:2D)/\"\n        \"fq=YXM_D3Se$2Jde$4Pde$gF?F.K@ee$1O7F.-%UHM6Q7IM[-w1MNMsu5qa;,<rjVG<4C9/\"\n        \"D5LTJDX3c`OA6')X$:ADXr`?Nq;qfi'0FixFhAKe$I9be$56%&+Mj(5g\"\n        \"<USMq,Cee$%;OR*5sW?g<#C;$]fmiq^)^e$0B^e$&FCD*3[IYG04fiqJ@On-r*Q9iaju1K=_\"\n        \"oiqaU&44RTbe$c1Mq;42&AXu0]`Xv9x%YxKX]Y*?28],Qio]/mel^>aliqAN(444Pde$\"\n        \"@cBXCTICXCLCee$)_8@0[qee$g<fe$h?fe$+rGL,Peq'8EP*44'>FR*+oBHM/\"\n        \"$;-Mljpi'<ggJ)=p,g)>#H,*QHE/\"\n        \"2RQaJ2SZ&g2X2YD4^`6#6L+pA#QUg_-L<PX(lI`e$mL`e$oR`e$\"\n        \":l:F.5wX_&0Cae$)T05/'AFJC;_TJDNHqfD=q5,EKQmcEW[NJMRRjfMS[/\"\n        \",NX3c`O?E-&Pl$GAPhs_]P]W$#QH-JJVubdfVpX),W'1EGW$:ADX$A``X&Lx%Y&qpuZrfd/\"\n        \"rb@rUms7$s$\"\n        \",I2W%HdK_&xNQ1g-kR5'0tnP'S>x]G9U=X(E-be$F0be$<=uOf;Gxl^8eRe$BamLp2>ti_\"\n        \"D3Se$='Y7n2DBJ`Tscof2o<5g<dCX(Qw]_&R$^_&CGw34-%UHM6Q7IM[-w1MgMsu5S?mA#\"\n        \"wlG<-xlG<-:mG<-;mG<-'xX?-)<)=-2;9O0We#7vd1PY##S-iL7XFRM]7BVMHn<4#=&###')\"\n        \"ChLB_W0veUGs-hrZiL3We;#4Ag;-h*`5/&ee@#(vA0M9q//1;LlA#qlG<-6j&gLCIViL\"\n        \"tAg;-rOViL*gG<-;Yr.MXS-##D/RS#)DluuACg;-.lG<-1()t-sW6iL8ecgLP;-##/\"\n        \"Gg;-J?xu-<,W'Mw*2+vbUGs-orhxLEWqSMF^$TMIp?TM5'A0#Pk@u-OQd&M$TF-veCg;-\"\n        \"XmG<-\"\n        \"[))t-L'$&M6#(.vqTGs-muo(M^=KVM3Y$/\"\n        \"v,hG<-lmG<-hHg;-2NuG-mHg;-sZGs-V`w#M<niXMusrXMv#&YMw)/\"\n        \"YM'18YMRBB5#e^Xv-%FQ&M)ZxYM*a+ZM=g4ZM2m=ZM3sFZM],d2v\"\n        \"W`Xv-E25)MX8v2v+Cg;-v>K$.;9j'M9A([MuI1[MAM:[MHk<^Mu(X^M)t$7v:Mx>-Oq)M-\"\n        \"Sv,D-^[Gs-xPc)Mvc2aM.pDaM)vMaM$&WaMj1jaM&9saMl=&bM-(#GMtodX.=5G>#*lG<-\"\n        \"bON>.%V3`W808p&A_lA#'uDi-@Co34.+_HM?<`-Mn:2,)H5hJ)I>-g)JGH,*;&vG*Za_-6[:\"\n        \"Y?g>6EJM/EOJMGj0KMN7hKMT>qKMWI-LM'P6LMSU?LMT[HLM8odLM^umLMf$wLMa**MM\"\n        \"m13MM(AO%vWu%'.]Pd&M_BNMM`HWMM/OaMM'h/\"\n        \"NM)%KNMq<pNM)B#OMnG,OMnN5OMvS>OM?ZGOM_aPOM`5;PMVGVPMZ`%QM3r@QM5(SQMZ.]\"\n        \"QMY:^-#ex=G-8mG<-p]3B-q]3B-r]3B-\"\n        \"H``=-URx>-<cAN-E;)=-_wX?-7PKC-Bk&V-LHg;-X6&F-pRx>-RHg;-SHg;-69RA-VHg;-\"\n        \"JX&7.o15)Mw&'VMf,0VM$29VM]7BVMo>KVMji5WMZ%QWMw=vWMsB)XMtH2XM=O;XMpTDXM\"\n        \"E[MXMxaVXMMh`XM.piXMttrXM2$&YM$<JYMcH]YM+</\"\n        \"#MwR6##EmVW#?KY)M^Z?##eow@#,'HtLYF?##TZGs-g5UhLf>)3v`Cg;-2Ig;-4O,W-+O>R*\"\n        \"QSGLM]6<MMj<EMMnG,OM'ZGOM\"\n        \"x`POM0+J6MU1v+D;_TJD7_5,Ei&%#QpX),Wst%)X0_ADX&%q>$GFD'SVO/\"\n        \"2'AWR&GBKJ;$xOE&,JlL5K8eRe$4YH'ShqNwT)f#AO,?3?$+l2`a)+@/\"\n        \"VXoPe$H,Y._pe;,WLJPe$47Exk\"\n        \"2>ti_.FRe$:1]_&ANYk=@iT.hU8`lgR/\"\n        \"%2hck->m].DSoU7w1q)lkA#)rcW-`,^e$493XC>6EJMKx90M%iji0(jnA#[H)a-<gCe?b_.\"\n        \"NM,0^NM-6gNM3ZGOM4aPOM;5;PM@SiPMAYrPM\"\n        \"B`%QM@F+RMSL4RMj&RTM_3eTMwh5WMj0dWM?7mWM@=vWM)h`XMHniXML*jxL[RQ##'>q`0]\"\n        \"b#7vhLY##')ChLnBdwL__Z##hrZiL9We;#4Ag;-bIg;-5A:@-4N:h.nUu>#g3]Y-o0*@0\"\n        \"&QqPMY:^-#KX`=-7Hg;-e.A>-cmG<--/A>-(mWN0.>cuuGRc##w16&M(RcGM/\"\n        \"XlGMXem##F4)=-3s%'.gqKHM0-VHMK:SqLQgd##T=G)M*=M+vwBg;-EHg;-FHg;-d@xu-?\"\n        \"fHiL5N=-v\"\n        \"HUGs-o[u)MRP<UMYVEUM-gb-vMH`t-)BuwL,/\"\n        \":.vZCg;-`ZGs-?;@#Mln>WMn$QWMk6mWM4D)XMqZMXMDkj0vJMx>-tHg;-uHg;-vHg;-&\"\n        \"iDE-**)t-w@<jLo^,2vDCg;-)Ig;-<a`=-\"\n        \"1nG<-2nG<-5*)t-tL0%Mv2m2v`UGs-jYn#M15lZMGE23vVhG<-txdT-@<)=-5Ig;-rA7I-M[\"\n        \"Gs-+U:xLvpp_MR.6`MT3?`M/B[7vuZ`=-,Tx>-(0A>-#b`=-hIg;-%]]F-kIg;-x<)=-\"\n        \"@Z=Z--;v92w1=GM/\"\n        \"XlGM]t2vu%[(?#LW=Z-]G7kX,r9-M4kpi'@g0j(36rA#G_`=-H_`=-I_`=-:YGs-6?<jLpa@\"\n        \".M6R#v,/3Z>-GJS8/N-I21TFhM1WZE/2'&cJ2SZ&g2TdA,38x?)4\"\n        \"^B]D4f`u`4aV:&5m,YA5WN0^55%9_AQ2S1g]iQ>6`rmY6/\"\n        \"=4v6'=K88)VJ59qCbM:)t$j:nE?/\"\n        \";nL^J;vjvf;?#=,<_1YG<`_m]>VUiY?Z$+s@3l%pA5(]PBZH#mB34O2CDerKG8*Y_&\"\n        \"pkl'8qnl'8rql'8HMJR*Uh;F.<,5_]EvQX(_Q4@07a^q;BF2ktLBbe$XT@XCpa<F.RTbe$\"\n        \"SWbe$6Fu-6Vabe$H4:qrdIsRnu?,&PfhFAP$T`]P]W$#Qo?C>QjZVSSZ?RPTwVkiUsO-/V\"\n        \"tXHJV=hefVpX),WE6FGWx'acWMZ')X.ZGDXt.``X2qx%Y$_9>Zc%suZ-66;[T/\"\n        \"kW%E>SY,2rqr$0nIp&1tJ_&/\"\n        \"?^e$<YNX(Cv>ulU12pJ5VLe$-a1`aYH:&P5[Re$^vbe$f8ce$h>ce$\"\n        \"h3>e?C&DfUZ[5L,6:5]XnRZJV[xPe$,#)MTqnVGWkJKe$jL`q;/\"\n        \"Ade$<VBXC)#T.hD%CJ`NacofIW(5gX+:W%1w]_&R$^_&]=K_AW`Re?oBDXCrPVX(VEjw9'\"\n        \"KU_&uxn+M_IdY#/FZ;%\"\n        \"3krS&M-mA#NW=Z-mS^e$ZQ@L,H5_e$dm@L,=j?qVIvA0Mir//\"\n        \"1k0qA#h-A>-i-A>-H,-h-mn^3OeqINM%*TNM&0^NME6gNMKZGOM:aPOMDGVPMEM`\"\n        \"PMFSiPMGYrPMKr@QM@xIQMFF+RM\"\n        \"`L4RMoQhSM^&RTMJ.[TMtE*UMwusUMf&QWM++ZWMv0dWM^7mWM`C)XM/\"\n        \"h`XManiXMf0sxL$.w##K4/X#ds8.v`Cg;-^Hg;-fHg;-jZGs-/\"\n        \"vo(M0niXMusrXMv#&YMI3B1vcZ`=-(nG<-\"\n        \"*Ig;-,Ig;-*2d%.G25)MH)>$M3mv##:nG<-JQKC-#Tx>-[Ig;-r<)=-mnG<-hIg;-J:RA-\"\n        \"rnG<-:6]Y-Zs]e$qW>qV*f'-MkaTM'A_lA#7lG<-<lG<-=lG<-8Gg;-fvX?-siq@-HGg;-\"\n        \"OGg;-j-A>-RGg;-SGg;-XGg;-NPdh-/G@qV]@VMMqg/\"\n        \"NMN$KNMF<pNM#B#OMoM5OMqYGOMFaPOMM5;PMJGVPMN`%QM3r@QM5(SQMT.]QM2:oQM4F+\"\n        \"RMfL4RMgR=RMEeXRMDQhSMx2eTM\"\n        \"2K3UM_P<UMSVEUMXusUM3.0VMb19VMeh5WM6%QWMF=vWMsB)XMtH2XM1O;XMqZMXMsg`\"\n        \"XM5oiXM6urXM,$&YM$<JYMT6&#Mb6E?#aYZC#Lk@NM%AMPM+f.QMO>wTMebgvLUw2$#OKn*.\"\n        \"dFbGM0-VHMWojUM-*%7vdtL?#0rcW-xu^e$e4`e$#r`e$*1ae$i[FG)Sd6,EM%72Li&%#Qk]\"\n        \"SSSrFhiUpX),W0-:>Z3$Ap&l^,%.kkBHM5'MHMlE2/#Qo:$#EHg;-FHg;-Za`=-ICg;-\"\n        \"VG6t.bwC$#dj]e$;T*eZQ#u?0_e=X(LgY_&sj<F.afFL,K@ee$U$HD*;Ino%e8Ke$snff(\"\n        \"GVuoJP:eMLu7D_&3C$#,Js$2hipt1q'dUiqqA9m'P5$QpPWAYG+8Le$E-be$F0be$$97_A\"\n        \"4Jf+Mx3=/(2Tbe$:UTX(/\"\n        \">Ze$2RoQajZ1,)@5)d*ZD:&5[MUA5a%3v6pWvf;3:tiC:$2)FW*GDO^a?>Qrk`cW'$6;[\"\n        \"19kM(VNoM('oPm0dLf+M-Sj$#k4/X#tugK-bUGs-5+W'M/'MHM\"\n        \"h92/\"\n        \"#GfG<-EHg;-HZGs-@rZiLVvHTMa0B1vX@gl^j-ZM_V+=R*2Jde$4Pde$gF?F.K@ee$1O7F.-\"\n        \"%UHM6Q7IM[-w1MNMsu5qa;,<rjVG<4C9/D5LTJDX3c`OA6')X$:ADX;&?0)`SbCj\"\n        \"D'02'A^w]G8.E_&E-be$F0be$+FE3k)b?lfd]`lgs]HjqO#MG);qfi'=K@&GhAKe$I9be$\"\n        \"56%&+Ov:5gr`Ke$LCee$%;OR*P_KG)AURS%^-4g)j%P]l4-]5'wFg;-6w]7.5LpSMF^$TM\"\n        \"s)],vv)A>-iW)..usWZM/\"\n        \"#5$M-`8%#4Ig;-#lq@-YtA,M.Z@IMVgS@#-E5g)W94po43=D<GqlA#CsA,MEjS@#$$)t-m:\"\n        \"OGM(RcGM(L>gLALihLh$OW#5jvEM0-VHM$:2/#b4)=-EHg;-\"\n        \"TsA,MfLihL;Qx>-nx9W.59OA#n1RA-hCg;-#i`e.W(1/\"\n        \"#QfG<-EHg;-HZGs-;S-iLVvHTM7nMxL(TrhL7B:@-U6)=-qC3#.KQ$iLQpA,MBnS%#qw3d*\"\n        \"W4RA-kD40M4=l5v%9=&#7f=(.\"\n        \"xFbGMV*WvuW6)=-/Gg;->L`t-Ni](MS^_sLdL>&#8@t$M2/\"\n        \":.vUCg;-^Hg;-fHg;-jZGs-<BuwL&+?wL&M>&#AT:xLFRE0v(UGs-7[ovLCbE4#5Ag;-\"\n        \"jPKC-/Ig;->I]'.4e)'MFG1[M\"\n        \"Nk<^Mr$Y5vg6)=-QnG<-RnG<-]iDE-WDdD-o7&F-r<)=-V-kB-(ucW-kb=R*w1=GM/\"\n        \"XlGM2hu,M-bTM'A_lA#6Gg;-Z-A>-HGg;-d-A>->Pdh-ul?qVJ&K0Mj%KJ1i2+j1uIr]\"\n        \"5etpA#\"\n        \"7k3f-Z5IR*%9IR*DK$44EN$449i:F.:l:F.D4;F.E7;F.F:;F.G=;F.?2JR*@5JR*_v,:2`#\"\n        \"-:2VxJR*I/=eZp*#@Ko^<F.VV=eZ*nDL,uXSX(]%`q;^(`q;)FLR*``gw9acgw9A&,F.\"\n        \"L]h@k.dE;-L3$&+5_5,E]W$#QpX),WIOgZ-JK-fq'$[xOb01Z-=vbe$f8ce$h>ce$$%\"\n        \"xUm4qADXu0]`Xv9x%Y#OOAYBD<R*(P[_&*2de$,8de$D[wKGxf#J_UL5L,..-eZ#,vF`^=P`\"\n        \"k\"\n        \"MvN]l$UixlfTx4ps,>Ppn#Ylpj#:MqL.s.r(`bxu;LlA#$Gg;-rOdh-v->R*,r9-Mxjpi'<\"\n        \"ggJ)=p,g)>#H,*Ac<#-g-Y>-#3R8/O6eM1QHE/2kDbJ2SZ&g2X2YD4]Vq]5k0qA#OPdh-\"\n        \"L<PX(KSk'8BE$44xaPX(mL`e$oR`e$E*,:2F-,:2Gb3@0Jk3@00hX_&3qX_&S04@00Cae$\"\n        \"2Iae$eV%44fY%44BmQX(9_ae$oP-:2..u-6^iRX(RTbe$SWbe$1_W3kaMZ_&[pbe$3&6@0\"\n        \"BF'44r+[_&s.[_&0*EL,oSce$qYce$413LG543LG+(TX(vice$T'(44*PI_&#@=5&#Ivr-G:\"\n        \"`e$%x`e$+4ae$OKbe$e/Pe$&Rkl&S?[S.e>^e$%gd=cOu1pJr`Ke$M_5_AB1g+M8t1W.\"\n        \"2Tbe$<?DD*'o_MUkOQe$=$IrZnRZJVc7Qe$4lW(W15XM_5[Re$.P1ci2DBJ`NacofDO=\"\n        \"5g5NCX(Qw]_&R$^_&OTbq;P3jw9nsT_&uxn+MWw@8%*=vV%.&oA#AGg;-Rv%'.'@^+M4EWY5\"\n        \"xVnA#]H)a-^cAL,,sAL,-vAL,32BL,45BL,;JBL,@YBL,A]BL,6HQX(R_;F.Sb;F.]/\"\n        \"9RE`=KR*e5ce$>l.:2?o.:2xbSX(G1/:2H4/:2.+BX(j6q=YKp.t.?DvRnNe;;$Nkc8/\"\n        \"^)^e$\"\n        \".<^e$/?^e$0B^e$vs<F.sK1ip9O`MU:kRe$$CT4olFHJVpX),WADf8/\"\n        \"jt[_&9.]_&#?)44K@ee$]mBul0ZBSoS?mA##Gg;-)Gg;-#$H`-?@o342C-IMA>FJMX+UKMR(\"\n        \"C0Mp2wx4k[J88\"\n        \"&X(m9q<C2:55l]>4PH;@5YdV@6c)s@W7VPKL#:2L^<GDO'>TSSp412Uq=LMU:LiiU0-:>ZQ<\"\n        \"rT/d&%&+CR](WhAKe$U5ED*V4l%lMp.T/Fa^_&b-fe$&`>L,J)^KMM.L0MIn)/:/GdV@\"\n        \"7Lt2C`qQP/nWae$eqCL,cSZ_&-wDL,t+I_&23F]FP'Dp/LB>e?k_xCWkBnl/\"\n        \"9ejJ)`0arn%-'8o%QEp/Em7L,J&K0MUn)/:/GdV@1(=2C:67p/\"\n        \"BTl)+xtWiKuJ%#QwOLMU2L*,WQ0`50\"\n        \"`MC_&u*sr$0tnP'W*GDO]FlDk+;:20f`L_&>6EJMeg/\"\n        \"NM#5;PM*`%QM6QYO-^g:1.R*dTMDd:1.j`4WMr<vWM>2r1.'xhxLPotjLgq,Q#RDluu[L,W-\"\n        \"cJU_&/dU_&6#V_&5Rae$Bs>ul\"\n        \"WPx]G?$Se$E-be$F0be$,^1`aT.;5K?_?Q0,gfCjVejfMTeJGNpsGDO:5*&Pds?>Qc2N;R.\"\n        \"P,F.loZ_&h>ce$YXf=c(LADXu0]`Xv9x%YwB=AYdg^]Y.?QV[LKqr[*?28]7mMS]2dio]\"\n        \"3m.5^6,]P^vTVw9,J1ci1;'/\"\n        \"`lE`f`keGDkF+AX(iT<REk`NR*5v)44[qee$x1OR*ms^_&h?fe$EP*44r,__&9C?L,w1=GM)\"\n        \"UY,MsPtl&5:lA#@`xb-n%V_&^ov922C-IMCW@IMD^IIM\"\n        \"9a@.MCQ#v,sQY>-/WR8/O6eM1QHE/\"\n        \"2qVbJ2SZ&g2W)>)4EO[D4`Mu`4aV:&5T9XA5K0T>6l@nY6#o3v6qnJ88gcI59_caM:)t$j:\"\n        \"oNZJ;pWvf;AYl]>DuhY?HC*s@3l%pA5(]PBN$#mB\"\n        \"21XMC3:tiCa)rfD>$QGEJOpcE@62)FV`B;I(b82L8TPJMe3kfMS[/\"\n        \",NUnfcN+K-)OW*GDO^J.&PaWCAPtA`]P^a?>QQhUSSNqQPT_djiUsO-/\"\n        \"VtXHJV7UefVqbDGWrk`cWIZ^`X&Lx%Y\"\n        \"$_9>ZPDruZ-66;[f#Am0`MC_&&(WV$#/qi0d`U_&/\"\n        \"dU_&6#V_&BFIoRN#&aF5[Re$1rtOfC2^VIF;#sIIVuoJ3wN5K+O5R*hGZ+VOINJM_wjfMYn/\"\n        \",NZwJGN(NZDO]a2L,Fjfw9^vbe$\"\n        \"/Ch:ZpxooSnxOPTrk`cW9KEm0Scce$ufce$vice$^5o'8(P[_&.c[_&)/\"\n        \"de$6ITX(1l[_&2o[_&3r[_&qgCrd.w$m^B-Se$)/5fh/\"\n        \")FM_9Nwi_96K_&9.]_&4Pde$;_AulcRGDk=sOe$\"\n        \"d&VX(]89_]MNbq;[qee$Q6jw9(iGL,sSVX(h?fe$&>OR*r,__&9C?L,w1=GM)UY,M;Qtl&5:\"\n        \"lA#_j3f-4,(@0.+_HM?<`-MO:2,)H5hJ)I>-g)JGH,*:#)d*wTqA#(]3B-5OKC-HGg;-\"\n        \"OGg;-pQx>-RGg;-SGg;-JOKC-_lG<-`lG<-YBdD-H+kB-G'*q-u]r-6B+='o]@VMM/\"\n        \"OaMM'h/\"\n        \"NMs$KNM.<pNM)B#OMoM5OMpS>OM9ZGOMXaPOMA5;PMPGVPM0`%QM3r@QM5(SQMZ.]QM\"\n        \"^F^-#73qB#8mG<-p]3B-q]3B-YwX?-H``=-URx>-6>aM-E;)=-_wX?-cRx>-LCdD-d``=-\"\n        \"RHg;-SHg;-*F:@-VHg;-8A-5.tOc)Mk&'VM-.0VM$29VM]7BVMc>KVM^i5WMZ%QWMk=vWM\"\n        \"sB)XMtH2XM=O;XMpTDXME[MXMxaVXMMh`XMxoiXMCtrXM2$&YM$<JYMcH]YM+</\"\n        \"#MKP`'#+Y;W#LLpSMF^$TMrv70#BF`t-U)ChLumMxLi?`'#1Ig;-2Ig;-4Ig;-hSx>-LIg;-\"\n        \"2)uZ-\"\n        \"Yp]e$)-^e$$&1F.Z4DMMqYGOMr`POM^F^-#i8$C#:mG<-5Hg;-C;)=-&kq@-cmG<-pHg;-5/\"\n        \"A>-tTGs-.;OGM(RcGM.wCHM/'MHM0-VHM05(.v98h'#(]u)M=@*0vZCg;--1r1.v=1XM\"\n        \"B_W0vb6)=-4nG<-9nG<-#lq@-M[Gs-hj](M%ZmDM&AH>#)4Z;%*=vV%rDnA#`o6]-lP^e$\"\n        \"M7OX(RxV_&9>Yw9Wx(MMkg/NM&0^NMq5gNM55;PM4SiPM5YrPM6`%QMW&RTML3eTM^ojUM\"\n        \"'i5WMp0dWMq6mWM:=vWM.*jxLPC$LMiIr'#Aq;j1^ad5._Q2uL-_r'#6h#V#3MX,MpO#v,\"\n        \"eIJ88#Tk]>*>)s@34O2C2W&F.7Xae$Y]RX(cSZ_&klZ_&lJce$&oSX((JI_&'OSS%+K=02\"\n        \"hF;ula&12'A^w]G'PD_&E-be$F0be$.GH'SvTq=YY4=R*/Ade$h/\"\n        \"w-64Pde$#?)44K@ee$+o>L,-%UHM6Q7IM]3*2McW=D<5LTJDX3c`O0_ADXVdaJ22nsooT/\"\n        \"d?K.kR5'e.l?KMUg+M\"\n        \"GT.(#wswJ2:/\"\n        \"NR*I4Re$%1<;$(+?v$*=vV%7d+g24S(g2q.2pod5LM'S>x]G;7E_&E-be$F0be$]%-g2A(\"\n        \"2g2jQ,[.59OA#32RA-hCg;-gPgc.s(1/#QfG<-EHg;-HZGs-;S-iLVvHTM\"\n        \"b3B1vfhG<-NnG<-ICg;-+E:@-N%a..[M#lL,qA,Ma]-lLWi3W#T<dD-OL,W-dj]e$#=E)4$=\"\n        \"E)4&IW)4Jkf?KM.o1KLcBe?2;Ci^R-f)4wi2ipanci_4DBJ`HNcofjo`lgj#:MqA_lA#\"\n        \"0Gg;-6Gg;-%X=Z->u_e$qX`e$r[`e$4Oae$5Rae$'W5@0#A[_&rVPe$0@<;$?qFA4^)^e$:@\"\n        \"K1p4*Jp&&.Re$/?^e$B:GR*:iDJ1OV^VIF;#sILlL5K0?CX(5LAul15XM_C0Se$%+aq;\"\n        \"='Y7n8VBJ`Tscof2o<5g<dCX(Qw]_&R$^_&CGw34s$e-63I6IM6-#O-nK8$Me+p+M60+D#\"\n        \"WY3B-.?Y0.GD3RM_usUMV?Y0..S1xLdh?lLiX;R#7/xfL[j?lL<kG<-0ucW-kJU_&rSq92\"\n        \"SX0JL]e3/\"\n        \"MXejfMrk`cW&[8a4Scce$ufce$vice$xoce$*2de$,8de$gJif(7TEJ`ZFL]lhthxlg^=\"\n        \"PphgXlpl5q.r#YkA#.Gg;-1Gg;-7Gg;-8Gg;-QGg;-RGg;-SGg;-XGg;-V$H`-\"\n        \"F[W_&lI`e$mL`e$oR`e$45BL,5wX_&0Cae$2Iae$GJJR*<6Y_&9_ae$QQbe$RTbe$SWbe$\"\n        \"Zmbe$[pbe$nPce$oSce$qYce$Z,o'8j(kA#k.tA#>M9o/hoUvu7%w(#1YGs-J?gkLKWqSM\"\n        \"F^$TM'gb-vo6)=-a/\"\n        \"A>-On(T..ee@#4dq@-XL,W-dj]e$8Vdi0ID^VIF;#sIHNcof^J`lgc1e^5>Yd@be^sr$\"\n        \"0tnP'A^w]Gv_<X(E-be$F0be$R<.XC_B)DNY4=R*(I9onr'ADXu0]`X\"\n        \"v9x%YxKX]Y*?28],Qio].dIP^5-N]lbbhxlg^=PphgXlpl5q.r#YkA#.Gg;-S&uZ-n%V_&;\"\n        \"2V_&=8V_&8Z^e$QP_e$RS_e$SV_e$Xf_e$)]2@0f[W_&lI`e$mL`e$oR`e$/@ae$0Cae$\"\n        \"2Iae$6Uae$9_ae$QQbe$RTbe$SWbe$JD_q;aMZ_&[pbe$nPce$oSce$qYce$dljA#ersA#8)\"\n        \"Xn/c>cuup6<)#(Gg;-2YGs-/36&Mt92/#4Ag;-EHg;-FHg;-Mt%'.cwYTMwA+-v)<xu-\"\n        \"qn:*MRP<UMT]NUMraVXMtmiXMusrXMv#&YMx/8YM*a+ZM,m=ZM/\"\n        \")YZMl+c5vwW3B-MIg;-ma`=-[Ig;-gIg;-hIg;-8#Y?-kIg;-xBDX-e;^e$;SEX(.+_HM<\"\n        \"Q7IM=W@IM>^IIMQI-LM\"\n        \"RO6LMSU?LMXtmLM`HWMMl;pNMmA#OMoM5OM/\"\n        \"(SQM0.]QM2:oQM6R=RM9eXRMQJ3UMcZHp-:8Z_&p72LGaMZ_&[pbe$nPce$oSce$qYce$\"\n        \"ufce$vice$$pPe$O5CYGx78;6%-be$F0be$\"\n        \"a_O1p]wjfMk,UiqrqaZ6b@rUmhgsr$*=vV%tk<Z6UNQ1g99S5'0tnP'S>x]G9U=X(E-be$\"\n        \"F0be$<=uOf;Gxl^8eRe$BamLp2>ti_D3Se$='Y7n2DBJ`Tscof2o<5g<dCX(Qw]_&R$^_&\"\n        \"CGw34-%UHM6Q7IM[-w1MgMsu5S?mA#wlG<-xlG<-:mG<-;mG<-'xX?-)<)=-(6)=-[@]+/\"\n        \"DIW)#dj]e$8Vdi0ID^VIF;#sIHNcof^J`lgk$>98E>SY,mvsr$0nIp&1tJ_&/?^e$<YNX(\"\n        \":V<e?[LSlJGN6R*-a1`aYH:&P5[Re$^vbe$f8ce$h>ce$1o8`W(x$jUB-Se$6:5]XnRZJV[\"\n        \"xPe$,#)MTqnVGWkJKe$jL`q;/Ade$<VBXC)#T.hD%CJ`NacofKd:5gF,DX(Qw]_&R$^_&\"\n        \"]=K_AW`Re?oBDXCrPVX(VEjw9'KU_&uxn+M_IdY#/\"\n        \"FZ;%3krS&M-mA#NW=Z-mS^e$ZQ@L,H5_e$dm@L,=j?qVIvA0Mir//\"\n        \"1k0qA#h-A>-i-A>-H,-h-mn^3OeqINM%*TNM&0^NME6gNM\"\n        \"KZGOM:aPOMDGVPMEM`PMFSiPMGYrPMKr@QM@xIQMFF+RM`L4RMoQhSM^&RTMJ.[TMtE*\"\n        \"UMwusUMf&QWM++ZWMv0dWM^7mWM`C)XM/h`XManiXMf0sxL_61*#K4/X#Ht8.v`Cg;-^Hg;-\"\n        \"fHg;-jZGs-/\"\n        \"vo(M0niXMusrXMv#&YMI3B1vcZ`=-(nG<-*Ig;-,Ig;-*2d%.G25)MH)>$Mnu0*#:nG<-\"\n        \"JQKC-#Tx>-[Ig;-r<)=-mnG<-hIg;-J:RA-rnG<-:6]Y-Zs]e$qW>qV*f'-M\"\n        \"kaTM'A_lA#7lG<-<lG<-=lG<-8Gg;-fvX?-siq@-HGg;-OGg;-j-A>-RGg;-SGg;-XGg;-\"\n        \"NPdh-/G@qV]@VMMqg/NMN$KNMF<pNM#B#OMoM5OMqYGOMFaPOMM5;PMJGVPMN`%QM3r@QM\"\n        \"5(SQMT.]QM2:oQM4F+RMfL4RMgR=RMEeXRMDQhSMx2eTM2K3UM_P<UMSVEUMXusUM3.\"\n        \"0VMb19VMeh5WM6%QWMF=vWMsB)XMtH2XM1O;XMqZMXMsg`XM5oiXM6urXM,$&YM$<JYMT6&#\"\n        \"M\"\n        \"@*C*#+s%'.HGbGM.wCHM/\"\n        \"'MHM0-VHMU]3uLw<C*#QxiW#5,lWMdi=(.6,PwLS1C*#(WA(M@#PZM9A([MNk<^M&(X^\"\n        \"MTW0(M^+C*#>r6]-Yp]e$)-^e$x@Ik=+l0-M892,)Ac<#-X?iP0\"\n        \"S6.m0fvmA#alG<-w_`=-plG<-'``=-/\"\n        \";)=-4;)=-5;)=-6;)=-JCdD-SmG<-p.A>-kmG<-pmG<-9xX?-x;)=-xBg;-YEEU-ZNaq-,w:\"\n        \"'ol,&AOsV[Q95?f@ktD^S%)lkA#AGg;-eGg;-\"\n        \"#Hg;-,ZGs-t4UhLUXFRMM2eTMi7BVMkh5WMr<vWMpTDXM.*jxLns&nLek,Q#G/\"\n        \"xfLYu&nLCjG<-0ucW-kJU_&5Rae$Bs>ul[Vf]Gh)@m9%-be$F0be$,^1`aZ@;5K_iNm9,\"\n        \"gfCjVejfM\"\n        \"TeJGNpsGDO:5*&Pds?>Qc2N;R.P,F.loZ_&h>ce$YXf=c(LADXu0]`Xv9x%YwB=AYdg^]Y.?\"\n        \"QV[LKqr[*?28]7mMS]2dio]3m.5^6,]P^vTVw9,J1ci1;'/`lE`f`keGDkF+AX(iT<RE\"\n        \"k`NR*5v)44[qee$x1OR*ms^_&h?fe$EP*44r,__&9C?L,w1=GM)UY,MsPtl&5:lA#@`xb-n%\"\n        \"V_&^ov922C-IMCW@IMD^IIM9a@.MCQ#v,sQY>-/WR8/O6eM1QHE/2qVbJ2SZ&g2W)>)4\"\n        \"EO[D4`Mu`4aV:&5T9XA5K0T>6l@nY6#o3v6qnJ88gcI59_caM:)t$j:oNZJ;pWvf;AYl]>\"\n        \"DuhY?HC*s@3l%pA5(]PBN$#mB21XMC3:tiCa)rfD>$QGEJOpcE@62)FV`B;I(b82L8TPJM\"\n        \"e3kfMS[/,NUnfcN+K-)OW*GDO^J.&PaWCAPtA`]P^a?>QQhUSSNqQPT_djiUsO-/\"\n        \"VtXHJV7UefVqbDGWrk`cWIZ^`X&Lx%Y$_9>ZPDruZ-66;[-*V2:`MC_&C*XV$@50/:d`U_&/\"\n        \"dU_&\"\n        \"6#V_&BFIoRN#&aF5[Re$1rtOfC2^VIF;#sIIVuoJ3wN5K+O5R*hGZ+VOINJM_wjfMYn/\"\n        \",NZwJGN(NZDO]a2L,Fjfw9^vbe$/Ch:ZpxooSnxOPTrk`cWVQZ2:Scce$ufce$vice$^5o'8\"\n        \"(P[_&.c[_&)/de$6ITX(1l[_&2o[_&3r[_&qgCrd.w$m^B-Se$)/5fh/\"\n        \")FM_9Nwi_96K_&9.]_&4Pde$;_AulcRGDk=sOe$d&VX(]89_]MNbq;[qee$Q6jw9(iGL,\"\n        \"sSVX(h?fe$&>OR*\"\n        \"r,__&9C?L,w1=GM)UY,M;Qtl&5:lA#_j3f-4,(@0.+_HM?<`-MO:2,)H5hJ)I>-g)JGH,*:#\"\n        \")d*wTqA#(]3B-5OKC-HGg;-OGg;-pQx>-RGg;-SGg;-JOKC-_lG<-`lG<-YBdD-H+kB-\"\n        \"G'*q-u]r-6B+='o]@VMM/OaMM'h/\"\n        \"NMs$KNM.<pNM)B#OMoM5OMpS>OM9ZGOMXaPOMA5;PMPGVPM0`%QM3r@QM5(SQMZ.]QM^F^-#\"\n        \"T7#F#8mG<-p]3B-q]3B-YwX?-H``=-URx>-6>aM-\"\n        \"E;)=-_wX?-cRx>-LCdD-d``=-RHg;-SHg;-*F:@-VHg;-8A-5.tOc)Mk&'VM-.0VM$29VM]\"\n        \"7BVMc>KVM^i5WMZ%QWMk=vWMsB)XMtH2XM=O;XMpTDXME[MXMxaVXMMh`XMxoiXMCtrXM\"\n        \"2$&YM$<JYMcH]YM+</\"\n        \"#MiTh*#lW;W#PYbgLhCh*#1YGs-9.wiLQWqSMF^$TMs)],vlH`t-Ao:*M/)YZM/\"\n        \"#5$MHDh*#4Ig;-#lq@-KIg;-,6]Y-gA^e$6T^e$%)1F.oWOOM5L4RMXusUM\"\n        \".[2xLEHq*#&Gg;-(Gg;-3/\"\n        \"&F-FqN+.3*AnLUEg;-Dq@u-MQ,lLQWqSMF^$TMWsN+.n`;^Md'X^M?Dk<#f-JnLHv2*M-\"\n        \"klgLGU$+#Ck&kLKWqSMF^$TMrv70#E4)=-c.A>-f@EU-a+e2M\"\n        \"tkw&M#<TnLcR[V#vJuuu)=vV%f:^J;9(fof:?]5'wFg;-Dq@u-Ew8kLQWqSMF^$TMs)],\"\n        \"vP6)=-(4>o.).(3vl$)t-O]u)M[J;3v]Cg;-@<)=-1qj#.R9j'MW'X^MR-b^Mo(NEM3bTM'\"\n        \"6TgJ)]Vq]5YQmA#8p6]-W9X_&x<X_&:0Y_&;3Y_&'W5@0)xSX((oAX(fX0kX#5dc;_\"\n        \"CxFMoS&7.l:9sL-^6+#FHg;-Za`=-ICg;-RU)2/\"\n        \"4T>+#]j]e$0B^e$w0GG)OV^VIF;#sIIVuoJ\"\n        \"jai,<GB-fq.XscW>wRe$tcce$ufce$vice$xoce$*2de$,8de$_E(44aN^_&[qee$g<fe$h?\"\n        \"fe$kE]e$+oBHM.t1-M(kpi';^K/)=p,g)>#H,*QHE/2RQaJ2SZ&g2X2YD4ZD:&5.4oY6\"\n        \"rE_M:m<$j:oNZJ;/l[PB0uwlB21XMC6UpfD9qlcEQINJMRRjfMS[/\"\n        \",NW*GDOMuEAPba_]PnFHJVoOdfVqbDGWwm<wpd`<wpw-%#MV#I+#8<:R#RGbGM0-VHM^UG*\"\n        \"vOH`t-,5UhLEWqSM\"\n        \"F^$TMFWUsLIhH+#NZGs-l[u)M;N=-vhCg;-RHg;-THg;-rHg;-tHg;-uHg;-vHg;-xHg;-*\"\n        \"Ig;-,Ig;-*2d%.=`K%M44k^MZ,6`Mn2?`MguMaMh%WaMj1jaM98saMk4aEMcNtl&0tnP'\"\n        \"/(lA#7lG<-<lG<-=lG<-8Gg;-QGg;-RGg;-SGg;-XGg;-`Gg;-lGg;-mGg;-oGg;-/\"\n        \"Hg;-0Hg;-2Hg;-6Hg;-9Hg;-a&^GMaKl(N_Ec`Or6GAPba_]PnFHJVoOdfVqbDGWu0]`\"\n        \"Xv9x%Y\"\n        \"&qpuZ)/_^>E>SY,+^tr$0nIp&1tJ_&/\"\n        \"?^e$<YNX(Cv>ulU12pJ5VLe$-a1`aYH:&P5[Re$^vbe$f8ce$h>ce$1o8`W(x$jUB-Se$6:\"\n        \"5]XnRZJV[xPe$,#)MTqnVGWkJKe$jL`q;/Ade$\"\n        \"<VBXC)#T.hD%CJ`NacofKd:5gF,DX(Qw]_&R$^_&]=K_AW`Re?oBDXCrPVX(VEjw9'KU_&\"\n        \"uxn+M_IdY#/FZ;%3krS&M-mA#NW=Z-mS^e$ZQ@L,H5_e$dm@L,=j?qVIvA0Mir//1k0qA#\"\n        \"h-A>-i-A>-H,-h-mn^3OeqINM%*TNM&0^NME6gNMKZGOM:aPOMDGVPMEM`\"\n        \"PMFSiPMGYrPMKr@QM@xIQMFF+RM`L4RMoQhSM^&RTMJ.[TMtE*UMwusUMf&QWM++\"\n        \"ZWMv0dWM^7mWM`C)XM\"\n        \"/h`XManiXMf0sxLsY<,#K4/X#]t8.v`Cg;-^Hg;-fHg;-jZGs-/\"\n        \"vo(M0niXMusrXMv#&YMI3B1vcZ`=-(nG<-*Ig;-,Ig;-*2d%.G25)MH)>$M,C<,#:nG<-\"\n        \"JQKC-#Tx>-[Ig;-r<)=-\"\n        \"mnG<-hIg;-J:RA-rnG<-:6]Y-Zs]e$qW>qV*f'-MkaTM'A_lA#7lG<-<lG<-=lG<-8Gg;-\"\n        \"fvX?-siq@-HGg;-OGg;-j-A>-RGg;-SGg;-XGg;-NPdh-/G@qV]@VMMqg/NMN$KNMF<pNM\"\n        \"#B#OMoM5OMqYGOMFaPOMM5;PMJGVPMN`%QM3r@QM5(SQMT.]QM2:oQM4F+RMfL4RMgR=\"\n        \"RMEeXRMDQhSMx2eTM2K3UM_P<UMSVEUMXusUM3.0VMb19VMeh5WM6%QWMF=vWMsB)XMtH2XM\"\n        \"1O;XMqZMXMsg`XM5oiXM6urXM,$&YM$<JYMT6&#MZcaG#aYZC#El@NM%AMPM+f.QMO>\"\n        \"wTMebgvLNMN,#OKn*.]GbGM.wCHM/\"\n        \"'MHM0-VHMU]3uLYaN,#QxiW#Gp4wLFKN,#]r(Z?1[I'S\"\n        \"C?_(WGS<R*4u[_&9.]_&#?)44K@ee$F.=qV5)wOoS?mA##Gg;-)Gg;-#$H`-?@o342C-IMA>\"\n        \"FJMX+UKMR(C0Mp2wx4k[J88&X(m9q<C2:55l]>4PH;@5YdV@6c)s@W7VPKL#:2L^<GDO\"\n        \"'>TSSp412Uq=LMU:LiiU0-:>Z%#-v?nM5v?oS>v?^ad5.5R2uLYhW,#6h#V#`MX,MpO#v,\"\n        \"eIJ88#Tk]>*>)s@34O2C2W&F.7Xae$Y]RX(cSZ_&klZ_&lJce$&oSX((JI_&7e%RETu38@\"\n        \"LH5qV=EtKG.FZ;%76xP''lG<-7ZGs-Mi](M[wXrLN[a,#EHg;-HZGs-7@t$MZdhsLEaa,#\"\n        \"Y8j'MXP<UMT]NUMpojUM:&'VMd=KVM3Y$/vNMx>-lmG<-jZGs-e_K%M*niXMusrXMv#&YM\"\n        \"w)/\"\n        \"YMd18YM.ToYML[xYM*a+ZM7g4ZM2m=ZM3sFZM],d2vB9K$.7-W'M3A([MlM:[M;t$7vg5)=-\"\n        \"i[]F-ka`=-5lq@-[Ig;-xa`=-mnG<-hIg;-Elq@-rnG<-:6]Y-Zs]e$E)aEe*f'-M\"\n        \"jW92'48oA#7lG<-_JU[-xhNX(CoNX(8Z^e$iY]3O>6EJMsDOJM/\"\n        \"j0KMO=qKMQI-LMqO6LMSU?LMWndLMEumLM`$wLMa**MMT13MMKCNMMlHWMM#OaMMqg/\"\n        \"NMg$KNM_<pNM)B#OMoM5OM\"\n        \"pS>OMA5;PMDGVPMH`%QM3r@QM5(SQMN.]QM2:oQM3@xQMaR=RM>_ORMJfXRM@kbRMVQhSM(\"\n        \"3eTM8K3UMeP<UMSVEUMUcWUM+jaUMWojUM^''VMa+0VMt19VM^=KVMQi5WMN%QWM_=vWM\"\n        \"sB)XMtH2XM7O;XMqZMXMraVXMItrXM&$&YM$<JYMPH]YM+</#Mj`j,#2Xwm//\"\n        \"EluuTSi,#.lG<-/\"\n        \"lG<-8()t-M<AvLw*2+vWUGs-<EQ&MEWqSMF^$TMIp?TMY'A0#Mk@u-s#GwLQJ3UM\"\n        \"_P<UMYVEUMZ]NUMN#(.v')A>-F,kB-`ZGs-:shxLrn>WMn$QWMpNvwL=bj,#tHg;-uHg;-\"\n        \"vHg;-^^3B-(nG<-.nG<-)Ig;-6<)=-1nG<-2nG<-5*)t-&($&MV3m2veUGs-4q;'M15lZM\"\n        \"`E23vYhG<-9nG<-6[Gs-Fj](M5t$7v^Bg;-d<)=-]dAN-MQKC-[Ig;-Q-kB-(0A>-s<)=-\"\n        \"hIg;-&b`=-rnG<-:6]Y-Zs]e$d8;'o*f'-MjW92'R=pA#T&uZ-t[NX(%w>qV2C-IMHQ7IM\"\n        \"IW@IMJ^IIM9a@.MhQ#v,)wY>-;&S8/O6eM1QHE/\"\n        \"2qVbJ2SZ&g2W)>)4Kb[D4`Mu`4aV:&5ZKXA5Its]5drsA#>8RA-CXHp-ek2@0#WAL,pC>XC*\"\n        \"mAL,(BIR*mL`e$oR`e$8f:F.Wxk'8\"\n        \":l:F.MB,:2,[X_&0hX_&3qX_&Yg,:20Cae$?q1;6<&AJC9LtiCqM;/\"\n        \"DrVVJDZmqfDI?6,EVmQGE7mrcEFH2)Fi@C;Il*82LPGQJMe3kfMS[/\"\n        \",NUnfcN+K-)OW*GDO94'aOSs*RE,+`9i\"\n        \"#'=F.[pbe$bKH_AV7Wk=Wl_q;g6Pe?r+[_&s.[_&<A6@0oSce$D(/\"\n        \":2w:[_&Le'44w07_]BS6@01_LR*vice$a>o'8,][_&Ic$@0Wa=;$Jxeo@^)^e$:SNX(/\"\n        \"?^e$<YNX('V@wTwHTlJ\"\n        \"Sf'F.-a1`aW<(&P#6>s@=vbe$f8ce$oqRs@en8`W,(iiUNjTs@j95]XrXHJV5m>s@\"\n        \"eqvKGHT$DWwb<X(jL`q;/\"\n        \"Ade$<VBXC)#T.hD%CJ`NacofKd:5gF,DX(Qw]_&R$^_&ctCXCW`Re?\"\n        \"oBDXCrPVX(VEjw9'KU_&uxn+M_IdY#/\"\n        \"FZ;%3krS&M-mA#NW=Z-mS^e$ZQ@L,H5_e$dm@L,=j?qVIvA0Mir//\"\n        \"1k0qA#h-A>-i-A>-H,-h-mn^3OeqINM%*TNM&0^NME6gNMKZGOM:aPOM\"\n        \"DGVPMEM`PMFSiPMGYrPMKr@QM@xIQMFF+RM`L4RMoQhSM^&RTMJ.[TMtE*UMwusUMf&QWM++\"\n        \"ZWMv0dWM^7mWM`C)XM/h`XManiXMf0sxL#2BH#aYZC#Kl@NM%AMPM+f.QMO>wTMebgvL\"\n        \"Tr/-#Ke2q/bc#7vml7-#,x(t-dGbGM.wCHM/'MHM0-VHMWojUMQY$/v5/\"\n        \":w-(vo(M?L<0v`Cg;-nHg;-CMn*.jnNZM9A([MNk<^M&(X^MTW0(Mf$9-#>r6]-Yp]e$)-^\"\n        \"e$x@Ik=+l0-M\"\n        \"892,)Ac<#-X?iP0S6.m0fvmA#alG<-w_`=-plG<-'``=-/\"\n        \";)=-4;)=-5;)=-6;)=-JCdD-SmG<-p.A>-kmG<-pmG<-9xX?-x;)=-xBg;-uEEU-vNaq-Gw:\"\n        \"'o1)'AO8K:6B5?f@k9A_S%\"\n        \")lkA#AGg;-eGg;-#Hg;-,ZGs-t4UhLUXFRMM2eTMi7BVMkh5WMr<vWMpTDXM.*jxL^.K-#Y,\"\n        \"g$0<pUvujxI-#1YGs-9.wiLKWqSMF^$TMHdhsL?6K-#Ao:*MA)YZM15lZMkG1[MHk<^M\"\n        \"&(X^Mi(NEMqaTM'6TgJ)^`6#6A_lA#rGg;-5Hg;-k``=-xgG<-kR-T-kR-T-`qN+.Gl$qL<\"\n        \"XZ)M/'MHM95>o.N)1/#b4)=-EHg;-TsA,M26T-#gSx>-S<@m/59OA#:Tl##PAg;-3()t-\"\n        \"9:3jL#'A0#dSGs-<GqhLN8nTMIqE^M_-b^M;2X<#oX`=-G*LS-6&Vp.*6xH#lhSe$]sbe$\"\n        \"nJPe$xk8qV;mEJC%OYe$9jxQExk8qVRBCX(*DxFMW3jvuR>x]G9U=X(E-be$F0be$<=uOf\"\n        \";Gxl^8eRe$BamLp2>ti_D3Se$='Y7n2DBJ`Tscof2o<5g<dCX(Qw]_&R$^_&CGw34-%\"\n        \"UHM6Q7IM[-w1MgMsu5S?mA#wlG<-xlG<-:mG<-;mG<-'xX?-)<)=-(6)=-.A]+/\"\n        \"m:o-#dj]e$\"\n        \"8Vdi0ID^VIF;#sIHNcof^J`lg2C9/\"\n        \"Dnr9)bGq<-m0$xP'#YGs-,5UhLQWqSMF^$TMG^_sLUU#.#un:*MVkj0v_Cg;-tHg;-uHg;-\"\n        \"vHg;-xHg;-*Ig;-,Ig;-_kq@-anG<-[Ig;-gIg;-\"\n        \"hIg;-lO,W-e;^e$R)(@0.+_HM;K.IM=W@IM>^IIMQI-LMRO6LMSU?LMXtmLMZ**MM.IWMMr;\"\n        \"pNMmA#OMoM5OM/(SQM0.]QM2:oQM6R=RM9eXRMQJ3UMRP<UMSVEUMWojUMM,0VMb19VM\"\n        \"nH2XMoN;XMqZMXMhvdT-e)*q-g0BX(c,>;$Ga@GD^)^e$0B^e$$8xReK,x]GjGKe$E-be$\"\n        \"F0be$KX5_A-0QiKP:eML]b5L,f6-fqPRjfMTeJGNrk`cWt'ADXu0]`Xv9x%YxKX]Y*?28]\"\n        \",Qio]/mel^EXXPgV(^'8MFee$mfNR*[qee$g<fe$h?fe$869@0kHfe$w]MX(+oBHM/\"\n        \"$;-Mfjpi'<ggJ)=p,g)>#H,*QHE/2RQaJ2SZ&g2X2YD4`rmY6l3_M:m<$j:oNZJ;/\"\n        \"l[PB0uwlB\"\n        \"21XMC6UpfD9qlcEQINJMq6D'oPNDUM_usUMr,0VMb19VMnH2XMoN;XMqZMXMusrXMv#&YM$\"\n        \"6&#Mgq5.#lW;W#CpUvuXCg;-1YGs-9.wiLKWqSMF^$TMs)],vj6)=-KF:@-1[Gs-LPc)M\"\n        \"@G1[MHk<^M&(X^Mi(NEMqaTM'6TgJ)^`6#6A_lA#rGg;-5Hg;-k``=-,mWN0@Tl##jP=.#\"\n        \"xpZiLf-v.#ESGs-eDDmLu2S0#7Ag;-NHg;-[a`=-N[Gs-((mlL%&<*MJvYI#)G>F#KG5-M\"\n        \"]7BVMtBdwL8_ORMXNoqLIaCU#Fv_vuVWX?g)O=SIhS[GEE%43.8S^sLxfG.#e@xu-9>G)Mw[\"\n        \"Kk.H.(3vkhG<-2Ig;-4Ig;-gSx>-YtA,MgaORM9ncI#hUkGEZ%1F.Z4DMMqYGOMr`POM\"\n        \"4F+RM5L4RMi/E6.e`_XMxZ2xLh'Q.#c9dV#pGbGMRh2vuk$)t--Pd&M/'MHM0-VHM$:2/\"\n        \"#Y4)=-EHg;-HZGs-GEQ&Md2m2vZUGs-MVl)M2;uZMNK;3vchG<-@<)=-1qj#.R9j'MW'X^M\"\n        \"R-b^Mo(NEMmK8$MtTS?M0-#O-NL8$Me+p+MmvlI#WY3B-.?Y0.(E3RM_usUMc?Y0.eS1xLu-\"\n        \"Z.#h'HV#xrKHMh92/#QfG<-EHg;-FHg;-Za`=-SHwM0$d#7v1U$K##S-iL7XFRM]7BVM\"\n        \"nBdwLn^i/#mP&7.&HbGM.wCHM/'MHM0-VHM./(.v/Rh/\"\n        \"#;)-l.90)0vahG<-`],%.l>1XMB_W0vb6)=-4nG<-9nG<-#lq@-KIg;-Zpx/\"\n        \".aOlDM&AH>#)4Z;%*=vV%rDnA#`o6]-lP^e$\"\n        \"M7OX(RxV_&9>Yw9Wx(MMkg/\"\n        \"NM&0^NMq5gNM55;PM4SiPM5YrPM6`%QMW&RTML3eTM^ojUM'i5WMp0dWMq6mWM:=vWM.*\"\n        \"jxLuvr/#K4/X#V_DuL1er/#^Hg;-fHg;-hHg;-e],%.schXM\"\n        \"usrXMv#&YMI3B1v+fq@-(nG<-*Ig;-,Ig;-p.E6.0+kZM/*>$M`gr/\"\n        \"#:nG<-JQKC-#Tx>-[Ig;-r<)=-mnG<-hIg;-J:RA-rnG<-:6]Y-Zs]e$qW>qV*f'-MkaTM'\"\n        \"A_lA#7lG<-<lG<-\"\n        \"=lG<-8Gg;-fvX?-siq@-HGg;-OGg;-j-A>-RGg;-SGg;-XGg;-NPdh-/G@qV]@VMMqg/\"\n        \"NMN$KNMF<pNM#B#OMoM5OMqYGOMFaPOMM5;PMJGVPMN`%QM3r@QM5(SQMT.]QM2:oQM4F+RM\"\n        \"fL4RMgR=RMEeXRMDQhSMx2eTM2K3UM_P<UMSVEUMXusUM3.0VMb19VMeh5WM6%QWMF=\"\n        \"vWMsB)XMtH2XM1O;XMqZMXMsg`XM5oiXM6urXM,$&YM$<JYMT6&#M&'&0#;tbQ#/\"\n        \"sKHMf0;+v\"\n        \"B$)t->fHiLHdhsLPk%0#t(7*.RF;UM*vb1#Kk@u-X@t$M?L<0v%UGs-Sf*$MB_W0voTGs-?\"\n        \"WA(MZD23vNCg;-:nG<-C2d%.Ke)'MW'X^MR-b^Mc8H`MVd2aMT#EEMQ@H>#)lkA#)Gg;-\"\n        \"5;Ab-xu^e$Pj;_AIvA0M4EWY5xVnA#]H)a-^cAL,,sAL,-vAL,32BL,45BL,;JBL,@YBL,A]\"\n        \"BL,6HQX(R_;F.Sb;F.]/9RE`=KR*e5ce$>l.:2?o.:2xbSX(G1/:2H4/:2.+BX(k:t=Y\"\n        \"N0fTJ?DvRnsPUlJoVKe$OZhf(obDGWbU2pJN45L,Ux%SnhBarneK]oo;LlA#MGg;-Z@DX-Q'\"\n        \"X_&).ae$m[FG)b_u(ELrqlKuJ%#Qq=LMU2L*,WP(M5Kv;G_&fV`(WM,u5K:uXY,._5_A\"\n        \"4HKe$$]GL,;,n34D(ni0;LlA#g3]Y-Q'X_&).ae$wh8qVM_5_A45w1KEqCL,i4SX(-wDL,t+\"\n        \"I_&%p>;$jKBMKIbtRe&+?v$/FZ;%2[2W%&r<X(1_1_A&l32'0tnP'OKhJD@><R*I]R4o\"\n        \"?^w]GViPe$E-be$F0be$bhCL,4Ewx+c-F/M&.Re$dt0ipPRjfMYn/\"\n        \",N]-^GN+0CX(t48`WYH:&P:kRe$^vbe$4qDo[jfooSnxOPTk+LMU4A1/\"\n        \"VqbDGWtwrcW*D,F.tcce$ufce$vice$\"\n        \"&EI_A(P[_&lqQS.F>eV[$(Re$)/de$<*MR*1l[_&2o[_&3r[_&itj@bM(#m^=tRe$_c#M^/\"\n        \")FM_wZvi_6-K_&t<Cwp@hTX(5Sde$rUt9MK@ee$vM7]Xt0Q`kR0R]lT;kxl_e?>mT%=R*\"\n        \",C@F.(iGL,#5OR*h?fe$%6=REkHfe$xcVX(?$8F.uxn+MwIdY#/FZ;%6tVW%F7AMK,E/\"\n        \"F.+l0-MPcTM'S?mA#=:)=-DZuk-(IGR*HLGR*IOGR*8Z^e$+YLS.oq*d*EmrA#.+kB-ABdD-\"\n        \"HGg;-SgDE-UlG<-&E:@-RGg;-SGg;-58RA-]gDE-e:)=-`lG<-lY]F-V0d%.f7j'M5GX%\"\n        \"vFCg;-_Gg;-.wX?-#.A>-&)>G-mgDE-(``=-mGg;-mBdD-ulG<->wX?-^+kB-X]3B-Sjq@-\"\n        \"Vjq@-0mG<-3mG<-YE:@-2ZGs-7w8kLI;oQM9@xQMqF+RMrL4RMsR=RMIXFRMV_ORM=\"\n        \"gXRMFkbRMiQhSM>'RTMC/\"\n        \"[TMM2eTM]K3UMqP<UMSVEUMUcWUM7jaUMWojUMr+1.v%IuG-e6&F-\"\n        \"#Sx>-[Hg;-nZ]F-chDE-WPKC-s6&F-rmG<-smG<-<xX?-oHg;-DF:@-wmG<-Lkq@--VYO-\"\n        \"sCdD-1a`=-vHg;-a^3B-,nG<-Qe2q/k,L+v8'R0#EHg;-HZGs-65UhLI=e3#4SGs-<Pc)M\"\n        \"15lZM2;uZM4G1[MIqE^Mk-b^McYmDMp@H>#)4Z;%]Vq]5A_lA#^Gg;-qGg;-x(`5/\"\n        \"Jce@#:?*RM;L4RM7XFRMW&RTM78BVMvTDXMsg`XM4[2xL&K]0#F6OA#.HbGMV*WvuQhG<-/\"\n        \"Gg;-\"\n        \">L`t-Ni](M(w70#WSGs-8@t$M,/\"\n        \":.vUCg;-^Hg;-fHg;-hHg;-bw]7.r2uWM8M<0v<`Xv-L;@#MB_W0vgTGs-_;_hLw)/\"\n        \"YMx)YZM15lZMfE23v[Z`=-:nG<-J[Gs-]vo(MW'X^MR-b^M\"\n        \"^pp_M^w#`Mcd2aMqpDaMv1jaMd(#GMQ@H>#5:lA#*lG<-/lG<-WJU[-.K/\"\n        \"F.3I6IMBDOJMai0KMK%LKMdx90Mhiji0k0qA#@Pdh-H#AL,i&AL,GdGwTY+)2MjS-29%\"\n        \"OcP9&X(m9EgD2:\"\n        \"KG=,<:pWG<DuhY?E(.v?F1I;@G:eV@K_&pA@CA5BF$:/D`vUJDoRC;I^IVPKJnwlKtU7/\"\n        \"Mw8d`OfbUPT+cllTvF12U^ZNMU`m//V/U&)XaDCDXh.WYZupXNLJK-fq2F_xO?$Se$^vbe$\"\n        \"f8ce$h>ce$$%xUm._ADXu0]`Xv9x%Y#OOAYBD<R*(P[_&*2de$,8de$(XXk=<*uRnH%ci_.\"\n        \"1jMLp0]_&JEbq;#(@F.[qee$rPVX(ms^_&h?fe$J.#.6r,__&9C?L,w1=GM)UY,MHPtl&\"\n        \"5:lA#NW=Z-n%V_&<5V_&=8V_&8Z^e$ff1@0s*#44H5_e$OJ_e$j)AL,RS_e$SV_e$Xf_e$\"\n        \"MD@qVY+)2M$bSV6qnJ88NpH59Fp`M:#b$j:oNZJ;qa;,<F>XG<M(m]>J1iY?NU*s@3l%pA\"\n        \"5(]PBT6#mB21XMC4C9/Df2VJDg;rfDE?mcED)B;IxN82L2BPJM_wjfMS[/\"\n        \",NX3c`O3(KAPba_]PeJSSS6(QPTFqiiUsO-/\"\n        \"VtXHJV1CefVqbDGWst%)X5oDDX6x``X,_x%Y$_9>ZVVruZ\"\n        \"[$bjLaZt(3Aokl8%gK>?+GD8AO7niLg]45TPLE/\"\n        \"M-6Z9M$;vr$0tnP'W*GDO]FlDk0jv+Mf`L_&>6EJMeg/\"\n        \"NM#5;PM*`%QMY:^-#RKx>-7Hg;-Y;)=-cmG<-kmG<-lHg;-&<)=-*$)t-\"\n        \"4TtGM.wCHM/'MHMlE2/#uB*1#EHg;-FHg;-Za`=-Q6@m/\"\n        \"XBP##sH31#+YGs-.j&kLYkv##]SGs-eACpL/\"\n        \"'MHMe'm.#hx(t-*6[qLqp.0#;TGs-U@mtLL,[TM'?f0#K#)t-DGqhLEnW4#\"\n        \"FAg;-,C]'..,@YM*<JYM&H]YM'NfYM&B8#M#Y41#)Ig;-+Ig;-/\"\n        \"[Gs-o)ChL80Q6#JSGs-%gHiLY;d6#_SGs-OW`mL[Gv6#WAg;-BN`t-;x8kLNk<^M^#>9#=\"\n        \"gG<-QnG<-RnG<-gI]'.\"\n        \"f4TkLM2X<#>Bg;-iV%>/rMrG#:m0-MdbTM'/\"\n        \"(lA#V&uZ-jJ^e$]gd9M2C-IMNQ7IMQdRIMKa@.MVR()3]2#d3W)>)4(Sv`4ZD:&50xVA5%]\"\n        \"r]5qBqA#OPdh-0J@qV^F`MM*H,OMJT>OM\"\n        \"wYGOM:aPOMQ@xQM@F+RMAL4RMD_ORM3lbRMg]NUM0dWUMciaUMWojUMjvsUME''VMc>KVM@\"\n        \"bVXMsg`XManiXMh</#M8p=1#kjVW#4HbGMRh2vudhG<-4lG<-/Gg;-2YGs-MQ,lLWWqSM\"\n        \"F^$TMI3B1vt6)=-a/A>-Q*`5/\"\n        \"59OA#4BXGM)XlGMRem##YF`t->oYlLV'<$#(Bg;-1YGs-T82mLl-v.#6TGs-:T3rLs&A0#\"\n        \"RBg;-T))t-J;9sL-dF1#7Ag;-M)-l.Tv%5#GfG<-)nG<-\"\n        \"$Ig;-&Ig;-)[Gs-k5UhLAZxYM+g4ZM-sFZMV)H6#Fk@u-0YajL.s+$MbfF1#$f]n.f1l6#\"\n        \"sF`t-C_>lL4G1[MjS27#SfG<-6:H-.9IwqLQ'X^MR-b^M`&-`M:@@;#ek@u-`tarLj1jaM\"\n        \":Kk<#]_aL#:;Ab-rREX(.(L-M*'QJ(4B0j(L+pA#M-A>-N-A>-K_`=-jj3f-;:W_&V`_e$&\"\n        \"S2@0Yi_e$/=+:2$):F.S%9kXY+)2M#X8;6k0qA#s_`=-Hjq@-vlG<-9Rx>-:Rx>-?;)=-\"\n        \"@;)=-A;)=-1u,D-L``=-/\"\n        \"kq@-b;)=-VHg;-iZ]F-D'hK-_hDE-,xX?-rHg;-`,kB-a,kB-;;9O0_W1vuA^N1#5+W'M/\"\n        \"'MHMh92/#GfG<-EHg;-HZGs-@rZiLVvHTM9*/YM,3m2vvH`t-\"\n        \"N]u)M2;uZM4G1[MHk<^Mj'X^Mi(NEMwaTM'6TgJ)]Vq]5A_lA#^Gg;-qGg;-rGg;-4Hg;-\"\n        \"5Hg;-'xX?-#nG<-&HwM0[?cuujcW1#BNc)M(RcGM/XlGM6-VHMx#S,vkZ`=-Z;)=-VmG<-\"\n        \"TZGs-/vo(M0niXMusrXMv#&YMx/\"\n        \"8YM*a+ZM,m=ZMY;d6#VpX?-4Ig;-g<)=-[Ig;-gIg;-hIg;-lO,W-e;^e$1E^e$7W^e$8Z^\"\n        \"e$QP_e$RS_e$SV_e$Xf_e$U0Kk=]@VMMr;pNMmA#OM\"\n        \"oM5OMr`POMG(SQM6.]QM2:oQM5L4RMHR=RM?eXRMQJ3UMRP<UMSVEUMZ+0VM[\"\n        \"19VMnH2XMoN;XMqZMXMtmiXMTEEU-kMaq-mg:R*71ol&4J4AOe>^e$?@x(3ID^VIF;#sIVq]\"\n        \"GNNDDX(\"\n        \"afFL,K@ee$U$HD*dnro%e8Ke$4vCk=M)VlJP:eML%o<X(3C$#,Js$2hipt1q-\"\n        \"27jqpWdxOMli34J^>SIF;#sIP@3/MXF=&P2Tbe$XU'BPk$O4oo@P-QXbRe$*DxFMh92/\"\n        \"#GfG<-EHg;-\"\n        \"HZGs-a15)MU3B1vhCg;-/\"\n        \"Ig;-njj#.G=0[MHk<^M&(X^Mi(NEMqaTM'6TgJ)^`6#6A_lA#rGg;-5Hg;-k``=-$$)t-:<\"\n        \"OGM(RcGM(L>gLevWuLj*OW#CsKHM<-VHMh92/#b4)=-EHg;-\"\n        \"TsA,M^912#a/A>-S<@m/\"\n        \"59OA#fTl##PAg;-3()t-9:3jL#'A0#bAg;-Cw]7.d.mTMIqE^M_-b^M;2X<#u'A>-Os%/\"\n        \"0n-]-#U9TM#7Hg;-]Hg;-nBg;-=@0,.o%juL^WGs-Ck&kLKWqSM\"\n        \"F^$TMpqO5v]J`lgeVfSSwtiEI8xvr$2$]p&<[@PSd^Ue$<YNX(Cv>ulS%voJw?)TSa`1`a`\"\n        \"Z:&P5[Re$^vbe$f8ce$h>ce$1o8`W&lhiU93*TSj95]XteZJV[xPe$,#)MTqnVGWkJKe$\"\n        \"jL`q;/\"\n        \"Ade$B7;RE)#T.hD%CJ`NacofKd:5gF,DX(Qw]_&R$^_&]=K_AW`Re?oBDXCrPVX(VEjw9'\"\n        \"KU_&uxn+M_IdY#/FZ;%3krS&M-mA#NW=Z-mS^e$ZQ@L,H5_e$dm@L,=j?qVIvA0M\"\n        \"ir//\"\n        \"1k0qA#h-A>-i-A>-H,-h-mn^3OeqINM%*TNM&0^NME6gNMKZGOM:aPOMDGVPMEM`\"\n        \"PMFSiPMGYrPMKr@QM@xIQMFF+RM`L4RMoQhSM^&RTMJ.[TMtE*UMwusUMf&QWM++ZWMv0dWM\"\n        \"^7mWM`C)XM/h`XManiXMf0sxL_773#K4/X#Hu8.v`Cg;-^Hg;-fHg;-jZGs-/\"\n        \"vo(M0niXMusrXMv#&YMI3B1vcZ`=-(nG<-*Ig;-,Ig;-*2d%.G25)MH)>$Mnv63#:nG<-\"\n        \"JQKC-#Tx>-\"\n        \"[Ig;-r<)=-mnG<-hIg;-J:RA-rnG<-:6]Y-Zs]e$qW>qV*f'-MkaTM'A_lA#7lG<-<lG<-=\"\n        \"lG<-8Gg;-fvX?-siq@-HGg;-OGg;-j-A>-RGg;-SGg;-XGg;-NPdh-/G@qV]@VMMqg/NM\"\n        \"N$KNMF<pNM#B#OMoM5OMqYGOMFaPOMM5;PMJGVPMN`%QM3r@QM5(SQMT.]QM2:oQM4F+\"\n        \"RMfL4RMgR=RMEeXRMDQhSMx2eTM2K3UM_P<UMSVEUMXusUM3.0VMb19VMeh5WM6%QWMF=vWM\"\n        \"sB)XMtH2XM1O;XMqZMXMsg`XM5oiXM6urXM,$&YM$<JYMT6&#MF@[N#aYZC#1m@NM%AMPM+\"\n        \"f.QMO>wTMebgvL:+I3#OKn*.HHbGM.wCHM/'MHM0-VHMU]3uLE>I3#QxiW#3q4wL2)I3#\"\n        \"HigPT1[I'S/\"\n        \"Ya(WGS<R*4u[_&9.]_&#?)44K@ee$F.=qVwB#PoS?mA##Gg;-)Gg;-#$H`-?@o342C-IMA>\"\n        \"FJMX+UKMR(C0Mp2wx4k[J88&X(m9q<C2:55l]>4PH;@5YdV@6c)s@W7VPK\"\n        \"L#:2L^<GDO'>TSSp412Uq=LMU:LiiU0-:>ZgoklTYDtlTZJ'mT^ad5.wR2uLEER3#6h#V#\"\n        \"KNX,MpO#v,eIJ88#Tk]>*>)s@34O2C2W&F.7Xae$Y]RX(cSZ_&klZ_&lJce$&oSX((JI_&\"\n        \"#)(RE@lr.ULH5qV)`vKG.FZ;%76xP''lG<-7ZGs-Mi](M[wXrL:9[3#EHg;-HZGs-7@t$\"\n        \"MZdhsL1>[3#Y8j'MXP<UMT]NUMpojUM:&'VMd=KVM3Y$/vNMx>-lmG<-jZGs-e_K%M*niXM\"\n        \"usrXMv#&YMw)/\"\n        \"YMd18YM.ToYML[xYM*a+ZM7g4ZM2m=ZM3sFZM],d2vB9K$.7-W'M3A([MlM:[M;t$7vg5)=-\"\n        \"i[]F-ka`=-5lq@-[Ig;-xa`=-mnG<-hIg;-Elq@-rnG<-:6]Y-Zs]e$\"\n        \"E)aEe*f'-MjW92'48oA#7lG<-_JU[-xhNX(CoNX(8Z^e$iY]3O>6EJMsDOJM/\"\n        \"j0KMO=qKMQI-LMqO6LMSU?LMWndLMEumLM`$wLMa**MMT13MMKCNMMlHWMM#OaMMqg/\"\n        \"NMg$KNM_<pNM\"\n        \")B#OMoM5OMpS>OMA5;PMDGVPMH`%QM3r@QM5(SQMN.]QM2:oQM3@xQMaR=RM>_ORMJfXRM@\"\n        \"kbRMVQhSM(3eTM8K3UMeP<UMSVEUMUcWUM+jaUMWojUM^''VMa+0VMt19VM^=KVMQi5WM\"\n        \"N%QWM_=vWMsB)XMtH2XM7O;XMqZMXMraVXMItrXM&$&YM$<JYMPH]YM+</#MU=e3#2Xwm/\"\n        \"qEluu@1d3#.lG<-/lG<-8()t-M<AvLw*2+vWUGs-<EQ&MEWqSMF^$TMIp?TMY'A0#Mk@u-\"\n        \"s#GwLQJ3UM_P<UMYVEUMZ]NUMN#(.v')A>-F,kB-`ZGs-:shxLrn>WMn$QWMpNvwL)?e3#\"\n        \"tHg;-uHg;-vHg;-^^3B-(nG<-.nG<-)Ig;-6<)=-1nG<-2nG<-5*)t-&($&MV3m2veUGs-\"\n        \"4q;'M15lZM`E23vYhG<-9nG<-6[Gs-Fj](M5t$7v^Bg;-d<)=-]dAN-MQKC-[Ig;-Q-kB-(\"\n        \"0A>-s<)=-hIg;-&b`=-rnG<-:6]Y-Zs]e$d8;'o*f'-MjW92'R=pA#T&uZ-t[NX(%w>qV\"\n        \"2C-IMHQ7IMIW@IMJ^IIM9a@.MhQ#v,)wY>-;&S8/O6eM1QHE/\"\n        \"2qVbJ2SZ&g2W)>)4Kb[D4`Mu`4aV:&5ZKXA5Its]5drsA#>8RA-CXHp-ek2@0#WAL,pC>XC*\"\n        \"mAL,(BIR*mL`e$oR`e$\"\n        \"8f:F.Wxk'8:l:F.MB,:2,[X_&0hX_&3qX_&Yg,:20Cae$?q1;6(@CJC9LtiCqM;/\"\n        \"DrVVJDZmqfDI?6,EVmQGE7mrcEFH2)Fi@C;Il*82LPGQJMe3kfMS[/\"\n        \",NUnfcN+K-)OW*GDO94'aO\"\n        \"Ss*RE,+`9i#'=F.[pbe$bKH_AV7Wk=Wl_q;g6Pe?r+[_&s.[_&<A6@0oSce$D(/\"\n        \":2w:[_&Le'44w07_]BS6@01_LR*vice$a>o'8,][_&Ic$@0C%@;$6oMfU^)^e$:SNX(/\"\n        \"?^e$<YNX(\"\n        \"'V@wTccVlJSf'F.-a1`aW<(&Pe,'jU=vbe$f8ce$Zh;jUen8`W,(iiU:a=jUj95]\"\n        \"XrXHJVwc'jUeqvKG4o&DWwb<X(jL`q;/\"\n        \"Ade$<VBXC)#T.hD%CJ`NacofKd:5gF,DX(Qw]_&R$^_&\"\n        \"ctCXCW`Re?oBDXCrPVX(VEjw9'KU_&uxn+M_IdY#/\"\n        \"FZ;%3krS&M-mA#NW=Z-mS^e$ZQ@L,H5_e$dm@L,=j?qVIvA0Mir//\"\n        \"1k0qA#h-A>-i-A>-H,-h-mn^3OeqINM%*TNM&0^NME6gNM\"\n        \"KZGOM:aPOMDGVPMEM`PMFSiPMGYrPMKr@QM@xIQMFF+RM`L4RMoQhSM^&RTMJ.[TMtE*\"\n        \"UMwusUMf&QWM++ZWMv0dWM^7mWM`C)XM/h`XManiXMf0sxL^Iw3#_<@m/(ee@#wW1vu`Cg;-\"\n        \"q6K$.UsKHMh92/#M4)=-EHg;-HZGs-a15)MU3B1vhCg;-/\"\n        \"Ig;-njj#.Z=0[MHk<^M&(X^Mi(NEMqaTM'6TgJ)^`6#6A_lA#rGg;-5Hg;-k``=-$$)t-M<\"\n        \"OGM(RcGM(L>gLLV34#4lG<-\"\n        \"/Gg;-6(`5/\"\n        \"wmvC#mMpSMF^$TMWsN+.pa;^Md'X^MCPk<#:M;4#32TkL+e(HMl-v.#Kx(t-eDDmLJp$tL'_\"\n        \"<4#NHg;-[a`=-N[Gs-((mlL+&<*M($XO#)G>F#aQERM]7BVMnBdwLoHmwL\"\n        \"%S[V#xKuuu)=vV%*AHGW8&iof:?]5'wFg;-Dq@u-Ew8kLQWqSMF^$TMs)],vRH`t-@j](MY>\"\n        \")3vfUGs-O]u)M[J;3v]Cg;-@<)=-1qj#.R9j'MW'X^MR-b^Mo(NEM3bTM'6TgJ)]Vq]5\"\n        \"YQmA#8p6]-W9X_&x<X_&:0Y_&;3Y_&'W5@0)xSX((oAX(h`3kX%HM`W_CxFMh92/\"\n        \"#QfG<-EHg;-FHg;-Za`=-ICg;-Q;-5.dN(xL[O,W-ej]e$w0GG)OV^VIF;#sIIVuoJltR)\"\n        \"XGB-fq\"\n        \".XscW>wRe$tcce$ufce$vice$xoce$*2de$,8de$_E(44aN^_&[qee$g<fe$h?fe$kE]e$+\"\n        \"oBHM.t1-M(kpi';^K/)=p,g)>#H,*QHE/2RQaJ2SZ&g2X2YD4ZD:&5.4oY6rE_M:m<$j:\"\n        \"oNZJ;/l[PB0uwlB21XMC6UpfD9qlcEQINJMRRjfMS[/\"\n        \",NW*GDOMuEAPba_]PnFHJVoOdfVqbDGWwm<wpd`<wpw-%#MX0b4#8<:R#THbGM0-VHM^UG*\"\n        \"vOH`t-,5UhLEWqSMF^$TMFWUsL\"\n        \"Kua4#NZGs-l[u)M;N=-vhCg;-RHg;-THg;-rHg;-tHg;-uHg;-vHg;-xHg;-*Ig;-,Ig;-*\"\n        \"2d%.=`K%M44k^MZ,6`Mn2?`MguMaMh%WaMj1jaM98saMk4aEMcNtl&0tnP'/(lA#7lG<-\"\n        \"<lG<-=lG<-8Gg;-QGg;-RGg;-SGg;-XGg;-`Gg;-lGg;-mGg;-oGg;-/\"\n        \"Hg;-0Hg;-2Hg;-6Hg;-9Hg;-a&^GMaKl(N_Ec`Or6GAPba_]PnFHJVoOdfVqbDGWu0]`\"\n        \"Xv9x%Y&qpuZ'nbaX\"\n        \"k$O4o-3pl&8eRe$/?^e$.QSV-ID^VIF;#sILlL5KI5DX(K=/:2/\"\n        \"Ade$AWQ1p>iBJ`HNcof&Palgj#:Mq;LlA#0Gg;-6Gg;-&X=Z-R[`e$5Rae$k_KR*x7I_&\"\n        \"AEh?K7>fxXlUdi0C2^VI\"\n        \"F;#sIwB=AYB,.?ZE>SY,,bwr$0nIp&1tJ_&/\"\n        \"?^e$<YNX(Cv>ulU12pJ5VLe$-a1`aYH:&P5[Re$^vbe$f8ce$h>ce$1o8`W&lhiUh5B/\"\n        \"VW:I_&ABEo[pe;,WD2Pe$S#ec)uB=AYxEhl^\"\n        \"1)FM_?awi_;/<R*:1]_&H7ee$QW#VmU8`lgR/\"\n        \"%2h^=P`k^Do%lc@DSoqw%5pvG:Mqejdxu)lkA#5e%Y-aSU_&/\"\n        \"dU_&VYv92,r9-M'BMG)BlW>-a?Q8/KhL50ediP0k0qA#?Pdh-vo?qV\"\n        \"L5pKMiC$LMt-w1MrNsu5R=pA#$``=-%``=-Djq@-Ejq@-9Rx>-:Rx>-DRx>-ERx>-FRx>-\"\n        \"GRx>-?``=-@``=-_E:@-`E:@-V``=-I>aM-prUH-oRx>-V>aM-*/A>-u;)=-]PKC-^PKC-\"\n        \")a`=-`,kB-a,kB-C`Xv-Y<OGM(RcGM0-VHMWojUM-*%7v[EfP#0rcW-xu^e$e4`e$#r`e$*\"\n        \"1ae$i[FG)Sd6,EM%72Li&%#Qk]SSSrFhiUpX),W0-:>Z2^dvZk$O4o4Hpl&tf2vZe>^e$\"\n        \".QSV-OV^VIF;#sILlL5KI5DX(6T.fq-mel^1)FM_k<DJ`HNcof&Palgj#:Mq;LlA#0Gg;-\"\n        \"6Gg;-&X=Z-R[`e$5Rae$k_KR*x7I_&B$pEI-V#8[P%Xe$8Vdi0ID^VIF;#sIITlof3_`=-\"\n        \"Q6@m/?=G##Zd_7#*YGs-2o;'M5'MHM0-VHMGkFrL2$a7#WQ,lL/\"\n        \"*],v5Cg;-cAxu-]vo(MQ'X^MQ$FBM&E.PfrS(Ab+=ee$hg:`WR;72hreQe$Y^UX(rMn=\"\n        \"YWo8GjiX5DkgkL]l4'fDb\"\n        \"wQGD*q^EVnsJhiq*_J_&d]Qk=mMIbM'V/+Mpxi7#,x(t-rHbGM/\"\n        \"$;-MtVEigR+m`b1Oee$SXee$W5Hk=XYAfqqVC]bSafe$BN'@0sPw-6sPw-67kl+Msx$]Msx$\"\n        \"]ME/8S#s5'&cX4RA-\"\n        \"s9RA-tBn]-RTg-6rGe-6,w#>cIbtRe&+?v$YMPAcecU_&h&<A+808p&GmOAce>^e$Hq?L,I]\"\n        \"R4oQ>x]GViPe$E-be$F0be$E?/ciV:M5Kr`Ke$DbU1gQUaJMD3Se$X5Z_&Y8Z_&A.Brd\"\n        \"Rscof>Cblg+4_B#5_iO#[+I4v2UGs-AFQ&MpnF5v^Cg;-Z0#dMk-b^MD=(6v[Z`=-YnG<-\"\n        \"qAxu->aw#M96I7vHCg;-<:RA-]Ig;-=;kf-WRGL,cG0_].Y()b(6brn%-'8ofNSSot^BX(\"\n        \"km^_&f9fe$;d1:2nv^_&iBfe$v]VX(Mm5`a8Mr.r9HrA##=)=-gv,D-9gXv-PHR#M3]\"\n        \"SbMxb]bMshfbMHoobM%uxbMv$,cMQ+5cMVLQ:vK(.8#@?gkLe'm.#<4@m/\"\n        \"ttTB#j$k5v]UGs-\"\n        \"Y9j'MSWK_M6EI;#GfG<-N/E6.c1raMN`^9vq6)=-)[wm/\"\n        \"gbE5v,8RS#JIg;-MIg;-ca`=-]nG<-`nG<-ZIg;-_Ig;-gIg;-jIg;-m[Gs-,35)MSD/\"\n        \"bM'w,D-.nWN0ig60#[2@8#lOc)M\"\n        \"QGw9Mn^5Vdv.w:d&OAPpHaCPgj9J>d.Fee$POee$Vbee$S$6kX6x4kXoX-F.=_c9M]@\"\n        \"uCsc7.?d4/6`aGQA;$.8dYd^)^e$/\"\n        \"<Te$-.aYd'h]j-b[3wpRg1Sn#><Vd:$r(kqu.DtXbeYd\"\n        \"#N'@0#_e-Q#_e-Q>*m+M#Ne]Mw;.&Mr2bW#@trEIa/\"\n        \"-FI?0v+MaMe]MaMe]Mef3^Mef3^M7m+9#wZ,%./\"\n        \"tKHMQNn*.Hb;^MPt<BMchw@bkg^Sfw2&_]4Wv:dn'0Tf8i/fqvBGkX@og.h\"\n        \"jeaSfAANR*[2vRnI))_]S^5Vm@KK_&[a.eZUfmQaArRfrMj(5g$blAPPs]V$:1+5g]nP_&/\"\n        \"dU_&+GxFMdUG*v.;xu-4.X$M#N=-vACg;-#;7I-.0FCMYVEUM/mb-vFXd&MYiDxL:iU@M\"\n        \"q$Xxb=@7Ac@Ue]cx8;R*ER]_&h1b9M8pVrdI30;eF6^Ve7G4L,Ke]_&F1ee$H7ee$I:ee$#\"\n        \"q0:2U-^_&QRee$RUee$T[ee$U_ee$Weee$KlxOfEdN]l20&#m;<K_&]tee$@E5`ac'eum\"\n        \"$t<;nJjK_&8rQ3k_Nu`M%^)aM6mE8vXZ`=-knG<-fIg;-;G:@-nnG<-iIg;-v<)=-mIg;-Z-\"\n        \"kB-+b`=-qIg;-rIg;-tIg;-w[Gs-xe)'Mp+5cM&u]+M#2cT#>p9`0P@cuujwN9#w16&M\"\n        \"(RcGM/XlGMXem##HF`t-tEqhL/\"\n        \"'MHM0-VHMvUG*v]H`t-T=G)Mh<M+vwBg;-EHg;-FHg;-d@xu-?fHiLa2ItL)/\"\n        \"P9#=3H-.VG;UMYVEUM-gb-vW)A>-Av,D-OtCp.53:`W`jR-QD%Re$\"\n        \"eTV?gAM4Pf@KK_&J=ee$lx%2htISMh=5<R*Y9^_&o:GL,3=@i^gw_]l(4Re$<Yx-6]tee$<\"\n        \"Q]-Q[<Y`MwAHDM?s+Sn5f,F.$]GL,c0fe$C<9c`op]oolgx4pg^=Pp<;Zlpo,u1qj#:Mq\"\n        \"#^hiq9@%@0+epKcj;.bM$J8bMhPAbMa`^9v@)A>-wnG<-rIg;-GG:@-$oG<-w[Gs-=]ovL?+\"\n        \"5cMVLQ:vY'X9#Z9@#MOUmuuUCg;-.lG<-1()t-m3UhL<-VHMAL4RMe*2+v_H`t-7'$&M\"\n        \"EWqSMF^$TMr#S,v;H`t-U?gkLN2ItLc;Y9#mOc)MXP<UMYVEUM1sb-v41tT#^)7*.BlT%\"\n        \"MEDuT#_]u)M#]+5vPUGs-`>G)MJwN^Mt-P9#=fG<-SnG<-YnG<-_*)t-[e)'M?6I7vZUGs-\"\n        \"r25)M]8H`M[,q(Mg?uT#w/\"\n        \"A>-w0Yc-vY]-Q_Nu`M%^)aM6mE8v]@:@-knG<-fIg;-;G:@-nnG<-iIg;-v<)=-iE&j-S/\"\n        \"__&a2cq;1.HL,*@+Raw;__&r^fe$AQ9@0$E__&ugfe$bbWwT\"\n        \"'N__&VLg-6uvA;$l7SMh^)^e$.<^e$/\"\n        \"?^e$0B^e$H7ee$J:[e$fg$4v07'U#G9j'MiC]4vgUGs-eo:*MIkw&MRJ(U#e>G)MT9t^\"\n        \"Ma3O'M&D(U#fa`=-ExGd.7Hv7vl)A>-5G:@-t$)t-\"\n        \"DnWrLp@l9#EHg;-L)`5/\"\n        \"^D7@#U)HtLK@l9#RN,W-x#Oq;TNt=lZFL]lA_lA#enG<-aIg;-gIg;-r<@m/\"\n        \"EIKU#D;89vKCg;-#=)=-)=)=-Xk+S0dC+.#.8t9#)5UhLv,80#%A9U#NcDE-\"\n        \"RcDE-VhG<-[*)t-W/\"\n        \"wiL<j*<#7Ag;-w<)=-]9K$.$P.+MXQB_M9RB_MaU::#8_u+j9_u+j9_u+jBrP>m.^v+Mb_\"\n        \"UU#9e(,j`bN^-o5`'89up'8%3B;$-MOGj^)^e$0B^e$MBXe$;vg%M\"\n        \"]Fk'M9lNO#*+t5v:Cg;-]N`t-6g*$M+__6vtjWVnCTK_&'AOR*qTSe$H?bq;H?bq;Pam+\"\n        \"MphU:#GHr(kHHr(kHHr(kIN%)kkKKC-M'a..<K&(Me>ZwLoKKC-EFHL-#=#-MIkg_MIkg_M\"\n        \"rq_:#jcg,.>tKHM'QD&.Wb;^MPt<BMk*x@b8Qx@kq0iCj>[3>d2u^Dk8i/\"\n        \"fqQ2rlg:kRe$Y8V4oL/\"\n        \"[ihcx<JiX0;Dk]aAR*vOGL,aZf@kwpEVnBrRfr[=P`ku.T`ku.T`ku.T`ku.T`k\"\n        \"$?W`kv4^`k>IuG-uNuG-uNuG-60LS-uNuG-<ad5.lW8(MDG?)MCIuG-:N-T-dhG<-j-Yc-\"\n        \"TtrEISmv+MG(.V#u4^`kA[U).:C=gLOlK(ME=IV#'Oo7Mh92/#GfG<-EHg;-FHg;-XOYO-\"\n        \"9gh9M5.l5vo:V'8n;u=lEM*GMl=&bMqU/+MXpT(MFJ[V#'Jl@M*_uGMc*WvuR6)=-/\"\n        \"Gg;-b,0c.#+1/\"\n        \"#;4)=-EHg;-HZGs-GEQ&MTk<^M:%Y5vVhG<-QnG<-_6^gLxC'X#uCrP-`Cg;-\"\n        \"5anI-ou:P-e$)t-(^u)Mj.WEM8'W+r@Z=Z-_eFR*)#WX(,,WX(>t.F.?&L'SmMUulo*Ye$\"\n        \"eBV'8DT2wp4'S5'M3*#mfA^e$B[t%4b7_VIF;#sIHNcof^J`lgn*2'oqSUulD5Pq;MHOq;\"\n        \"G9p:m&QVe$+GxFM3:SqL)G@;#Zi](M16o,vSCg;-)wQx-f;)UM_P<UMSS3:Mc+n=lH*E>\"\n        \"mkbH3khPQrmQRhKcm(/PoA_lA#s<)=-p*`5/*F7@#->.bMN.?:vfhG<-'2=v/c=G##w6H;#\"\n        \"(Gg;-2YGs-9k&kL,-80#=@dV#&[>hLaXZ)MtCj*M<-q(M<%OW#o2s>M&@,gLoRR;#@05)M:\"\n        \"wCHM5'MHM0-VHM=wXrLxMR;#EHg;-FHg;-Za`=-Wh&gL30bW#Q^,%.(3,)M6`d)M$WO-v\"\n        \"xXkA##<xu-1>i*Mhle;#aR^U#HtKHMRJntLEWe;#NnG<-ICg;-?(Rx-l5ZtL=^)aM=^)aM?\"\n        \"dm)MtO[V#E7+gLcem)MNXZ)M]*WvuFCg;-/Gg;-Dq@u-'m'hLQWqSMF^$TMs)],vP6)=-\"\n        \"gSx>-Nu%'.ktV^MQ$FBMuN9YcZn+Mp<DV4o[bHYmS9kPpHVZ7ngEWVnD3Se$cYQk=i5%\"\n        \"bM6J8bM/ifbM,%,cM,u]+M1,aaMN-aaMN-aaMLq)*MJ'PT#M0FCMc]Kg--+M'SdDn+Mv0X<#\"\n        \"N)-2q]S0K-flvEM*T0K-OcKg--+M'SNY_'SNY_'Ssl6VmL#$2qO)-2q_]Kg-..M'SNY_'\"\n        \"SeJw+Mw9tW#N)-2q_]Kg-..M'SCEn3OA9[3O%x'Jqc[Xe$C<)GMIkw&Mi%<*Ml?JS#%3s>M\"\n        \"W3jvu6XgJD<dCX(OD?ulL(.mK3URe$s85@0]fRX(RTbe$m'1ipOMu=l:kRe$Q4Y1g]\"\n        \"kdumwJM'S%S/PoA_lA#s<)=-l[Gs-V*ChL5D/\"\n        \"bMN.?:vfhG<-u>dD-9*7*.U%D*MJN,W-ej]e$\"\n        \"$8xReRrYSJ-31/\"\n        \"r,Bbe$^,'REdn(Mg,W;R*L@[e$X$p(MP,E*MC$OW#QX3B-ZCg;-%<dD-QT-T-ON-T-R6)=-\"\n        \"kcg,.U$UHM$:2/#;4)=-EHg;-HZGs-GEQ&MTk<^Mf_e&M*M'=#QnG<-\"\n        \"XT[Q/2V/fqHW*Giva9JroR-;nf<<;n<qRe$F-PJrCYQk=i5%bM6J8bM/\"\n        \"ifbM,%,cMZFQ:v_D/=#6@&n.G?<-v_hG<-RHg;-G],%.+w+`MZ,6`M_DZ`MguMaMj1jaMmC/\"\n        \"bMthJ+MGi9=#\"\n        \"?be@#OIbGM0-VHM^R,.#^kw6/([ZC#5TlDMa^nI-ihG<-ANNB/\"\n        \"lfx9v:][bMLc]bMtfT=#LXt(tQfq@-QA0,.*Z[bMLc]bMuopX#KRk(tOhBDt@6V'8FM$s$*=\"\n        \"vV%fm$EtUNQ1g,782'\"\n        \"0tnP'_LM5K0?CX(TNUX(GRiCjO&`lgR/\"\n        \"%2h)lkA#=XU10H%k5v?lxX#TnG<-g1r1.*RJ_M<He7vrm@u-u,,)M3Z*8vgUGs-F*%#Ml=&\"\n        \"bM<J8bM,VJbM/ifbM2%,cM2u]+M+uxbMavxbM\"\n        \"2$q=#`Lk%uaLk%ubRt%ud+-h-?bEwTpin+M2$q=#aRt%ud+-h-?bEwTa_WwTa_WwTa_\"\n        \"WwTqow+MavxbM406Y#I4H-.63,)Mq,6Y#aRt%uf4H-.B&D*Mq,6Y#aRt%ue+-h-@eEwTqow+\"\n        \"M\"\n        \"8<6Y#i#jW#b(hK-b(hK-b(hK-b(hK-b(hK-fL)e.>+1/\"\n        \"#SpX?-EHg;-TsA,Mt&,cMt&,cMt&,cMt&,cMG6?Y#YHuG-hLHL-b(hK-b(hK-b(hK-b4H-.\"\n        \"6u*cMb&,cM`jJ+M&dY-M[&,cM\"\n        \"],5cM],5cM],5cM],5cM],5cM],5cM],5cM.0->#]RT]u^]Kg-;+M'S].`'S].`'S].`'S].\"\n        \"`'S].`'S].`'S].`'S].`'S].`'S].`'S].`'S].`'S].`'Ssuw+M[#pFM]&g+MeA>A#\"\n        \"Svn+M&5T;-PsfcMS@-##,XG5NaF6##3EV6NUL?##Pp@7NVRH##dtH:NWXQ##q;K<NX_Z##-\"\n        \"5m=NZkm##<Lf?N7EwAO^'3$#VQCANENw&OOw-*.f4OcM;`Ys-rHQ*NZX6##L$x%#+MC;$\"\n        \"ub;..m+#-NS@-##-k)+NTF6##;DG-NUL?##vCr.NVRH##FL,5NWXQ##a119NX_Z##/\"\n        \"$t;NZkm##MRf?N7EwAO^'3$#,^wBNENw&O%%a..K2pgM1%a..cQX0NgcQ_#I7tlAk^T%J$'\"\n        \"4RD\"\n        \"peZL2K==,<P/\"\n        \"SQ:7ZRdF=r7fG'>ZhF-)xf;rgeqC,7:@';Rj`F1CRSD11xiC)C%F-7xEcH-DM*H:1_oDGsE;\"\n        \"I=7FGH2'2&G'p0oDtY'I6@H8eG=2O2>D51c=dbJM='DvlEXP0l1DAu$6\"\n        \"7Zvw7.Ui.MhOQ-NnP1@-SXit-+Z<SM'u0]Q4059B7YvLFSn=<Bli,CH.4FGH,C%\"\n        \"12cOFs1Elr=-T[^40<rY1Ft%q*H54ZEe.xMGH#+tfDZ8QFHv,a=Bh_'j:Q^$8B0d/\"\n        \"*#Xst.#e)1/#\"\n        \"I<x-#U5C/#vP?(#N5T;-]:.6/\"\n        \"s*2,#3V`mL`NEmLT)l?-f3(@-c3(@-PqX?-mD3#.B'BkL,B^nL3x.qLn.AqLU1T-#^XX>-\"\n        \"CqtiCY]F>H5`/gDx5g'&V&*dEjaf'&=eNe$,FAVHdvm'&\"\n        \"R,/&Ghdm`FBxj>-*aY_&J$.XCo7-AFdc/&Gh+#^G@j9X1+hRw9e+o>-1b`'/\"\n        \"JAWq)`XX%XJ%5?-nu;M-,JKC-tHvD-b_nI-C;Mt-KEpkLFU.(#XS6#6]Fos7]Wdo7J(9`A0)\"\n        \"pM1*jZuR\"\n        \"OJ`]PC5;PMX)crLV#uRM,K6iO(djE-[lVI.Y5C/\"\n        \"#QPxf;:Ix5BU&8;-Zpu9)hS4GDE2'?H#-QY5+v4L#?,j+MuIr'#fp_@-kj-x-7g$qLwX+rL/\"\n        \"bamLn(crLg+K-##>Hs-`(trLor%qL\"\n        \"13oiLIFfqL6.c(.(mWrL*hipLD(8qL:),[RQE=;.%/\"\n        \"5##vO'#v9pINMq0&T%#'K*#';P>#)5>##Q'+&#P-4&#I;G##jQD4#2AP##*Ic>#0Su>#4`1?\"\n        \"#8lC?#<xU?#@.i?#D:%@#HF7@#\"\n        \"KI.%#JF.%#NR@%#R_R%#Vke%#[*=A#P_[@#Tkn@#Xw*A#]-=A#a9OA#eEbA#iQtA#m^0B#\"\n        \"qjBB#uvTB##-hB#'9$C#+E6C#/QHC#3^ZC#7jmC#k<(7#H,IS#N9&=#fG8=#Y?*1#@b>N#\"\n        \"?,<D#B/3)#$M8n.wpM<#B^Rl&vjuu,PIt1Bd8V.qGO9;6AQd(N]Ivr$Z/\"\n        \"MfLDjto%_@f+Mr+SV6Mt587Q6mo74;0P]:[jl&u=+87LwGS77NZlAjrY(a7^v=YFb68.*\"\n        \"w658m3io.qKIP/\"\n        \"ud*20#'bi0'?BJ1+W#,2/\"\n        \"pYc232;D37Jr%4;cR]4?%4>5C=ku5GUKV6Wa%29[#]i9`;=J:dSt+;hlTc;l.6D<pFm%=t_\"\n        \"M]=xw.>>&:fu>*RFV?.k'8@2-_o@I5]xOYg.29^)fi9bAFJ:\"\n        \"fY',;jr^c;n4?D<`=IfU9R(AO<lGi^G%Ll]I<p=crLv%=NC@A=xqrx=h.W1ped2S[j:\"\n        \"sLpvGNo[Z/\"\n        \"qu>,XOV?impr?2'LS@BC?VQfJO>#6?-5AS,HcV:WdlAe5.2B@&aiBD>AJCHVx+D\"\n        \"iPw.C3xBGDN%u(ER=U`EVU6AFZnmxF_0NYG:ZExtcH/\"\n        \";HgafrHk#GSIo;(5JsS_lJwl?MK1I$>P';<JLx43Vd1]=JCt)ZfL-`8GM%QVcM*6[.hB_\"\n        \"xOo7Fl%On$QcD=nixF$rK]O?w->P\"\n        \",FHYP2G7MKL->loI^arQMvASRRCGP8GZh7er_Big<R%5S&Um(E`EAVHsfQiKW]u1T[\"\n        \"uUiTD9t.UFS9ci_8m:dnFb1gCBTfUs)RrZ+LUfCv?OcVVnDYGk5:PJGfi(W5?j%FAP?\"\n        \"SIGxe%X\"\n        \"tZG]XYlexX$*DYYU1U(j&.Duu,Z[rZ0s<S[fHf:mi^Frm8MTl]<f5M^2KJi^B42J_FLi+`r,\"\n        \"0G`u;Kc`N'+DaR?b%bvrAYPXd^xbb/@MBhxe4fH]4GDPh`+`9=1`sX$auGmAUlJnbWuc\"\n        \"Rmu:d=4=VdxTR%kS+o7e`U4Sem1o4f&i4PfsUk1gwnKigIEi.h'=Hfh+U)Gi/\"\n        \"n`(j30A`j7Hx@k;aXxk?#:YlC;q:mGSQrmKl2SnO.j4oSFJloW_+Mp[wb.q`9CfqdQ$\"\n        \"GrhjZ(sl,<`s\"\n        \"pDs@tt]Sxtxu4Yu&2>##*DlY#.]L;$2u-s$67eS%:OE5&>h&m&B*^M'FB>/\"\n        \"(JZuf(NsUG)R57)*VMn`*ZfNA+_(0#,c@gY,gXG;-kq(s-o3`S.sK@5/wdwl/%'XM0)?9/\"\n        \"1-Wpf11pPG2\"\n        \"522)39Ji`3=cIA4A%+#5E=bY5IUB;6Mn#s6Q0ZS7UH;58Yarl8^#SM9b;4/\"\n        \":fSkf:jlKG;n.-)<rFd`<v_DA=$x%#>(:]Y>,R=;?0ktr?4-US@8E65A<^mlAx':oD]if(\"\n        \"Nj&^jMhl8N2\"\n        \"UjA,3lV^,3;Cw-Nan).NYt2.NZ$<.N[*E.N]0N.N^6W.N_<a.N`Bj.Nhs]/\"\n        \"Nf^j2M.NL99ER,x'_@e9DqB#x')^Aw%:GViFP0+cHwhrLF7gGW-PH[X(.UQSD>$<:)&=;\"\n        \"hFWJCL,u[BnD\"\n        \"Uf7:.+6O_I>?/9&-ZkVCE8VT.m&4RDH#vs-5Pw3N)?J'.@m<oMA/\"\n        \"^,MCGY)NDJPdMC8g,Ma?x/\"\n        \"McQXgMEMPdMcNFKMC>,HMT%DP-gdC]M@PD6MCYD6MFi%nMBY`QMaY`QMc``QME``QM\"\n        \"bSD6M&Maj$FTk.NYt2.N4w1r&j;A'GodiTCOaK29ggW%8=IJ88v7,dM=B[L2sDG&F:LOGHh(\"\n        \"LVCt>]:Cvl0g2VY#<-UvN71osWgC1%ofGmV(*HV'6P05F@EHi<XS2*@]3MM)uZ-qfFQ1\"\n        \"'M#<-,-SjLn`wuLZ$<.N4x*%$*U_qLfE.c$uOf'&9qlcEKdZkC*>DeE]9XB%>hISD:UH5*\"\n        \"B23gD9vV6aMDmEIU&thFxJrv7=hr8*9rN<BRPVQ2E5#c4&_nFH1rw+H%>VMFE`Rq.f%Sb#\"\n        \"C5Uh,s-879oYGEEsp%UC#BKKFW'S*$ANx*$ejt'%W@ViFE&H/\"\n        \":^sKG$u>^;-3?^;-MpSGMW0E.36jPq;J->7E6)TF4i]YD4g/'g2H?/\"\n        \"K2Gta.Npwd1MF0idMA6[l1>oVMF2p*A'9Y1g2\"\n        \";Dn]-jxQF%2XnLM%+NI3i`>W-nu?F%62,(5r-5t7B)a7D'EtNER[RF%phRF%&7SF%d?<g2@.\"\n        \"+:2.L*.NaVxb4b;gG3iV#<-fMSb-da(99Z#wh2T>*3NoLO3N:et3NENqoMjEq2B+)YVC\"\n        \"q&sXBJnCaNqrdL2^vAr--_1:)]i$OMeZoG;sd;,<w2SD=x;o`=&a0#?)&-v?\\?s07'jXVMF)\"\n        \"rNcH),`aH06EfOnj7dDV[=Z-wm/+%=t/+%A*0+%B-0+%F90+%H<'+%M0M.P<0jiOpji,D\"\n        \"Hp&pMUBg,MUkDv>2LHv$(Qp;-FQp;-N8.Q/\"\n        \"x':oD>N*.35XI,3X2bs%'wI:C,,F(&K2>U&e%VeG%^4rC#8A^&2/dW-st6C/\"\n        \"$61PMxR.9IGE&T&R/dW-57_h,DLTSMGRuG-/SuG-v,c%&\"\n        \"BLa:8/a/\"\n        \"g20$i]$'8$d3'8$d32iQ-3?[@m8u:ViFc4fjMqVPkMscckMw%2lMx()PM@[Lp7-7(\"\n        \"qLfwd29p9_M:&QOA>,7Hv$w:'C-4Qp;-;Qp;-=Qp;-AQp;-CW5W-B&mq27VN3N:et3N\"\n        \"GaQPN&37oDO=NU8VYjJ2_38$&na+9B[;AF4J25u%X^IUC-mgoD@53TB,ZkVC<P[=(9K6=(\"\n        \"Zx7s7Zx7s7^2fZ$n.?LF3A98Mg&bS8F[jJ2*W`M1x^nFHrxuhF0sY1F-^[%'DHa8&wt<R/\"\n        \"[6iTCU/-39t0(m912-v?[J39BZ5se3[/#H3OfsjN#U;KC>R9/\"\n        \"DQS>8JCx?)4uqNjVm-S79Xd&K2>4@LMv&8r81),d3g-)m$.=@LM`hIJM'51Q8G',d3`\"\n        \"IJs8R+5d3[JFF7,I*W-qnS,t\"\n        \"Z)3i2%f`=-lY#<-m`>W-irQF%LFQ^Qm$8r8jxxc34R%K22n9FItm9FItm9FItm9FIa1,-=Z/\"\n        \"N.3&*F,3&*F,3&*F,3&*F,3&*F,3'3bG3'3bG3'3bG3JJ(W-_K*RsJK*Rs'diwKc:m;-\"\n        \"&E+4%2[3.N'v2.N'_n#%2[3.Nuu2.Nuu2.Nuu2.NvtmL2JQ+d3v#'d3v#'d3v#'d3EY+-On-\"\n        \"S79c(,d3KWWU-vRuG-KWWU-(.iH-KWWU-(.iH-(.iH-9s6q2xtsWH`Yce3#RBeEO0W@8\"\n        \"Rw,x'xplx's]F.-_u):2-:@LMU`/4;Ocj=8/\"\n        \"PhG36&r[-'Iux'i6jG3'XS>-nLm<-oLm<-;0tZPa5fR9$p#d3@m'd3va&d3va&d3wg/\"\n        \"d3t]ebP;vrU86_G59qRA#eeFmt7,UaJ2.Sq;-\"\n        \"=E6.%iaMr&KGRR2^<u4:xe_$'B=r;-RoJ0%m_=p^gO2:8+SaJ2_=2W-omQb@C47W-4a4.-\"\n        \"nndnE]1[F%t#B)4t#B)4;%r;-gG8F-gG8F-gG8F-F1kB-hJ8F-/&+@8U>u`4*:tw9b#&gL\"\n        \"MwL*%oJ4.-s;q2DUZHL28VcJ2Pdo;-Nb^C-.K:@-.K:@-.K:@-0^qw-D^XN9kW:E4K0=gG7^\"\n        \"[%'9rN<BECsb$TT#w7x7f?7YsmL2bBs;-9C_S-=H:@-kH.j%+4*W-%s?F%?sJF4&8YY#\"\n        \"4=35&2[oi'(iX&#d*N+5OF6##`Q2uLI@-##lkVuLDDC2#`TGs-%@AvL7o-3#fTGs-5w=wL@\"\n        \"O*4#nTGs-BEuwLFta4#vTGs-RvhxLNNT5#'UGs-`DI#MU#?6#0UGs-qu<$M]M)7#5UGs-\"\n        \"X0W'M)e::#V15##P=#s-7wo(MG:-##sgJG2o8&=#r7>##orx=#Aqw@#<SGs-5nQiL5e'E#\"\n        \"cSGs-HR+oLI23G#wSGs-/^<rLh<DJ#ATGs-v0&vL5f-N#cTGs-PmUxLQd,Q#)UGs-eS[#M\"\n        \"mIAS#@4G>#6r^%MlUoS#CUGs-Pk)'M0H[V#]UGs-u,#)M5g3W#i6@m/\"\n        \"b8s<#-Y1Z#'SGs-eLFgLcvx[#8SGs-?3wiL3['a#IvgK-lieh%X(35&Q9t4J[?TT9-,FG)\"\n        \"4iZlE_eJ=B4g`f1\"\n        \"Yq$##X*wu#u`wC-C104;gSGs-8BPmLqOtx+Venv7_,tx+9vUnL9cSO;UF]&,?&`nLT@$##%;\"\n        \"cY#.cg81i$2eGcR8'FcWJ.#O[>hL:q.>-%lL5/Z7xJLvM;iL,L>gLY6E?#3*,##?)ofL\"\n        \"=dp%#5^/E#?([0#q`P+#-Wx5#KZA(M#_::#K6a'MDk<^M8nw6/\"\n        \"R-gE#K%v1MYAwW%rpV]+b8m.L_8v:df`r7e$.-Jh-n%Dj>26Vmd^Z(svu=##FHYJ(]rj]+\"\n        \"dI,v,jn(s-q<pJ1Hf's-\"\n        \"tNso%8V3ip'uq-$I(r-$RCr-$[/6`al?,ipk8k-$?^k-$P(`@k+8^:/\"\n        \"iSF>#%16fhQTDig)h%DjkW6#,=VnA,Af4L,qIqXu(fOatTwRJ171<,203cw'xwvu#\"\n        \"a7jV7GWsS&sYG^c'I2ci\"\n        \"o]Huc')Z^c6rErd>4Frdh/k-$*%BucsPk-$D/\"\n        \",ci>4-ciFhLLiZBk7n_U3SnT0?cr&VqV$gxP8%*CvV%HRP8/\"\n        \"*3Ik=xtRJ1ov1_A$.WV$AL[?TiMQ'Jx,l'&r0nO1cGbY#x,?;$JuD)@\"\n        \"]-NcM+l[4o16L1pEGF#$-FSx(sET(0+OwRRU_oRnc=n--AHA=#cQ;8vS7T;-4]cXQL2?hLC:\"\n        \"O<R'l?xk5R$Z$INMk#4$d-.@U(#v4ZC&N0r?xkKj0(&$Skxu+A:mSBx-qR.9R1g:L:#v\"\n        \"Nsi8ve4^V-1n$ktORf$N;Y,W-W>C(&&Ykxumku8.-&>uuXAAW8Ft1IMGaDN(FRBI$-N,\"\n        \"XCifxGMY7?uunIU2TeF6.N+D&F-/nCaN&Ji'#>Tl)Mwl.38S)h-mH[G&#AjuL,2`kxu-*?hL\"\n        \"FO@/&[EIgVub&F-.n;=-w_CH-s@pV-<V^d4J7aV(Ld;;;6&_DlgBRr7Ejw],Z8/\"\n        \"1$f#M*MKklgLmC)2T=k9xt'A,^0c_x;RU&qq.a5+##Ia.E>3qO4o3h<W-]1]q);vB;$&\"\n        \"WexuxKM]l\"\n        \"n>2QLjeh2)m>hp'1Tf).it0f$CEe,MUbm##*DkxuLA-+9oOWp^qk_m(6mjb7r1^wOQ(\"\n        \"wXlHSmN%I(WtQHYqn1VI89K@XcdMjMP&#>&(W-Vsf&Z),,ci9?=l$uRqAO8b<L/\"\n        \"dbKe9bP(*S\"\n        \"&d7@0B@eooNHYfrwRTQCaAojL64F4'PQmj#tE/\"\n        \"=-UfWf9.r<QFI=uxu<E@vn_DsR':RZxO0H]8+si4&'YFtc.Jr5Q:h)%k.V3C=Ocl?xk*S?W-\"\n        \"N`F']B=S9it+k9#%lDBMRS$MTxT9`W\"\n        \"V)X8&0%P&#Me_&O;&]iLQE@;#thGQ8BR&?7-r[(#d&Fk4];g/\"\n        \"Mm)uZ-uk=K<RIZuud8tB-]dh9MD0d<-N`ES:v6D3V?25)%lJ@FMc7(lL$6b<#1GVeNlr,<-[\"\n        \"-MENv1V2PktcT%-#^Du\"\n        \"P&xO]mpH>'B(/'?qcV^'n2fHMP9J/:Q_`b75-#l+.29c/\"\n        \"1#'58^Gv922Cai0Yaem0D1$##YAL_&+Bk;-:/XV0NXI%#+/\"\n        \"06v;thxL?QrhL]r0LM&8V'#6g#X$;&$&MXixo7iv#Z$VRs.r\"\n        \"PFtY-m5[&#)at?0kK%W%0VWcs;9]Y-Uw?F.xOkxufIp;-FG5s-^_kgLnW4?-r%9;-Mf4p7[+\"\n        \"@['4Max'f#M*ME9V'#u(B;-dJ2^'KO7j`F'em8SeF&#0'M&#<+r;-Obto&,t8X-,?T*I\"\n        \"j1a=crC@MqYrSQCA$Hi(C=Oh#EH=_/\"\n        \"C.3F%$lsL>l9Wm8CQ7`]4B+##'(#.6BA$s$6x60Mv1$##s#9m'[T.m0.l+AuvLkxuD=u19,\"\n        \"UF&#8UO_f5;g&Q%`']81tJu()b8fdIBxDG2OsUm\"\n        \"HI0fqP-mxuSecp'@3f;-YPI#%f_X18;_kxuvqcW-g-LuV-&+#v<sduO=Iv:<7kkxuL+B;-\"\n        \"E1:1M@T#<-bQER%E>dG*&m-/rEw/XUXB>@'K/Y._VGCwTUEsL^?DZX-Kev'/LYp-Md@(m8\"\n        \"'S@&,^sR`&#9s,:/X1#vvr$8#,a^s-WK<;NRm('#L`U,NSVp;-%/\"\n        \">T7Xlqxu=)1e8f+NQ1QRQ##v?DY&j*AX-]bL@9eC)W--FCk`S2'%PRw^-PiP]BN,--e%K*m+\"\n        \"%lHp+sFEo;-ICJ7M\"\n        \"Q_Q8.u.(v#LaW8&6pagL3UY,M;_;=-L[pgL?;K;-$mOKM,Ec9R?2;P-Q?J>-DV:a%<9Xs-j@\"\n        \"XGMf77>'sS)e4LC?##wQ5C/HUtkO,mMXoiN>rdmBSs-df)s<B#cxu0cZ<-`$@q$CFk@-\"\n        \"Bfq@-$c.j's-k59$;$:piI^w%N>gkLuiJ+M?YT9v4hM=-/\"\n        \"VS$(L($*NIM=%tUk)*Ndw;g:LEB&v?mneMm.Y58gIV?@SG,33r2gW-@3a8p51se.ia;<#+3/\"\n        \"_%^XpxuU4hK-,Z&$MW@w#'\"\n        \".Fn58t6>?.6%gGMIVu%MXMYGM+>^;-d%OZ%u0.4$?iQ;QgW,l&:HrxuP^OD)iul<-*8ie((\"\n        \"Be,OL-&>-D#<G=wklxujA^kM#4A(s3@$Z$+Ln;-6`Ys-5Tu>>L0]5'$c&g%GIOh#p_LxL\"\n        \"h;>xuwMN`<oWkxuXPZZ>qA5R*7j/\"\n        \"hLCZ@D*il)J%75I['Ie-W]H_Y-M1?VhLcl5d.FKb&#.rCq'=jbp#Pm1eQ2b;<#%bm_&=(\"\n        \"qgLQD,h%EXEPp,:<j9cru`4aT:i#CZdWA,sA]Yp`i>(\"\n        \"g'8RLM=?uuM:gwOZ2F<)TA_pTW>p<QGq0P'IRwQ88U1@0'R=MqB5ZqT],B@PLm@o[0((#\"\n        \"vmt*H90l9#vN(>uu.&).M<1@xkVJ^@k/cErdF0[a.p,/9vos.>-Qc,9'6^K%M3JG8.EK^+V\"\n        \"0YNh#Ec)'MvZN;*ZqYVR;v8jiAb:END&oj%6FZ&Q'?#p%_o###2Pds-(u>q7p_'#vx8ac8>\"\n        \"Kc'&[^o-$RQfrnYB?q`I&Z3t[<G)M1>a*M>M99vxK/(Mmr)$#87V9._7>##Ak7q7lXm+s\"\n        \"Fi-<-bmL?\\?cUV'8P$@dM5J.`aHq5J$.h&396Uv.mI[Is-lO^%O;19?.MbV+`K_eW-S<H4+:\"\n        \"^@S<VTDG%u@Dp.[vwXu;<K_&92_@kK@-KWGd)fqj]f,MAl?xk-9vY-[fFm#rDvWo[bm##\"\n        \"N.[w%Hsck-mDF3)UM9i^6ZlKPP1Y1g7oq;-50;t-n@<jLw#axLDX[&M+tU6vx`_7#5rC$#\"\n        \"GL`t-VDLq8bkX&#]Ru?9EULk+)rk'$*QtV$sUcgLPi&,MJ,,ci>Fge$&]O&#Ii&gLf4tZ%\"\n        \".,rJs2.gfLr['4.<@%0:loB#$`b>#v1No5#d3E3&wQDgLxcxo7+1pq)We$W]B@S(WJh$ktC(\"\n        \"DqitDAP8'M?x0evUEMF1m<-A^4?-1OUQ8^g_$'9pPe6gPo,M'OQxLN6Buc6^BvPKl)fq\"\n        \"ww^xB.YmW-]K<1#;L*R8o6V)+=sNI-gp?lF,Lm,M'e`.M_OC29+(973d4d;-*[`UN*G?##'\"\n        \"r,3?a:%w[KrT=uegZ&#Nmt'&3xoe$F&>uuWfE7'wU52$.Qu8R:Q,IVnZ)+&1lvW-`OM9B\"\n        \">v5pLI^rX-4T1kDkxhUR,7/\"\n        \"W8=(Kv1ZjKe=tQO%*fp-J$k219'<,kVM9L?uuJEd;#6'5DMZQtG%_`sxu*7)=-`u%'.6p>+<\"\n        \")@g;.q.M_&Th](MN?VhLNm3<#1R_&#o0Lk+A,(v#k,c'8\"\n        \".do?9>?<%tvLi9.uwni'prVv@xqJfLpYJwupf5W-DcSk+]DAO+/<eAe/\"\n        \"XPgL47,,M(_T._1(%@0]Q95&'dnxu+m@u-b)b?MfNwf:xEL@0^<XvPaaI;R?^P<RM''1:bs/\"\n        \"/-G#(-k?hQ29\"\n        \"p(.@'>:th#HFr+;Dhc+%S@]%''O&$Pnw$n8(ZO&#Tirn9x*n>6[7f+MRFdI'6[OW88et)#\"\n        \"hdSn*RQb@Rl*wI'EZ59RvQ-ipcFJfL+5Buc;XC#$vOX&#u1#XJ0(,)'srwFML4E$#0[f_M\"\n        \"uQB_MCja>&5uwR/\"\n        \"%w%M^cbN4o6iB,Mo6L1p&8E,s$]ZS%`E4@*98W.$(qam8>PG']=cq9;V1pv7;nqE'L_t-$\"\n        \"34&NYR'C.$FHcp'3;<</):LLG%Sbxu&kF9.';G##4D=#vc(>uu`ATo%\"\n        \"Ljt?000k-$>N<dt9w(hL[m/49dq_$'/[HgLdtqi9;)rti/&)eZe&&Z$<Y)EO.<]CsH*#/\"\n        \"r-ukxu'4o7&(nZ*W@=emh;v`Fr`PKEND=L1p;jF_d,(1A(CDsP_Sem##K2t@,CBGe8GLc'&\"\n        \"2),F@LvgS8*t]gONSgd'r'=E>$kAA0%*%k#$]4%0;-TT.hmM<#0#1W$&uUJ%q6'=Q]PJG&\"\n        \"sOc5rK_2#&o2Op7J*D($(:#gLYug=>SE0pp6;Hj=9wZXffpH:&m?dt?i:790B,/hYE;]Cs\"\n        \"^TX=-,/?T%9$eCsNEF]=nbBYAP:aV(kX/\"\n        \"WoQ9mhLawRb%q`R?>+0mxu.exr%2b;<#1U-gLmM)7#N/\"\n        \"Nm$Di.1vsk^%ML2m<-GF#-Mt5Q6#X:d^9/IpEuM7?uux)Im%-eTx74.gfLa]OO8\"\n        \"<rl^?9*w=PXPwu%uK<1#9H#MT:m#K)lS^e$74Ki#5S(J:/\"\n        \"DS^-P^.JM18Re%skd;-.46]%Y-CG).Np39D$vjDIVD6M/ZT0'K0H/\"\n        \"$PpV&Z;C:p7iCEX(]IVe69@OWoSQIFNtQ%;'%6*O8\"\n        \";%Mk+GH<%tp[72'Mf19B2g2E-6hI:.(AP##iBj;-/M#<-qcZ6-Za?j#fd*@9+N/\"\n        \"E>(9v%.TC=gL_7RF'GDtgLFWA,&2O<N9:fm+s2iL.M`PtL;h9<X(POj78?>2RU;=qwLhAK;-\"\n        \"=I,HM\"\n        \"9P,*8)%[w'%>7X->N?A.H[k4''^%J$$B8H:V$E3V(Gd:8QQ*I-RukgL=3[P8dp*Lu^X5$>+\"\n        \"Jr_ZJeR[=GDd9MQnb2vnn77*L)DI*VQk@b+7X]4TjGuc7N0A=SVkxu%p6m1L^F2vE:Yuu\"\n        \"@VS=#I@uwL5S(%Mh[-lL-af#MaVZ)MZHFA=-6p6*B1pV.3o/\"\n        \".$VsiuL--d2v^=UB=Y%L#vh#x%#D<G)MHLU58CGqKGht]w'xIkxuEHgS'/06x-J,/\"\n        \"58%nL(&H'<E#J7?uuOoM<?5[Wt(\"\n        \"SZA,3Le1p/\"\n        \"BncJ$#<7v(YYo'H7eU[%Z68K<RQe'OS7oS@o&QFY(RD:':Q]EN$ji6MXXZuu$Vl##L/\"\n        \"3.F1Dr-%,rpPAcAUw0dW:.$FpB6VmMtcNNIZuub+8'.-HcDM:/f#/hJ+##R3q?B\"\n        \"9?198DSkxud+B;-9lF58^Vkxu8&tnLn#*bMPlk`<a7Lk+Adk-$;4G/Xmw6M&ssS<-hB></\"\n        \"s:YuuaU[bM31f?*^C9E:s[A3M8f=(.)Ib8PaXgR&Y)e^6Om@o[Ogq-$[I/'-naof$S+e?I\"\n        \"JA]Cs=a#rBI+qXu(dRk+Iu>xtZR_]lMl3X/\"\n        \"hdQT-7Uq1MudX&=?Q$Z$GLk-$]-eDcJ$V*ND%qXu/\"\n        \"1ZGMs*,MA&B*9'L8>d$k$8X-+eTwKU.RS%s&m-$49`&#k24L#+-@K<p@s[>]kL_&\"\n        \"6DDW-Bf)L>?-E%t]gJlS$lX1gfI.9.,EBulBJ*E<6TBh5>O5Qqxt]+Mohr>&Xh?X?@`X&#[\"\n        \"VxC8Is*-tJCsUm]Ivl4J1tS(R-DIM*k1HM/c#W-gBXEn)D6<-7xR3'Vs'o8-weS<NC_%'\"\n        \"druk4]2N#Lt>VR%bo^HM9.)Y(WKY@-MC(29U60Z-+nh0,2>tx+xUX&#(5_e$,B.;6k$Pp8^>\"\n        \"df(4ojh#%J]o@@U>f$#2ocE4uX&#&Y%@'L_-hL15K;-hrA,M*bl,M&M5s-AfjRML4$##\"\n        \"-mYs-KE5LMa6<MM7u8,MfWqSM0jF1#PJiMMb<`;-U.jV85PTE>9ar3U@n1^+l3H@7ns@jL1?\"\n        \"VhLT9?DNqhX&=cib&#D#M*MmOAbM#>a*MIlg9vwMHJ9doB#$U8`lgZiLk+0)oxuc;N;Q\"\n        \"xVgW$UZWe-cu.rgW8P9Ro8ZxN^%qXu75,ENXQZQ'R-CwK9jOFN=#G_(ADh8.kS+##g^+po1)\"\n        \"V4oj`19.Jd3cimj]oothp(k%d%j#_%vm8(Ia/4pF1LM2LYGMRwWC&?TjHML%vS.H&g@k\"\n        \"l?8cO`0co&w>Op7tY6L,D8Erd&9(QUs>R1g22IwKY<L1p8mli'P7g'&7U;k27@@xkB>$\"\n        \"VmaJ+cihJ+##Bvo-$^oKe-JA-F%Doq-$ko+qTXNP8&s@cHMPP]R0rVo9v.(>uu<Qn>M[645&\"\n        \"-kR5',oO&#RAg;-6Aq^=Hh'#vn0/\"\n        \"vL1DgV-UnIkM,gps$1MqB<6q0#v)uC$#:JoxuTqWB-bCT;-M1IA-wFg;-D1Og(#WXp7-f/\"\n        \"q'2/.G<bdQF#2:#gLiFo(<ZYWX(P7x],YS42$P<seM\"\n        \"+B]Cs=u4qr*lZeMY4^GM%8wb<lC:O+b-[.MhPHJU)+fJMZ(Cd'+lS1p>%*\"\n        \"KahfOkLsZT9vFLRB&H[p*%I+s^MCbE4#inWB-Ne1p.f2OxuvgIib?6L1plQVkbB;FL<:UiX-\"\n        \"m0_k+:&Aib\"\n        \"@iK`$pG/\"\n        \"gL*Le1%R,qXCIPb-?0qe5'Y7ib%E7uj)3whHPocSCPa3B<Q8Ii68N*'NG-2-<-xF*]$b)wV?\"\n        \"0SEQ1Yigd'`=rW-e)TjMnIUlL+H?:8=cF&#H+MsBF`7N'gfs$B>LeQ8$Q4m'\"\n        \"1USj9mwaJ2;0<99IQrhLOqIiLJ:$##l2pKMt+98[w`.Fa@bxU&kmd,OlJ6I%)$'<-+J6DF;\"\n        \"dWt($DbxuN3bj9L/[=1>kU_$M/->>fO]^-)YU1MHM=%tMM9-Muq,p7g=KE#-RC`NBjNu&\"\n        \"iMRfLo;]Cs:SEq'iF+ONMrDmM<`fVN`u#<-?4v<-H4#dM;b:e&jv)#v]71^:o]O&#7=^gL>>\"\n        \"cCj.EQV[cN(.$i=p.C1?hL3D*@q7Mi1p/q]TjLP=AW8(YwA.Ak95'_8&:2srwFM2<,9'\"\n        \"T)v(3qDMV$2/\"\n        \"DkOfxS9@bUgp9Zg6NpE+eY'^%cgLrBVM9YmqDl@1p_7OSP-4Z,mZ1_A'v#i0K1pwloXu.Z0`\"\n        \"dXW02'uTbw'bS?O=,Hc'&h-s-$E[(&+S]sx+WUtY-Y_+',TiCulu$;J:\"\n        \"uJ*dt3[^C-:.:1M*m$1'FbKi#3/\"\n        \"J&#FH99.phni'8s*XUY;uQ8gO@@[N3I*.YCQc;4@+AupJPYdx:B_Z#6[W^-7_](_Y,9^IJ-\"\n        \"MT@b0,&S5[S+=#SDWpL4`W.IEG#PH]>-@7^,M5qO4o\"\n        \"AEWX(e*jYPDVXg&F####/Och%'<xTC/aeO.*$P8$B/\"\n        \"T:Df'[=B=rFS7&(,k;Ah(b*)X$t'7Igk$FvF:vdRLF;oeQ-H9eV8vu(>uun.X$M87cCNv#\"\n        \"X7N9Vp;-enU&Ol0<,+`tEY-4-I(8\"\n        \"E:6Z$ZaW0->adG*<<$*]4$J4$b9V'8fgf<-:1UV-ng$`$mN8c(dHmxu-oG&)14+eb8$-[cG%\"\n        \"s,/,xm8&Pdl6WS@7QI7=C#$?eC#vw(>uu`L^.=`YGxPV/r]$`6L@.3Es^%m>xw'$TR&#\"\n        \"gn+>])PpZeGbX1+-;-EG+QBi^=PBcN0fm##=4)t-v?%0:HWgJ)1'f5'U:N2i/rY<-v/\"\n        \"p1:ns?['uIkxu6A(@-Ixqa%3*TW&TJp@P`[vA)'er>n>v3+N*^5p8^Px],=5>)N+7F/\"\n        \"U2Vdr7\"\n        \"rOXJgRY=fC>%DhHEhP&#j1f$(]`cA)s?*1#3ss,Mv)T%)&Wk5rVS40&]``SUV[5X,\"\n        \"48Sh52NgJ)_*J39N$c5r3gD7#a]W48mdb5rF`T9vEDQ5Jt:N*I<ncER8T@:`.V?Xq#2kw0]\"\n        \"v<b*\"\n        \">.g0UK))h*K1n1MlpLoIP)AjMX#4GN@55w&`FN-;-_kxu+%f3'lJtdM&G,H&CxU68NYF&#\"\n        \"7I#;;F&>uuF#EW3wC2=-o<9C-k7T;-(:GD'0;nF>4FO.]mcve%Y2EK*%L2@det(`K(-X8p\"\n        \"XodQPLBC58pZ@`_Y:@j`#H[GW:PxjLKV`P-UE'D&NwBi#Z<]<-/\"\n        \"YhR]9#:X'J&>uu8,R4veGnDc5dm_e-B&o':9-9'-qcCQfmSQ`Qqv:($Q-I;%Ea'\"\n        \"QsSp59rnLO+mTU)/$Ja&=+&dp^\"\n        \"1j4=::[Q.m<*j<#wuF:vG-0rgp:tZ%0<]*NAu_2997G(/Ako=>B%pU)7-/9vRd+m/\"\n        \"xM(OjOuog%CH<%tUFJwKvDO`Eu?J'J'Yek=g('_$J5&O8%W8gmxMI&#pl+W%)Mj+MX1^\"\n        \"gLLA]Cs\"\n        \"SUB?%^U;mgd@p8'0VUk=v_6q7cHs*.ti9#vuu+>#Tg$Q'suvp'/\"\n        \"`^5+Ze2L5Jf6[Pmlr3U@R0P'<heaND92X%OJEx>n]ls0WB^kMJ;)P-F6;.MJ[a&-F1XnJJ*^\"\n        \"q)ek$p*[nH-Mrcl,M\"\n        \"-jYSIK6,V)?B(Ka%g[a*5P-<-fQ+Y$o=a-?YF35&F%.<-ineT%<5ZS%;H,@'m0Ex.@m3<#\"\n        \"ohmF#0-CAQkal&>U26EP>scl&[Li<-GX+8M0[BC:-,73M5lsV-j*b5r-x:T.hmM<#hgbY$\"\n        \"3@C#$4R*<-<rY<-1`/\"\n        \",MHt1-MCEo(<3UQY873&C%Q2THMjr^;-_@4S&afZ9RK3Pd(D7g,&%Qap/kT(fqXff/\"\n        \"&?W?IYK7?uu&m,P0Od;<#11]1v=M0%MDRKC-?fG<-TnchLQf[qI.)PI>\"\n        \"R4U0M95T;-^Sv:8CucG*Zj0WhB%co7t_q]5)i/C/@Sw],J<V,%J#O.hhOTY,r=F&#Q+/\"\n        \"F.k_,_ftw2<-4FD=-=DmpL[2<uls2HeQJ(lJa3k1HMR4$##x@@b%%Ywk=PKZj#5J;ul(8K,M\"\n        \",t+?-j3*/\"\n        \"'trjv%)+B.?qWZJ9EDf(8Grn['*`#7vV(>uuGT-iLZ9?uuM;VIMI0$l$Hd8xti`'v#Un.<-\"\n        \"NRSZ$c:0,jT@Q.4uf/fh>,28faoA'#O0qOfk[rUmLF&H;7?#ito$W+O%<bS(\"\n        \"?CY&#s=r$'#6W,M,:#gLcCoe'v9L&#N.T&#4V@&'aH(.$7j+ciaIuGM7#/\"\n        \"a*OO)c*T5_s-36g,L(aP'?R2XG'=[abNngK)(+tdF<?3@['-Dr>nMKU]&`S64%e1Z68?vq%\"\n        \"KT2g_%XX*KN\"\n        \"TF?##LDPd$Go;'MKkw&M6#F<#@gI#$G'V#$#V7@'x)R&#:cvT%<l*^dW+2OCEhjM,LN1wI(,\"\n        \".j'&bhxu3pZt:0R;8v[dWr$*ip:#uei4#/Ag;-=7T;-j[lS._M]9vOBDX-uC%@'7D72'\"\n        \"hII.m#C[>M$r.>-%P$9%Gp/\"\n        \">mREmf$aZu)Mp_&+F`YJn3-):m8,^u^]pf1L,29],Mvb9b*1Zgs-2,b?M4MpV-bIZ'S$a)x'\"\n        \"?9Os-4Qc)M`<kBM=3=r.wx+##03B-mL2i=cLY'gL3[Yh8\"\n        \"s&%@'Tr2f;Zpkxu1he5&$a9X:D#@@^x;9<-am3YJ-5HU89tVq2]MI'SkMa+-=Q-ip:\"\n        \"53GMvU>rdk$S[o22)q%/\"\n        \"ZCr7uC=,m$>.pISIO-ZK[E,m&Ji'#[Wn#MKBl#+N$N.MM5T;-S%,)N\"\n        \"N)xO];765Tpx.?-e*lkL75ClSq[38f%G`s-fWA(MJ$qCM^*h=Y>_mAI@;]CsqJCQpBWjCj]\"\n        \"NdCsE*s-$q.Tk+i8;KE403YP'7$gL`:.&M'9gV-<dCX(F=Nb%&b0L3AH-ip*xhiUso>]8\"\n        \">h_&O4iiU%gh*@T+YrL,ir;r7=c->m-<gs-Y0Q^9Cekxu$+;O_2qo@T7N6ZAHl-u7X(@&\"\n        \"0M6(L:l9R>:gSD)+O#qWQMue(MNSax&8.=Q(L<)1&go-<-oc-T-f'=c/4BQiXQ%](#mi^%M\"\n        \"7)b8vHNTX9>ePiX-Xc3H$B@MhsbE=-UePlMJkb+&L3mf$kXIQ8u,ge$LPUV$=\"\n        \"SUw0sYx32CkLEN,0.EN[_1Q8kHG&#llOHMW$U`MkZ&d;RYqL5PD(ENjRLE'/\"\n        \"s.;)Q:<Rs9qi8vVX1vu\"\n        \"o5`eMhhF))XrLs-Bm:*M8#1K-acUqRvQDY&(K#Qqsak`))Np<-@Kr.&mpxr)`BT0K.]s+jH#\"\n        \"CpTRn8m@pIAb.EJkxuW6mt&f*sW/v:vq8$Ts9)opt7IRpF&#ZY/#vd&>`MPmw7vbX*9#\"\n        \"['Q7v>UGs-,WA(Mm=a*MDX[&M+6I7vTu>t$:I,ip,7)=-Wpc]'nR:xLq5/b-KB*1#OI/\"\n        \"(M?j+U_N%>G-9:sY$RQ/n(_:`s-E,iEMn0o;'CRK.$u;dEPYR>_Hk184vvvUJ:i^kxuHf@0r\"\n        \"3k#'$<1T,M(Q_B%.Z?EMu?rV-bRD9Bc`n%+S-+9.GnpOfsZK%'/\"\n        \"gnxuuH.q.Yk4ip55FQU8T3YPp>E%tf'K1pk=X1gswg,jFeT4f'JErd>UY&#[R*@'c#Cul#,-\"\n        \"F%oH>_/DfG.M7`h8K\"\n        \"C;'9v7*m<-=L+WSE?S>-l'T,MM&co7rg'#vAXx;M7GpV-X^.RU)>Bb4?qt+s[$edM,E&Y%@\"\n        \"C+Z&KI&s([8jk#J6'>-cJ5g*-b8<-IeJV%N3$j#uI2ENIeoRn5-*.bP:S(WLK.$vkEluu\"\n        \"O6d7v,B17#vWda$u)Q&'Fb=@'89Lv^.hU:#>BG#1*I1_A<I@o8kMJ'JfW1x[fX<D@WOJrL)\"\n        \"ME/%+J#l(1tOv'=;^GMY6/Z$fHjIDVYF&#a4J^dXH%fMMl#e'Yt<iM.,'gLG<I*.l9jw=\"\n        \"g*SjiGj5l0f'K1pHf08.5XJM'a,+R0XZOP&-v:D3,-0A=8#ZK)'Obr?Ke1GDHF'(,XhdxF^,\"\n        \"BYGcGsu,oS6MK:/D4L%.ei0.u+DN/-`%Oj%%)3Z>i7eq+r58u*b9#q;':#<[$0#<eR%#\"\n        \"0AXA#iR+Q#Tf/\"\n        \"*#;'^E#FvaT#=dX.#%U$K#UIJX#f&8P#6=]P#KMkA#OCeS#B2-&M,FOD#QgJG22sWT#9FaD#\"\n        \"1[^U#FfpU#xs>Z%H@#d)0.TD*l@w1K%dh`36QLA4QL7MTNq#s6]U^S7\"\n        \"bQKcV(J:;$AD?G2?o(.$SNViBK#vPBwV4L#5V3'o>]F59T11L;&qYlA*rQ`<1gn+De,?X(;\"\n        \"aQ>6ZIU-<A-l.L%o2A=62k(ENHdj#3,Xo*%cH]F9BxP'u1B;-.LRm/&+2,#6gb.#N.fu-\"\n        \"*HwqL1F=)#9Us-$gXE_&IHPP&vC58.*]qr$#(ei0VK3L#ik(58nru'&Z_-##F*,##wEX&#M`\"\n        \"($#a.4&#_(+&#VYu##Bww%#f;G##gpB'#7l:$#b8q'#1Y5<-<er=-73K01gXI%#?Mc##\"\n        \"FF.%#lSGs-DFKsL?fcgLCApV-%ZL(&&:U:@%vt&#+vv(#ko&gLSmI>/\"\n        \"h]Q(#+(trL@ZZ##P$J^.+;G##=aoF-8`oF-e+$L-3wgK-4M#<-e-OJ-A.PG-oMTp*^]R-d(\"\n        \"Hg3kPr#T&0Ia/)\"\n        \"&)WEeHdR50QAS_&46*XC<nfQabUF.$t+18./\"\n        \"cHd3:1VV$iK*g2%S:9&_.9>,2(&@'X=no%g5a>$3#Y5'iBm#$gx@D*q_iJ)tRK#$C.^-HL>)\"\n        \":2xA(:DoHG_/Ic8F%V]L/)Uv/T&.4e-H\"\n        \"59*XCq:HP/].l;%MML@-/fG<-@L5-M]VT;-&>eA-E[%,85xqP0h/\"\n        \"eT-:x^4.BStGMS`8%#C0?D-1ZL&Q+JO?Qo8--N)u2$#][@f6qnrr$-L?']H#l-$tVsx+]\"\n        \"LP509.ZW%u[^l8qD4L#\"\n        \"`V7A43,v(3$VF&#HORs-bw9hLnKf0#2AU/#hfG<-rb%jL0Ag;-BGp;-[_Y-M1F)/\"\n        \"#>e[%#>_?iLcpS%#der=-D`/,Mu'^fLN@6##J@FV.]/\"\n        \"5##Rb%jL0Ag;-7Gp;-FX@U.%####rlUH-\"\n        \"7fG<-O]8;/O`R%#hKNjL[Fp-#I)B;-U3S>-+pk0M6[WmL%qr/#tMh/\"\n        \"#KSs+ML6$##[lls-2LTsLrgs,#cV3B-iB^$.;/\"\n        \"RqL@#;'#VkeV@`el]GsoB,3xOF&#49;m9+KQ'AQQx;-idF?-\"\n        \"dYlS.+_''#1V@+%b*kEIgkX59<>_?TCIb?K/\"\n        \"fLS.).dm#(Ow.:Kn,87pFm%=Q<)58l`=J:.PhR3uo);?dx$B51iu+;hexS8WhMe$\"\n        \"pimY6JI8pAj/_w'.YFcMv?c(NJZU*#TPj)#1CV,#\"\n        \".Oi,#E[b;.uj9'#NBE/\"\n        \"2[)'021YYY#X^j-$*HcF%AWL@'_uUk+1H[`*>+5JCIFp.CVl8>,-xmo%uLbA#$%?v$73f5'\"\n        \"7O=X(U(78%sFbA#A>2h)Ae35&GW(,);iLe$t'McD:lKv$w7B;-\"\n        \"*l5<-7%KgL>c(p.e^''#*tfe$<)b-?aVkKP;BIAG-TrcE;nZG<+#+^#U),H3m//\"\n        \"DEsT#jC'3uKP20T-HL3/eQw]I/$x(f+MAa:)NZkG<-2fG<-^Ibn-UV/\"\n        \"eZ93Lkb,rtA#spfQavx9p&\"\n        \";D[8pF*l<%p?_-6u/\"\n        \"%)*1nl3+KgAQ(.klgLp[_p7XVV8&.90N(0wLk+)JFcM<7PG-^1@A-5er=-I?xu-x<6eMwV(\"\n        \"w-o'ChLa<L1MoPBcNx:-##bROONsxQMM:C:p7kf'^#58nA,vY?EM\"\n        \"_5<j$cPf9Vno)A91Q=p&Y]#^OD55GMW5$##JM.Q-IdZq&Suw-$i8n9Mwf]E[cg;A+ZW3L#'\"\n        \"1wu#8*i`FR^sJ2::W_&SV_e$4Tpe$7xho.dZ=G2CW.&GL(x],R6YmB3/[o@UH3L#%4f'&\"\n        \"/M5;-R-iP0*=vV%I=g;.9B`k+x$]f1QUT;.xJOA>_o?^5qEX`3)G5W%qA)L5qSou,hDLM'\"\n        \"0po`=LsoP'XGpP'MOT;.WA1a4)uQ-HwOwWU3l[c2Cl*^,LB7I-?Y`=-Jgi&.?1SnL,]U).\"\n        \"AEaJM%?+jLQ8xiLRxIQMV2q4.AC@fN^sJfL)qugLYPFjL$WP&#5p/\"\n        \"kLL4$##Y[oR00l:$#2a($#14RqLTH_w.dW6crj&3`sn>j@trVJxt;^2&+[u&#,,f487aiY`<\"\n        \"ubV]==`5AF[w2>G\"\n        \"a<juG+RDVQLsASRe9(>Y+d<S[5>9P]mnF;62OIq;(mq(#:rs1#?(02#E:K2#JLg2#P_,3#\"\n        \"WwP3#^-d3#c?)4#jQD4#od`4#tpr4#$-85#)?S5#/Qo5#6j=6#<vO6#A2l6#HD17#+C0:#\"\n        \";6H;#1,@S#cInS#q0tT#FHmV#Oa;W#v[1Z#Fkw[#AEEjLBRKvL/\"\n        \"2DpNY6J8%N5>##Lx26.A0xfL(@,gL5_7:.`Mq'#OCvV%hV=s%*rh=2)_HwB6aS:DN&###=\"\n        \"nX]4vQ<)<`e^&#Zk`e$\"\n        \"&xVe$JG:;$O-T,Mv`EMg<L#/\"\n        \"$.&[mMi9vfCUpmcE'(-Z$Uac8.DBfYG8(Te$DOT8MK7$##2WKF%.ug>$Y)vW-:^Xe$7Q'@\"\n        \"MK7$##D8LF%.ug>$tMV9.3R8/U.7h>$)-#dMI9$#Y@XGf$\"\n        \"NL=sO7xcW-dCVF%/PMF%'(-Z$Oac8.7#cf_8(Te$7W9@M4%co7U,h>$H[aG)t3a_/\"\n        \".ug>$vvldM8X/;m.IH?$hI:;$U*:-M3r1p.t`+##dlZ@'?FQZ$*PUV$'>pV-J5n0#l,/\"\n        \"Z$VNwBO\"\n        \"rQ[5/PE.D&C_(iOwiP;HL'`_&AnDe->-3fM0i(T.H.$##q^#`-:4/\"\n        \"@'.r#Z$2p9#P@JE#55rX&#()xE7)K7;)*3mx'9)0d<[eMe$p3uS@OR]PBnt=_/\"\n        \"Jg`;)PuS;)G^JQ(o<iq7`vR_8\"\n        \"ZX@=--ma4T`iE?%t*]W&?_;=-#vldM<#,sQd<C`/\"\n        \"q=W@'p[qTN3l>W-b[3`&H^Y]YDelF%.0K8OuJts7EZQZ$:RUV$jCk(Ooa>;dRW@)&O</\"\n        \"^OJmf(%E:[c;]/F>#F1Ve$E_lb#j4G>#\"\n        \"xGg;-j:)=-fIg;-=)^GMQ0(8M.__%OW*GDONmi`FRQaJ2`lH#6eGW'8CKY_&h;-:2Xf_e$f*\"\n        \"PX(F2he$4PH;@'VF]u(,H;@u>u1qQHE/2LqhP0xIF&#EKsS&'M:p&@5)d**u0^#n:)=-\"\n        \"E:)=-&Ig;-Qw<R/MoY;$OWrIhj;F/2d)F/\"\n        \"2I1tY-j=XfixLbxu,nhxu54cA#F0@v$J:9v-PsCPgRRjfM3d^ooE2^VIX?iP0SZ&g2Y;u`\"\n        \"4I%<#6>HIAGssrc<#Tk]>xOO&#[QKC-9Gg;-\"\n        \"JGg;-AGg;-&-BOM3.$YuPL9v-D)B;IVd.Z$b4'w%>R[Y#*RwF`=^v%+B8tK3ocfk(ke.T.\"\n        \"UF9:#4F3#.bx?qL,/AqL%^FRiO@-###QNjL'sJfLcH6##sdHiL6ZPgLC(2hLI:-##vl'S-\"\n        \"6^c8.%####8@uA#ZVYw0lqToex5K9r6F(B#w:@x*niit--mWrLMOd.#W'dH=Qape;(^H&#?\"\n        \"cj=8%it&##)>>#=`b?/(5>##bFFgL.`YgLf3E$#xp7$PKdJ;Q7^#&#Unc)>t-Pi;4vH&#\"\n        \"`sUnLF%@J=M+F_&bJtS&G)^N>-@KM>h0`;%Xt?v$o_*5((>Z1F_e:eGPQ4GDm<v<\"\n        \"BtNvhFnet4JM%72LS[/,NY<(&PmlAwQ<u%fGLJ9fGC*`(WIVeQ/\"\n        \"^:$,H33NrZTpS,3d7Qe$BCo@b\"\n        \"E:]+H=>78McwN^Ma(]iLT_H##9l:$#$kmIMQVkJM*_uGM'VY8.-xmo%$ctA#]vJe$OGuu#,]\"\n        \"UV$Io[A,CusY-E%b#-*>>>#/c/I$MX5W.Run+MU:-##JcPW-VW/I$:w)JMqsW>-BWo(<\"\n        \":0WrQMftFiBdX`<T@J`WW7(s-/\"\n        \"Ju._Be55f`1[2LdbYS[wQ)ejol9Z#>?fG;N-@5]:w`3F.YGW%onUf:(C2W%d(<X(:sc'&#\"\n        \"ap-$tre+MBH4v->PxVM0Dg;->Kx>-cD[:Me?VhLjD;t-\"\n        \"RTOOMvPwtL^AQD-nGU[-Y(Ik=nr98.bJ8<R>x>>#[WBD3)$UYZ*o'^#kXH,35cZD4;ZXQ'^\"\n        \"Ilr-'@KfLIDlCjE1>;RXrScj'tTYZ(0V;.2FOw9v(+GMAaSrQRnMY:bH>_JfI&r2Gf+^#\"\n        \"(i'^#)%l3=-dtP'[P2E+j0.5/xujcj+ZD_Alds7Rg+<>Z;xH-Z)lkA#*.CPN/YZ##7`b?/\"\n        \"&5G>#mhtgLLs,P0WP?(#=;YY#@dm`*LUPv-)VVmLK9^@#dU*T#46QV#EBdV#DH>q%)4Cfq\"\n        \"5L8P&Fs4YuN-vo76,fA#i@xY-+xtA#rv=pgSL6##o`R%#2YlS.Mj9'#c'A>-E6T;-;5`P-\"\n        \"X3_S-U.m<-SArT.(AP##LW4?-C@&j-KovW_H2>>#KwGF%/8s9)wUtA#A9DF7*TC(&:@cA#\"\n        \"Iege$P1NP&f[pxu^#/>-v/$M&MEqE@pc4;-*Cdv$,wN`<a1g/\"\n        \";AZ4D<H:Kv-%Tt?0$CX_&r`NqM4o18.iZor&r/\"\n        \">G2mtar?xn^xO=t4Yci(dS%ZU+5AJ*m&#bLIrmRpxc3O'sP'``R2h\"\n        \"PW`.hqvP%.d9?*NqlM7#kDt$M,-VHMXKI*N]<05#Vtq=M>J+`sEpkM(xlEvQga[rQvCO&#\"\n        \"mmr=-N?4V.MQu>#QZWfL)B-##hQd*Njfj,#g(N/OE36YYkFiJ))1Xv6krnr6Uw7'fo[.g)\"\n        \"akF^,KS]Y,(DO&#v]3B-Xdb;.*x;5&q+f%uu6$j:WudG*u_bJi];oFiR@P8/\"\n        \"Z.*d*,IQ?$i%6D<pFm%=PpgDX+%t@XVR1p/\"\n        \"jbE)+wRbA#Ta@v$^f&B#8+f34Xtbf(fk5&G0+l&#@1ZY#\"\n        \"eV5d3Gt]w'1Wt+;dG]G<N)UW&&(^fLXY?##a7p*#EmRfL<WP&#UKf88EBKv-.4o@'njCG)n#\"\n        \"T<-1#J^.D9F&#LT2E-'rL5/,),##OGIbM12M'#b@;=-#5T;-?FuG-,&hK-6l+G-/Yb?/\"\n        \"&/5##8oFLQFu63#=t0w/\"\n        \"3xL$#IGe8#C=T;-)x(t-tQ]vLns^)9?+1B#0iUV$1R&K)oEX-?5OKucrFeAYC<l:d1Z)<[D?\"\n        \"aY#=no=c$kK>ZrbQe$&.e;-@tV6Mwnrn#IX@U.fV>j#t9:S-\"\n        \"-7V9.VQE1#sCbA#(]F&#xUX&#La$=(26k-$[5tR0eA;[0f=AN0q6+Rs:cCZ#kvaH<9&@T.\"\n        \"s8G>#Ccxt%h_'R'GHBY$f3;'%`'w>#7(Fk4v:IGV(PvO2g-x6/+H*2LU7MMMJ/I+#N/:/#\"\n        \"TZO.#*#krL0RX)#MKjJ2#-a[%[itw5Xs>>G6,r./\"\n        \"YuN=Ii9dWdIfl[V>8ja2M>H]2NGUYV+AcY#e4w5J_=$hd@2=,%=Zv0N@A?7'#LqA#fJ'K-\"\n        \"0oK@10sBs$6oPf=[Hjh)Avqa3=n2n8\"\n        \"K3W[8FrI;1_m?uBC#XM2ptIu-HODINBAd@-(O4,H,(rE-ghTD=r)oL2[wp`=jJ9+\"\n        \"475JlDidoCHwrKoD]%jS8u9RG*pD[L2GpEM23%sw?XK`I3Agje3LVlC5rKsP2hjx*.u4(\"\n        \"gMmTT(O\"\n        \"`NSF-U2L@/\"\n        \"-K*.3%,:kL%WOh#l2^M&XDP##e?'j'-,Ph#_KNh#^P.b05d(f@`90b:oNPn*]<Kme[Y_:l%^\"\n        \"'$33.@#[t$C4m)wia@`Q&4/oUR[LOFx/t0g'Lk_G&+=#u)g_f[o]W)MK>O\"\n        \"N<46]kb&Xqp7,Ea5s#@Ge]Y?/5a8sbvD:26pcM',,,@_cT#=_`7:SEMl,i/&NFi/\"\n        \"Ee`Ls7UuXsgr=iSK(%@mXc@hYrR*0K8jCe%d9Z9RD)iaF=lb?IkmMFq8'o'o9i>X.T'4]2e;\"\n        \"v)AD\"\n        \"X@6)SV]Q5I)$iZ(4<Mq&vKt$<8q9)MwX(%$o/oh#+:xbha)iN$vDm:5g(/IJ/>t&*$::qor/\"\n        \")AOa:n.61aK1g`rh8fv31&L0,,%V#sfBd@e'1`V`v>6]d09P=6F?._QV1@$940Gi%I9]\"\n        \"x9gSd44UGE7BTcK+O>(kd7&LH-A]]_XRNQHN&U5>;FYS<7/\"\n        \"?@B?wo-2cAi9fPq^x'g2Md8,YrweYV1OUF6G/\"\n        \"]`;Ic'VU_#7M]k.h^C<RViu?JlJepEmkJ4`oV]iSJ=8WVK+*L,1:[$<*\"\n        \"=EZ-e*9NSEcUdla8Mk8]$1N(a62Y7)OJw:F8i#_Zm^INN^X#IhOOZvRW;I'UfU6f^9(LP[,*\"\n        \"CZd.nkYV9pQ+<UgMBBEv5W&^>f57p=K5=>^0,9@cu@0I@x3J##hBoaZ(MKd95v#t@*DW\"\n        \"cv.V2_^`U2:HG>>Q`G>#.fCv#2DvT27+m<-BO.U.0g>G2?uY<-]H,h1p&#t$^'w>#7x_v#\"\n        \"Tl4/MFl`>$_FfI2IRL,DasC;$g,,t$[N<=:k*M%F%a#%-'/T>.0T/.*35n<*=H]kMfL'wH\"\n        \"Dh//\"\n        \"P1$[uUSGFC,eb[S'U2iG)V%99OGF@D?J8[gX%]8D4MVqD'OYTh,trU>$(PvO2V*=OVQt#\"\n        \"ODb3Mf5WdsdGU]i3ClTitBH.+s1=Y[72h%-Q8x*1m9/(+,%<JuP8vqxS8SE[*72+`HQ\"\n        \"J2fB#-0Q9M0a$U2X_%:.H(un*Pw1aFLlY.P6q3@?Jt:^#L,.(/\"\n        \"7916Mw-s.PXrlJ-+OM^'jFffL3F/\"\n        \"2'9'?MX$(3OoHx+E#Ee*<maqusi(IXvLF&WdF5X-55pHeEa6FsF7<wTR<9n&Gk\"\n        \"pGhG5(HRe?9`GoSi5ktpkZ41+fDi;OZj].GN:xO.hPosk6W&#W>4k$IdAo$W9e-'\"\n        \"UR2vFEkumt#*^/\"\n        \"th4M(1caRcepn4]d]L'h_pw'1t,vK7$q$Hqk:J1b:5nY+o@i6mLaAMFZKH67Zh\"\n        \"JmHx'3kd_CK2kMCS9-EHBK8xQQ+G_*Z8rN<GN[7*371CeBa/\"\n        \"<4`4t::3ovV,hU@s-)nn(%j(/\"\n        \"'6d+.kge>A]V%RbK_chkCr>3T#Y4bC3W*)r^tVhm&s=wl+f_$hKhdHm3;pXW3rCfrvM\"\n        \"@[?S2aNA-Td21uKKl&I+7FN?IRt8ma$;-+;='R$Y8s;*tV.6*VjQ]TR_Nw]jpwW*\"\n        \"Ss5IHEUsJ-Iu1RfYs0qY8k6+#tnO,QU^Ms?Sb.*LH':aKkA=rgIN6c1F9CvQ%K<,r]oJ$*\"\n        \"WO<`$*\"\n        \"WwrdM)`kWeBNO2_qneZ,GO?TdA7*J?%fei6u&aGKfj,Fau494e7+e*:;2Y0l:$o$,0=J]e]\"\n        \"xBEeQ_1h:SX/-[Q)e[?(lIwQD)jZuGxG+ARs;uO#QU;F1>5&:sIsK*1EU*j+ZX+-ot@?@\"\n        \"r>-ENQu*sHJ.kII/\"\n        \"c<J25XnY213?^9)=8=%m`%h.j4c*:]XpjCIeg>8`rjJ2s]G9'H031)Xni;-SB1X%em?uB`\"\n        \"aJI*BTJT&5VZDIG;u)'t0lJ2W*]u-/gsbNDcT%%,`0fFoq+m9oKri%\"\n        \"0v9Bdc83M2Vb]R92J.k;xQ`7-$8v=N[AnW]3xJV:b$)//pG/\"\n        \"qQB9$6=G@:BM-2k0WYdpfeG;02s/\"\n        \"c$k)GDqqNMZTLKHF3`&SOdiU*Lkc<0:b#HxY8:?Id$Ku%k]6jKHi%]r(N3Lml?d*\"\n        \"]7in6*u*-Z=q];86PiCo$kn)i_+M/0m$(0]UgT8-[7;cR<lM-p,P1RQmP8PJdRZ,O>/\"\n        \",+?\\?7RuIC#.JeTm-w]&J?v9oCx3+j%&6>&33LFx2Ch7lNjSkZw0#sq95tpQ<)kE<r?S,;\"\n        \"WcT@\"\n        \"NpQ%YSRj`7oe(JcRcfr[MFkwIRu$/\"\n        \"Ht9tB5vZxmh_cA1)9q79SK:5_Nt,$%I$Th&GW=UTV=ohS8.wE9LT)6]/\"\n        \"V#fj5m@R;s00G86w%[NkakW'MhXSSapN*57r9DRnHOd_Re:AZ([,j`7\"\n        \"X<`]8i6?kVSYkCcarmoY-g8NDtDXW]u2@[hco#CqeGXJ85H3qKdc)dgQE#-U&N:0_xT#rHI#\"\n        \"P8pr[A/Uvn;i<>i2[4fu(J-YA4/4e2H=rSF9L.3sQ$kK4fiuu;FO]s$c3iHw$C?l,f`N\"\n        \"9mWxgUrAs(_:08PP0cJO+YfI/\"\n        \"Is?1(k$Y^AJe.*mEQROl:cgOH<?+s%:?#,Ax=f.B)o=?#3xTHMu`C/\"\n        \"o-kFQ2-EsmU2J]V__=5=T?.sq]toZdk3$8Q20HhP2Sv[j%4>&x$';lu#:oB<J\"\n        \"wFbW)cfNE3NLe(M)5m8C:*l0DHJF*TLIhE<#IS9.C071)PfAs.H<h5'e[*qB@mbR2ZCM?/\"\n        \",QG9Bt<xR9,7dE=i](v9[asdG9@t2MU3V>$OpNM2(JlLVWi%o9P55%9-V'*l;.m9'jFffL\"\n        \"N;m1K`4GLR6X0NY_Y<MKZB[n-fvOs*m%;4)EHS=fja8:dT7)D;CI(MOn'sgDq@Zefp<\"\n        \"Y570QYh&CO=5iBKjbM#33i2w'8$)><Ku<Phuo&mix:*[&9Yf:0FM#$^N/\"\n        \"S5oQ?IpDhS)KPQV<\"\n        \"U?S,Zo*PWA(?]mY_,?qUY)wXO@nb=PGS/\"\n        \"HN(4<_U@B6A&J<wX`RNT0@2%Q6]=Q(20sFUdhP)lVC.>'tGP<r$bkmg#[UHGxYM5W)b%\"\n        \"fNTo9wP,J87nk>O+/39ubvRP@SAmt+,2kGwMm:b\"\n        \"Ef<oi1`P'&bIF*fh1P/`oRlQ_/HBYqhvkD;VQDo45?eCZx?/K:#g,T5Kh+5/\"\n        \"tl831^N_hX+?T.87Lmg7A=tV78]TU.dUIVI9V-hZA@Y(Rr,/\"\n        \"Fmwe6SXqk;L7&RcY;'TP.SK3JQAXJ0(8\"\n        \"8#?unM?kO.:9Smgkh`cqB_P.1_%4h9+J=_,#KqGjAY_g,b8M4_fIh(rM.w%e&[nIag2#S-\"\n        \"V17atM;beVx7eU>#3.-N8;l$1J=Ap^60hnSu9E$&&E=_L$r$`a:kd9?dK6AtA(QbkLj[<#\"\n        \"aH/:tA8S0#6Y>g#$CqdCVHiC0&r;q0Hd-cWMLB1TZZu+>>[DNtf/\"\n        \"=k00;eXYKcmh:Iec?,3_;G+*)lKpN?8c2?H9[2<&a_9Ux^#$sEkb9QLHZ$,d<RKvB^T%\"\n        \"pEek2FV@v$4AZ5&iR0JF\"\n        \"6fiEHmg2SCx72iF%BOnDp/s`=GdRh2#5c/\"\n        \"M888-)]V0eGE9oF-&6VO^5(#t7:8QhFBe#&JrGW#$SV(%0T6t5/\"\n        \"nEBDIif$o;mxlL2DH*.3>B[L2u7Ux?^&OC5@T*i2BpP2VHL4L#Fgdq%\"\n        \">+V$#6c@5&TfK>#+'cLKbfdTb$o9FN)1%)<f3,?%%Nms@#w0T1Jn+;=ZvN:3j<L[^O;:[g;'\"\n        \"4ps^v[lV<kc2EtEMS#AQ+j2J%6Zgnwkt/r.v)6<?D#<kEN6[ga2ShE(e5O&eNYDP2_e?\"\n        \"J,LQ/nw0fGfLL_Gq-^*omVfcHnFtjUb-8D$=EFE5jc-Bc&'fZrZ@O558dF2Irx/\"\n        \"89,]+wqbwfhs1cDnKgd/\"\n        \"Zocr<^1dF*mQNOuw.%mGcFdSqrtl3Ng?;W43<%1l5pDUx-;1'DC2G&cT5\"\n        \":-<JiuIU0^fqXC#oo4c(9^i)-]V=KA,o(LkP'`vdUVowXt@f1N]hJ7t=Kxn$OORX7*\"\n        \"5s2i0dlG2(/\"\n        \"Guu%YFE3;4[MeE[D,san#E`-[;`Mdt:PsJbe<-hS`^'iFffLwXs@XkDD8fV2aUW\"\n        \"Il3E:s]HdGu/\"\n        \"_&m*AR,&2nFHt4xr__BZFB(Z=$Ek:[tvf4Tq^4[AT&.c&7*8xRI.lb?ODeP6OxkL7hBl+\"\n        \"EUdi0hbMLNW@&XCGQm4+w7ul><fZ1^ahemfjsQH+[(@O<mn``ILXEPMEE*,\"\n        \"#0ObW$A4u2w]>?H8;M`3Gar*oEX@076wZ`n@[wwC/vjxq#Y(,T6[&/\"\n        \"G&ogOg*Im?2'Nct?@(A[n@8Y97DLZJV^:%F?M<Z5$FjtQ04_t$laq'C:2W:NUBYUAX;^/\"\n        \"Sg:A1_T7/.&4)VFvR\"\n        \"5hVGUa$'P911dhrD]:ST#ZS?=r%&L2_RA_2Xkle8)KZO(39P?$#$;fQ3.MjJ?$Znk`4nu';\"\n        \"O9U2KG>C5D2>C5LG,c4)'@*3fC)X-+[cgEa^Rf=I1uq&0Q@D$;^>#>+>)*HMIUR'?K&=%\"\n        \"C,*eFE(jiFaPoI2He[q.F;)_=A66.`&w-[BeTv3+'4&n/\"\n        \"f8Fj1YVU7;_W9HO%7TD=2Rck:Gg;vuXKS[Wik&,gKW.EDf,D)2RmOJlQK)sW^wZw@nY#\"\n        \"s94XjPEt(wRRmiLBbI3egCq;5vU\"\n        \"hosBfsrsB*$tTmr6mt[n/7(x7SvkU9I0b=daiK?A$6'Njv_h/\"\n        \"u:8c38Nb:jn_d?9:.VeG)TNF-;'Hr<<Qu/\"\n        \"*vi2w5DT_L9NR:tOJ+?:uld`rpFB3haHpuiQtXcx%^Kk.#dkQT0mw<3s2\"\n        \"q$D>$mP7C$o'UnWcC*ulUm-rJ>).ODpT;%h00/\"\n        \"IRF#leS.]nX,sCTb.KiuV6IL><RE$dK@3]&$/\"\n        \"#<3DdOl&SUV9(_b_qud6v+H%(ibrg#qhHD<7L^PG*mv@d6.aq.Pk<+`accii@hMK]\"\n        \"9Mk`I0m*vd0IVlpc;pBYJ&hQ^isjR[H=]/\"\n        \"Bh&=7*4qP'o>pZ8kjG=FL(bc,FJ,h7lZ3x)#`>r'L,7o8W?$KZ7#Yh.\"\n        \"5ec17LtkGw9IGqAo9N`jHq+n',ZG2]G>Ci1^^sb@i]YdJ%Kuj.4\"\n        \"rx]@mQZ&3*Um)MkYnLbVDvsHSWIcfs6'QrT902'Opo^4/\"\n        \"sXMgD?%.(e;DoYAC)B,qY2`?iTu/p0pSQ=dW(fmBHm:4o)(_SG&lbiu<9#7<Y/\"\n        \")l]Hi0(GboC.I6&6]o`Qu#ccXWVRSQA71\"\n        \"8-VPuSq?g_opA^N^Qb5/\"\n        \"(ntO2>@in:S.(^#wOFE3kQ39%_Tw>Kn_NtQawo4LUY+i?&N#n8;<ZD4`[sM05w-L2dPJKV@\"\n        \"5Vj:CHd%H+W#hF?bsdG1AgTCBu;2F^UR72xDAuB<MVX-0X&_d\"\n        \">Z=R2,Jxj:GitA#A$,D88.;*H53^_8I@mJ20ECw-fh`BPN6DVC55hoDHkg%J]=xMC.`w1M$\"\n        \"F`>$=9RL2]NP>>;6>G2'Y$HNBMQ>#$Gm6<&1622d8_DITj$T8^JQdXIL7w#R-,##@oZH.\"\n        \"YF0Obk#sU_<`T]9N@>C3&r(BZNNa;l=KNBu`bC<#uT6)TJsR:+$=7bSBshwo]$CIR?'s[B;\"\n        \"O0t5<Q-Wfw/fVCCl%LaA`/HR6H8R3g[===JAh&S%fE_R.ggGHE%M1%H<CV1mh-_D:>AwM\"\n        \"HuR9QWN6u$B4@fS'G&mOjP;_RSmO5DxnRo+b$hPm]BAu(,d=(/\"\n        \"vE$Wp.*Uoh#B;wZU)`7:xQ%e5>@t)2kgT[>wuvNe4:<x.:O00ZFItc<P>`UqUB?A5h5^8-$\"\n        \"s4jn*M@a9&:sBUXKQXD\"\n        \"mtcIFgUU:SQgD^`k.,,:EsP3XfgAZS7F?N(N-'ZG4H2s?rACm/\"\n        \"jb=+=Apd1VOdvQ'L+bQ3As$e2Gja.3C^ae3kckw?V9)i2csjJ2605B2@Uqn'_-j;-HpIN%\"\n        \"1NnL2j3)m$(aK^dq_SdF\"\n        \"aZr`=wS9oDc[bM:3A2eGog3nD)[%F-p9UD=r5T;-1:#Q-O1l<MZd=f[Dc<;e7p-QNb_<\"\n        \"ljVd?YT+g1o396`-$Xu]gWf0$o,b.D)VG=^EV?NwAY-6.K]I+_ct=/2YQQnCEDp$[vliq+ic\"\n        \"m$Mf6gr3W#Qdq8KYYxfj`W&q8kH&R2lgmpqRjUS;A(G^#;iO]o%'*fY.K@;aKWxcu,+\"\n        \"UOoGB_,(a%PT=f#;67<J[%gOt11mhN(A7KdldV(E5G)TFW.j[TF07mLWcFw3f4C-euiH+s-\"\n        \"Vg\"\n        \"HcTBO&dF8q,;7uM%HC9['>1sg>Nl?M(Yjr.m<Cors&0wTXZN-cmpNmSsMX;cU:IA3r63F=\"\n        \"pZmg46?IpFLUll*DJ/Otm-q$C.vgj6WWXEX?HjG/>d7^_n4ac8sn>B)Y4lS&&[*1vtPQJq\"\n        \"-2LD3r89FsV.0;2o8D&*-2jo:trp(jCO#6BR[Pk>_JBBPj@]9dr/\"\n        \"4)$#A9YI_c77%iO==eV9X'UVmMx,=Q2YYlO[8TT5,$=]n$$rULq#3#hv:A,>785o:NQZU^]\"\n        \"8v(JL46bUN,a<khII\"\n        \"G#)sS`sk;o$#d&fB$vNqO+sQ-3a^FFJ8$c$nk03R$=lp8re=i2v>E2+V^q>O0]Q&\"\n        \"GLmCB2Xhbb*HYmame(h^;$Xn).nbO_WgLx6n`X7K9Zq=f5YFDR_vVj-U/\"\n        \"`;)8FOLZeu#jjtnu,+;\"\n        \"nZbL<Qfl])odxsAPq0K20;H59.htA#86:-4*wGuBSxwr1OV7r1>cwR2F%aY$4V]X%g1WE,f)\"\n        \"mt(_ds$65D%N0?\\?[L2noxKV1s9-Nj<sdG0]KC-V#ou/BJ8>B,P:iFqF#w$Xd;E3IQ;J(\"\n        \"p/()f,Dwxa(9#?;[#bvlC9b<-F/DU'jFffLr#.crPe.1G/>e-eU:)Dq8xaD5%jdF.#=&4Q/\"\n        \"c8Q^YuA,ik83r;]/]:g+?`Z8*/5B6(oF8s=*&^WD9E%%J'82Ns?wi+:_&</(]r_ef`:fc\"\n        \"Gkc<-#_In$BQFd%wjT:=7DRdJrZ3-Cc%MNlMxcJ,h7#e,'.Xbk]'NeifBK/?B9(n1/\"\n        \"vH3a:UFGkBe5>U.Zct>J?FWt7/vK`[Ac/I[5;PtRCf/]KF]gu#/\"\n        \"O-DewUmjCB[>JZLHd]%F+xP\"\n        \";+M8?&Z+c`qoJ9P'aK-SH@C4Jm?Q<F)@_HFHBmft7lY**7N$_b`/\"\n        \"KTF_ZM]Rb2bg3$xge<-V:%?&$g@mN;q6ZQrbp^4DC]r:Al61fZ1+Po6mQD90+BZ?3R1dk&R,\"\n        \"u8[6o^tq;H6nqPR=\"\n        \"v4)J^XwQ^FkCR4t=f(U,6G^ZB+siBi9j^Kj^FBN),0XqAIU/\"\n        \"UTm-OCJg28hHq*WtXwWU$6n.BTIaJY[4O*3I6rWS><W@aWR9uKK)2^sXelI.uH%\"\n        \"im1Ffw9J32@mxEur(QBo.b&2uO#PA\"\n        \"L(k:kBmex8.d;j2Jet@0ii^Alto]n30V(oJAYME7@0orb%H<CV+*]=9.T?$OX;6IWN&5:gl[\"\n        \"Yj/s25ec;k+OaGrSLC)E6*vRwM/Zacmdee#[mIDh-HC1w-X2:v?Q26PgT93;64()V6wY\"\n        \"#`RqfE6(+]xlfe3@*O39vlX_,V<;.3D#BF4F85(5j7aPMg8<M2h7)X-%H+wI/\"\n        \"9Y<8J@nW_YQ@D$3qnG;*d,hFei=J2FBab$Se39B>X]C&3S0oD>wdNND?$n&Jk&MN([0m&4)(\"\n        \"K25E$+8\"\n        \"l8LB#XxefL6lNfLSA4OO*A<JLQYb3Ibcf&o'ZSqj8F(q$UwRiZP'#`M*)&I>jpMIigb.bI:\"\n        \"NTdfpR1xV3fYPDVL%W7V0xfDUk#EWGbjhQT_@;.b4_/RA3k9B1Va:DCjY4JTox*r[R4pT\"\n        \">h']qE`XE#Tv9i,d=_0u2.EEKB^^Qm?ixd/\"\n        \"MvrI8Wk8$=,P(YWVf(frub*[QA<WPu^w:Gl:hwRQ8UvZ,HE^2_xA$fmQKBtq,JF,c?MY.\"\n        \"nfT:x_t65tV8u0aNUNODb33LtET<rDhbd:o_\"\n        \"ejoPp69gc2;v&7&rMKq(c7D6+?,VYY[lRX'_-0xnsHh$$jO+2RV(5=d)PuE#.XR,144AA?\"\n        \"3PM$iV?QLLnjoZ1_50:3hrHvRPIY9jVO(ZoS]gS;w4c//1=`>J&7f3tR%C4qY3Lb$Z/hHY\"\n        \"x&%6T#`'ZOkN(N3TBN,CTQ6ql91OO&#_VbF/\"\n        \"Ei4=[<:q,8i,+[>bY7+H]B5XB%;cI.l?*bX>g,A4wx[251)b1bECp$@njuGFIR]\"\n        \"FUnN397Mxj2/M4Z%PPC;$p%6O2KY6m%`qGI6*MZs-\"\n        \"`SG>9ra512$j`qB+m=b=*-rv7hjd:C(tZ.%'47f=KBnL2hgvE=K@gTC^mwU9b(%O=<JDT.\"\n        \"AT?#%[oTc$'MNC#Z9FG21xQHNeMQ>#7',@7&3's$R[5[Rk'aB)q[]h;;E>m/dVTN2<1rMV\"\n        \"=0ha9/BZ_^pbhR;,]P=7j_6-uTMQ8n-V?-6/\"\n        \"NrV7P@aW9eQd;%$1ER'pXffL+SUV$x<MtiLlkd>)gI,dHY7Z6lw,1aE&7q-Ex4ug,^PI>q@-\"\n        \"waYdgZCYt,la?kKbtpFXkURS/5TiBPCa\"\n        \"i.XlVmK4So^,ur([^>#X1V.Ff5xn+O$Kfp;%;We:i(FJWZ8R?].hK)$wXooJ#?S7h?\"\n        \"1kCFDrR9%`tv$1Hvu6bx'4W#6DS&u;*n,V^LF4#TNg&KQ$SOCj)bX`So9*]_t#C*,Qm8qO+\"\n        \"BM^\"\n        \"w)$_+#X<qim.PU#quPxhOMW,U7Vid@T94Vsd'gO@U*+m/\"\n        \"C%s('G_n1Fs7XLt:Cj2DR?J2$^q<@ZU7CUIeeueG9kF9CBr0r]S[)79cV3jdhr/\"\n        \"BH]FmG%u=r]gcNa%e1AO@B9M:]BWOE#0\"\n        \"k/x$EXQG@97rkmPD9m0nUm+I/agvnHGj-Ug*GM*3OvGSgOY--/\"\n        \"rWDoI(ZaPROqI+P+^i]Mu0@Srf<k,sME:dt5rb(M-jP'B-:Tm-On]/\"\n        \"%N.VCh$fm*n$rq3#mUsjsG7JP?BM+hgh&bP@\"\n        \"09C=j^3DD@])'VNjTNU#UB7aRPl3XFb3p/\"\n        \"%x@5^_GB*i>>kM[`0CP_bFuoG&>YB'=.b81V,tjhn/gwfs5htS.,vZ2I_,/\"\n        \"*.+gQ`>&gn]<nUC?+Zog>rWb(p6$pnxjb./>U/$-Nh9_J4[\"\n        \"Lvw%3h3>`25&6r#,,9OL[rQvIi@XgjG2U4&F#S6N#VP/\"\n        \"C_I+q&1K-(#w*Op%7btJ1`^cc22JP##&####qiC6VZ3sv#u_=o3@N'(,>-8T&tXIbORfRI2@\"\n        \"do(5s`Y=$5F174=4R8B.Y>0^\"\n        \"M@5mQTCpKkL@+W'>xsfLFXNiD(ei;R-vZj7LXTfe7@`-.bq1Etbx<Z:+,)lteEJ7v6s#V9+\"\n        \"4pN2l$SMbOY8qf3bZ*?]w-/9MlQ]n:1:b4rKUT?\\?C#2*mJSggZW64Wpnc?3)j.j<N.ED6\"\n        \"5C?#O*ekTEgI')vuToNiC#`x%b]vEY^f,fi([0hhS/\"\n        \"9(auhn=Gd;:VBUfWC-5_-ID&7M3U@V6?%uN0m1n)UoFWBJ%@haeqkh,&I&*p;7jjL%\"\n        \"Fg3mBqDx/D?WE-I&WY^J/1hbP1_r9o&J\"\n        \"8)7Q2RW>'S#89CWXB/,_Bhl-PxIMd^&X3]TvO4nf0-*H^/\"\n        \"EqQAEJxO8k+lMcfIeAI-jNPsC6PA<4'*DeU%oF%Vd+VqDIq&FE$Pn@l([Vu4[\"\n        \"88HcOvbKpxs*iLv&&YkHwidN]Vt9?n@2c\"\n        \"fF2^Hf-*G@bdE:bVvF4.(fOP_^]:CR>NY5Qr.%:aNB0p@QI=')q7h18<Z,tqCm`++-*4vI*\"\n        \"SHaCQePU^*B_wLN=UkLAX5j`2p:d28jkgLg3WZ#x&)=IjbJrBJdrh1Su'F#<W(Z#u.%GV\"\n        \"_s9(OqXuc2::2t-;(.FOHC2d23&@*3AZ<i2FpWi2hfkw?5cr[0^B8<$EoY;-o62eLi,+;\"\n        \"SGx-,8:*dLd,,^M:n,)eAZ?Q*i=]&SJ#wu6&`qYxOF<%DSV&dODov'YpcfqPHuw9tZ:$,dg\"\n        \"2B*TpPA?I*+Lfw+HmIa]pdiWrY8S0Y-7F*Wv:#oq07SB7FK)#IW2okKPA9qRrH&bIf<vr+\"\n        \"65K2]3s&=,4]OIT2gEg@=OCY6D/-/ZBC`/AJjiJXOA7cJx;YaEGQud1A6(U9#.RAWH).*u\"\n        \"Q8=77pLL5nK'n..&O1HFsGg/f/gU)hSc/\"\n        \":n[Q?aQBi[`8JcIh*7'3sgIWWu<l23]@f.T?Ft0h795*GftUNA+KUO[*ukDrSNYN31q3q9=\"\n        \"4Ctl@O^D]4(UvV((gF#,cnKmTJcKxA:L:oUr\"\n        \"a74Z3l9j5XSDPHMd40LR[k;GFGKntHx1e[rX0-C*gxwq@JbwaOSe<p;<^#x<d>t&I&N+HGv$\"\n        \"x`J=gm^0KGK[F8V+_IjaVuK,P87mW*U<NMT6m;_UchIw#,`<M-Cw3oqKdi:OXtcnUin@\"\n        \"ILec<x'd5/*4-ximltSedj?J5xTV[#E4/vJ$$LMp,d7r^a&?U+b'n/\"\n        \"[qhV=3USg*gu@&ISm5pSgi%^CFCswq-h+TZhwq;QeY>9jQX_0C8an)lXG-o1s5-?@UPoVv-\"\n        \"a/wg$E/.xI3[ro#\"\n        \"ZSO-D9J9N*q9vHOt];xjkO+'dUB=%GD1+@G7FLpO]MunM@q@<#lNl*rtt:<#\";\n    return _nv_sans_rg_compressed_data_base85;\n}\n\nchar const* UserInterface::GetNVSansFontBoldCompressedBase85TTF()\n{\n    // File: 'NVIDIA-Sans-Font-TTF/NVIDIASans_Bd.ttf' (169412 bytes)\n    // Exported using binary_to_compressed_c.cpp\n    static const char _nvidia_sans_bold_compressed_data_base85[135675 + 1] =\n        \"7])#######*DE2c'/\"\n        \"###I),##d-LhL(t1C$@=cd=1fp7T7>`Y#&E(##[g:t82@VlUn[D>#IT%##;x+h<W^G_9^JA>\"\n        \"#w.'##O=*e=<`9ZRC1^Y#_[*##6-we=U-)7D]>Zw0)b'L>+vA0F\"\n        \"bm)O7w0&##U2f--)O3'I>)#6'Ux###<pv9)n[_-G2]FH26s$##U/\"\n        \"H50u2$=Bda:8%0r:kFA#xE7+1HkEpk%B(G^7kO<B+##H_@UCrpsf)ji4_As1dD4AJr9.7EX]\"\n        \"krpK-QBT#KDWW'pi\"\n        \"31d1#=eR%#ZA^FE__URr@56+#meMoL]V]`EYU%e#E4m8#Qoj,##cq4FrV:G$.-3'MW;\"\n        \"qd1i3^=B627^,k`?>#:Xg--g%n4GQvRVL*1J(#JNN##2H_(GHis436,T3#DIm##]SUpLQ`$#\"\n        \"#\"\n        \"ghW4oWE0^AeI1=#G<5)M1X1`jKn&2M.W#xW5[1A4<4xuc>O[w0QPYY#t+9;-Sr?]-Of^\"\n        \"w0VeA(M9@LQ$>v8kM>P,W-UQGX(-YDW@B$axF;L'L57Rx+2/H&##<<<,i='7C#xQ*)<<3]V$\"\n        \"?A1G`h[Wn33R-C#jG>F#Tl1Z#;`uY#'x$m/un_Y#(;%##4U=Z-X3n0#)2Oj;/\"\n        \":F&#$8uu#o-ED#e5tT#IRV=-cH5s-V.ofLorJfLs<a?##J,1MP]b@t1fs-$'e>A4w=T;-N@\"\n        \"Rm/;0j<#\"\n        \"Mne%#ZM#<-DCRm/\"\n        \"ARac2ff_c2Es9W.u.>>#VKb(M`b#<-cU,[.x6YY#)gG<-t7T;-<7T;-p7c`N?.W+\"\n        \"Vg0gLpATx>-WdF?-R6T;-Y5T;-E5T;-:5T;-O5T;-_L;=--u@T%Ltq%Ok>4AF\"\n        \"ij4,21]aY59T'&=ucHA4Jirr?qqgY,SqGJC0lD_&T#)8I-K2fqw:SV6#`058)qW`<n%\"\n        \"or62T5>>v+su5FKBs@o%D_&#R9c/PL/(MgI*nL-/\"\n        \"2`ja[&s?wqmxko0l(NA=vY#j>t-$7qD<#\"\n        \"tOZL-55T;-i5T;-a5T;-%5T;-@AT;-gM#<-95T;-*5T;-25T;-ufT;-)A;=-bXQ5.:&\"\n        \"mlLJsH+#0-=A#/\"\n        \"[lS.p7YY#w'LS-Wwtg0'->>#Jw1G#e9=,Mp);Se#*EYPnq0dE+u[w'mUi.$\"\n        \"agZ&#]lq927wLcMT`a,#C91pL3VTvLc+.m/b.DG#/V'B#/\"\n        \"=T;->4`T.n]O.#@YlS.U#J-#D(.m/CaS=#P&5>#gq/\"\n        \"K1?gpl&tYP]=afZ`3>$4F%%fm-$l7m-$T<cw':IS]=?@E_&SMii'\"\n        \"](2F%vML]=MM1F%e$s-$5@m--a;UF%Zs0F%'nvx+#=@_/\"\n        \"fqho7+*pl&s-KG)03TP&]Uro%X;48.+'fl/1iWs7^[-5/Nw19/'e.5/\"\n        \"ur+F.79_w';.+kbC2+87dc'`jS*X_&pf9;Q117R*\"\n        \"lB+OM7Xa2MiS6MT5_Z;%RjacavQ7e<wR8)*sL'p7eR,v,VlwB,kb.Vmh#i4f>/\"\n        \"dY>ouCDX(%$?$j<q-M];)g(E:(B#Na0HMjxv6#nD[8#mHO&#8VK:#-X)<#R5T;-RYjfLLW$\"\n        \"lL&GDB#\"\n        \"U`''#DIA=#_7WD#47?xLi:#&#uZ2uLhG#&#I392#r-eiL<lK=#F=UhL;lrpL8?]0#+:.#\"\n        \"MvmP.#5Ytn-Z.=R*?RB-d5/;5&%c1p.7[fi'TLm-$v1*kbwB^`3,k>wp,=dfCV%SfL^)0/1\"\n        \"q*.,;?Y]&#c<oEI:m?;?Si1F%tPp-$rJp-$x]p-$5@n-$14n-$`il-$UIh--Idao[i;S&\"\n        \"QLq*'M11p0%mAgR*jgpl&*(N]=da88%SBR-QOIjEI<-x;-T;up7VZ&g2+L9s7G;2i1+'q^#\"\n        \"&hBB#'H]t#1vclL4oMxLqa;xL:%axLtY,oLslGoLM,<c#_Dg9Mr1JT8IxFP805l-$;Ii58S(\"\n        \"jEI,+-;#O[=W%m2N-Znt:kOw*X-Q:S4'#r'x%#:itgL,&Qt$LYO,MeD(p711uo7Hg0^#\"\n        \".[xvMUC^nL=PEmLL]MXM2l<+.45&)N^L$2OpJ^ZNc0tnLLt&nLbZWmL'(crLrMpnLlA^\"\n        \"nLTJ/NOX2bRQgpon.FaP4orGd]N]:il-:tqxBGbBwKV(ivuZBm9i3en;-5x6a.?$###;5T;-\"\n        \"V`b4%X@FwK`p+.$=7I<-,.4,%m%)t-5$G)OEW;;$nvJW-@B+?ID6nQ8Re'B#$P]s7*m'B#V,\"\n        \"*/M/bb4%E^akOFG._J<NT-HS^0F%&im-$,UF_&]3_F@%3n38_AC^#b?RFOYYpORk*^fL\"\n        \"KV^C-M6T;-Eo,D-BJYx7_O-db.s+$MBgo#Mq&h(MRL:68`R:_A+#w(#8kYEnfn;-vN5l-$\"\n        \"twG,Me8E,9Xfj-?5?j--P<@60;U^D*B]RS@m,Ppo_Fx+M0Nd.q:^quZSo]Gj3fDJU*q1R<\"\n        \"+=oQagZK_&/\"\n        \"DdK*PAw+#5185#=NvtL_<05#hOFgLpAQ6#xFr$#8,dlLbmu&#nH:_A0U:R*8j(,2Vj4F%h?\"\n        \"uE@)cI(#IhY+#6pd(#*qq7#1AL/#cP6(#153)#?F%%#%#7qLrZWmL@T$+#\"\n        \"do[+M-f.QM#,`^##N#<-cFX,%ms?D*8FR-Htukr-%:v^]MV8R*4j8_8#Cdo7cq8Z.'vEx#F@\"\n        \";=-QYlS.aH0#$C0d%.>*eiLkCC<NNo$+#PHV,#iSc##)533NKN`'#0bP+#dhKwK34X-?\"\n        \"rj+R<<Rc-6H/\"\n        \"w9)`RoDOa57>Y+66;[(a>&56pa-?;A[`3d&a#5Y+=A#c)b=$0:'>$$4i($R%%w#:r_Z#Sf&\"\n        \"V-O(.m/-=7[#Tr2`#;P/R.ogBB#pg&<-utre.<Pew#b#u_.(=Jt#7WjfL\"\n        \"$18v$u%x+MTvmLM,=3G#RNft#lCsuZrjfV@Aa+Q8v<1AlleX9`3Oh^#uVs)#?XimL`\"\n        \"TjMM5r)*MG$axLUc%Y-f1h9;]gS>?p5(EXws%##Zp8.$c+?>#(ok&#w%(v#Fle&,[Pt(\"\n        \"jnSw>Q\"\n        \"T1vr$j`c'&qqH$#6&*)#iBV,#SY$0#G^V4#6[(?#S'4A#t2qB#APsD#dI>F#+U%H#f1*h#$\"\n        \"2N`#dO*p#abCv#=:7w#Pq3x#vAQ?$R9k=$'P8A$_0`C$CTkE$LU&j,g.I`,.HHk,c?D<-\"\n        \"0$J=-FZF>-V5:?-r.[@-7@KB-p:GN-2%`k-I2Lv-4,PYu#,>>#/\"\n        \"*46't5>G2%U,wpU`a,#U`-0#lji4#;nC?#Y9OA#$E6C#Gc8E#khlF#1h@H#.S$iLtsU%Mbu$\"\n        \"7$/lUv#ji#d.QS:;$\"\n        \"h8bJ-*c3v1a6iC$UYHG$jhF^,%q-tLI*'U-pBM<-66f=-W2oiL($7@-5Cj.NmZ-tNY/\"\n        \",GM1KHuuo(>uuOfKK-h5T;-)=srQ8cvIU::u]uptxXu7gIq.esPo@Cf2@'QL+muMFxlu^<[>\"\n        \"M\"\n        \"7x1p.HB%##QIew'%`06lf[,]kBfGxkdrF*lAfwOlAmsak[u5bk@3++#A*+&#EpbZ-7^Oe$\"\n        \"rJp-$&jp-$(u<`a(?g:mDsXlSu&%;ZGnE`aM-]w0j8Qrmo.6s$K@$##+)u-$+v2`#$D;mL\"\n        \"QJG8.R?;584rsh#UBic#hY6g#x4*h#U(?tL'gN:Mw0#,MWGZY#dgG<->CfjLFwchLOFZY#<)\"\n        \"f5&`6>##9rC$#6ic+#4mk.#TX$0#est.#0HY##D<G##OBP##6m@-#:tI-#^QE1#ul?0#\"\n        \"SI`,##Zs)#fZO.#^$S-#=f1$#vtu+#(%),#wH^2#jIe8#c*]-#jdET#`#(/\"\n        \"#w7p*#3un%#lXN1#c-^*#xM<1#nUG+#*jC?#1@XA#.b7H#i5A5#X1PY#4LkA#kpPN#6RtA#\"\n        \"lm>3#noIH#\"\n        \";kBB#4tRH##1f-#Y-gE#SNrG#@+U'#<9ZV#HK?C#FTOI#tAS5#j$V?#KWQC#*QMO#M^ZC#+\"\n        \"N;4#*cbI#Rv)D#LgkI#'i7-#gWPF#pA4I#Y6<)#Xq%P#3.BM#lv.P#BXR@#vPHC#BJCR#\"\n        \"wLEL#xI31#S&jW#54KM#DPLR#D_[@#%YWL#n&8P#FVUR#5+V$#U,sW#CZXI#7:TM#Fee@#`<\"\n        \"':#2_gU#71`$#/a0B#^Gc>#[e?K#B`2<#=g9B#`Ml>#^kHK#0@-C#@fg:#0sKB#.rQK#\"\n        \",xTB#^/\"\n        \">>#ZwZK#[6G>#]'eK#EY)<##&]H#lF<L#dDfP#XF6C#orL?#CT%H#>[.H#+9)O#8DaD#I]_\"\n        \"R#1(9M#>L@@#@RI@#=):J#oc=Q#$+GY#C:OA#(rw@#t$2G#-X,N#hFgM#@/=A#\"\n        \"4LA=#kr=u#6^d_#wiv_#Tw3]#ocm_#X-F]#_9X]#:&<`#0Fk]#B,E`#48W`#qv^^#-2N`#t&\"\n        \"h^##3$_#FJs`#?\\?6_#uJH_#wPQ_##WZ_#r;$C#KchR#MiqR#f[YF#48ND#hjrO#gXG+#\"\n        \"_'+&#a5C/#hMh/#jG_/#Tgb.#p4=&#t6D,#?nq7#P]U7#Tih7#5f($#tOtA#6[tv.FZ/\"\n        \"<QH=6##2(?pg?&H&#gEX&#kQk&#o^''#sj9'#/1/g'=Pc1#;rs1#?(02#C4B2#G@T2#KLg2#\"\n        \"OX#3#Se53#WqG3#['Z3#`3m3#d?)4#hK;4#1Tbo&XEO4#pd`4#tpr4#x&/\"\n        \"5#&3A5#*?S5#.Kf5#2Wx5#6d46#:pF6#>&Y6#B2l6#F>(7#t.@K<=)8-#jZ$;ZEVbV-\"\n        \"ud8MTEQ0,2&'DMB\"\n        \"/nZcD5N#m&KZaf:*ZLcVMg&,;,gh(WrTBA4,K@JC5<W`EV8^c;0)I`WWk)>Gvkix=\"\n        \"eMMfLsrc:mm1f4fT_g]+c4(##Ex[1p)GD8AvOX&#Wn`e$H3^Ve(O/\"\n        \"C&xeT+r8ONP&kpF_&LmPm8\"\n        \"/M<1#A1r?#pa7H#dfg:#*Oh/\"\n        \"#qsN9#tb39#Ui7-#wn%P#b>#+#R:w0#iUL7#Shb.#)nE9#GTPF#(=VG#2pPN#)R`m$9uSE#&\"\n        \"-uoLtW_E#SNrG#CDBU#x0DG#?Z&0%,kVX#A)GY#%F,tA\"\n        \"E]<^@1HPG>o?W.=_Lqk;NY4R:>gM99.tgv7t*+^6d7DD5SD^+4/\"\n        \"Ll31ffHW.Jtvc*h>Mk'N'tp%=47W$-AP>#c/\"\n        \"Q?.E?TV[_:+N$RvZ6:Y42X-:?$=(?pm=(w7%N-t?cP9P8[V[tr-A-\"\n        \"X<K%P/I<mLbE2eP2e8jM5*5gNhZ6/(gL0;67/\"\n        \"H59:>1,fd$,2_(8cC?e&%BPYs`D-Qr^_&v6X_&Hj/\"\n        \"KM4klgL=#(Z-W%&9._1dID9_,Q#D^MZ[(20LO:xYQ1caDd&,u-T8A5k31?/ZQ&\"\n        \"0hrS&2jDW[;sE%]6qtD#/@q^#'>[u[:tx2'`.079A(u59?L?s<V5*t8/\"\n        \"tqY?1%P&#ZhP[.#Y:Z[rTp$.5u:#MD`@]O7cxdaX^gB(gixs0qW[2C?0R][>/\"\n        \"Mn33GC29q,Ub[9>nD0e4oD0\"\n        \"sR#M1U8s^[QB:99Oh4jL%31I=6GbB#dN'3(<nmqr/\"\n        \"Nu0.Nnb)M?N)=-@f@U.TFldrFNi.MvmXW%XU,2B+]F&#fe:qVsc.Z-)&Vx0R(q)#U57[-\"\n        \"SWdC?Y'q?-S8^7]i/dn0:SplAKOZca\"\n        \"OJ6g)5ir=-xw*cN]`FB.K0G0.sH#3N/peSD$^wZBMBkW[)pbh#+3)N$K7e59sKN5/\"\n        \"]NeQD3+f5B%UKp8%5S?-n/\"\n        \"mx=[wTs8eFsV7n>'%%n'6Z2%HU[-RFPBoSbDb.BabC?A&K&GM4g88\"\n        \"Y#&gLt6gZ.J]_h=c>hhMUrMZ[+jh5#MW744kn*79K&w596ZCHO0Z,<-$CY)3;=aC?f[d$v`\"\n        \"Bj@-%uN(#43:5M?&i;*5)<tUviCHOLG'_[xqh1CfdfpC-PHY-t7Z][WH_A.@Q[][,eG?.\"\n        \"Con&lXo<Z-A8voL?41lLBRqHNKfZe1^bMW%d-m]+5.Fj$@Jw<(-nsmLY]g88s$f:.eTNp.>\"\n        \"B#b[PR'?-Bun%#d9+XCWEm'&MDuDOr1M9.,^-6:`4fV[^ZCh5C?>e+ZNN-Q+Jx,ROaGna\"\n        \"Sd_V[hVtJ-^DtJ-`jUtLt84)TIhX0MwTU?-CV%0NS42X-,wO1>,c,UVGc?mX4a=nE;<fKu,_\"\n        \"NmLbM>gLH`?6:8B.^#F(X>-C?3W%@fPEnC%5K1:0Q?.lPg=-D)cC-',uB.(#>F-h:XC?\"\n        \"94;^?^_3b?*#ZQ1g7'A.sS_D?n$'@'ED(W-]9-A'v=;&5VE2(&@LZP8rx.a9).7r7:.nw'\"\n        \"tHpw'M#n31/wC2:9*`M:WrHh,Qc;c%p0pIOd:X;.)2'(#s`vZ[(dQ][1/HDO'GvhMsHqB#\"\n        \"3C+tAg'QB.e8cP9sMx2vB$5(]IqJ1GlqXe5'JHDO)<`KO.PRD6;8voLjGjC?dCLsL`X2W[,\"\n        \"F>F#2GGQ1XF;##;m-B#D3?g7Z6W%@D[f$#&^=1>0PV>#k_w?0u1dID;abC?wETB-t8Fg<\"\n        \"ea`PDVr5v86Er&#g<*p.5*3D#6,Nv6N*I8.lRiY#EsvG&Iq4Z[mgj2MsKN2Mu^/\"\n        \"jM9IG,M7Xr8.-Z3)*Xh-hMmMsC?)L77PHMU`M$5lKM^RSv72%^q)8wxM-Tn`E[2]SF@C#14+\"\n        \"63/a=\"\n        \"i=Ne$#tPDO`*sE-8kk0MEUDiL>^-(#?sr2K_*P>-V^%BJKP];SugMBHe+]]GuO&&G.FGDF(\"\n        \"T^cNPw>&#AA6eMZcHJM@$_/DrIRMCP<GmBj%;5BEhj%?a?.m'Klsop_KX9Wc^^S7LquP9\"\n        \"Pn9x,;+_s.`jQp>Z'Jb%X:S7CC*bg*9>V6Cr?^w'6p16Co6^w'crl-$(2#R8`$o;8L4(\"\n        \"wInQ/\"\n        \"2L)pBMU[M<$LCvsmksseKU(&jWV?EQFUc[<GVcvFAU6GlK-<7.l'sd`A-<?\\?+UYA*$L\"\n        \"cMS0$j2J$ULbC##GJ4k#T$]iKX@#B=Z>C/\"\n        \"(90qeT$UP?(J;EKTNx_N#j0<S[)G'k#cPLuu@UwVSwp95TW6@FTq-qtSX([2lV4KlSp%\"\n        \"BsSN`Q,)k'A;Rhm=;RA[kORvIlc[13PwL;sPG2\"\n        \"*5R/\"\n        \"G%=fGQaYi)%jjcpLsv4Y[-DZhIt-2K#?5s+MX?ToLV39SLHe#)-rs,h#oXE?>_(R>>_q6x,?\"\n        \"h->>seW?>kL3?>c4e>>iN4U%3PH<Q#8Qw#A.[S%g@w>>Zr?>>6X2##0'Lj$ol+;Q\"\n        \"IxP0;p6qY=x;Ha<dbQ`N%+Z4M@^AW70;b/%]92>>86nx[cNPf?ow+2#)=0[[I&3>]/\"\n        \"(DO,f-1[[6jG<-73O7/\"\n        \"1QT9Ag?B-N(%DT.GO=GH?B,s--b1QM6f:GHMkkcHP,gGMF-)iL@kn6/\"\n        \"%*U'##tVfLXbai0SB@m0kl/\"\n        \"C86uc)H?@^e$G$%@'P_wu0FQ]Y#[[:Z#E6.[#7*rZ#5xL$#GA[BNAYB9vQKo9$+%vW-*ZT:#\"\n        \"w]s.CAqC]OT)>5KL+@2L$>9;-iYI6/M[%-#5Tj3N@:m8.\"\n        \"QCV,#9*K5+5-KlSLb97*H0Mc#x.MT.1%9M#Gsls-SGIbMTL$##)uu+#s1g*#oFMt-Cn.nLP(\"\n        \"jG<s<ec;dcb(jIW.GD1&no@^QS8%Ew@]kej%DE_q-AF1b`'/9SL;$8=_*#*3M;?_E=^#\"\n        \"X:w0#2-al8u7T;-@;cT7_Rw#vQTQ_uX*?;#Q#M*M.+hh9xo0sfW7dviF-)fqZsZk7`%dB#\"\n        \"O7YY#OX1vu,s+NP163a+3<c`3<c/*#Yf60#jQTU#'2ED##OrG#M5$s-pBAvLxGUKcrZUA+\"\n        \"3W#/\"\n        \"UGBU>5DQXc`UPE&+vgN20@(X>5a>bJ:+UkV?m@KAOS@v%kuaL2BvpO#>(QociS62T7xerA=\"\n        \"F>]/CkmFsH9F1aN^uqMT,N[;Zst(ghPew/1#7t;?x&jJhqY^#GepWT.kcc,`@Dns?\"\n        \"AEfsQA17K_H10Np&NUH2sMQQJFF[g_/\"\n        \"[r,iY?B9I&xGNgN`0[,:@JEa6uZw#`J[-)2T>k0cvwQ8]*xZGwKYEWsI^QfSURn/\"\n        \"0cO-M6#9tZp`rZc]1(LC8`f$5xl#O^*Jd[#//h'F6Zw?Y\"\n        \"fdR*s45EF32hEXQpbfO'&UJUR%17LhoQ4o/\"\n        \"c$JCFtD%7o@VUFEX#u@#j$+rRL>)%l6m$M1nufIML=YOpW9c:IF<0xu.RYx>uQQ(Xr2F+\"\n        \"sKoWf2:,@YHor:YQU%6YZ0Go@cmh+YmjIh`+\"\n        \"CJ/\"\n        \"JVWW')FI_]>$ce9,3?7si:%=niCQ_12LQPt%Y,sVca.TQ21.Ail^FMQ>Q1a0jCSUaP^\"\n        \"0a2N1pDwSJ2.M8f#.J#Q6;x,3([Z,WIu+-3G5i&G47t8SD?Bjh_?<?6U.kvH?q>QT45Jda\"\n        \"/n6Wn*JK3(#kME4n.YW@c<.3LUDXdWTEATfO(.HsHBXB,;J-t72k80D(2DBPiwns[n`\"\n        \"CNha6g#v`%S60m%P<@n&tgMmw@<[lrdgikgY$$vZM*4#cqTAx]>*O-KZN_,F($m+;t6'*\"\n        \"08b4\"\n        \")+[6B4%X<R.B-n^j`?0iq/M[$xT)I3)+e6B0VI$Q7,/\"\n        \"h`>WjToF*4F+B;c$6CpoEXI7L'cH2pQpGw[e*Fr):8EmLeEDhp9SCc=eaB^a9oARVL)@\"\n        \"Gqw6?B>LD>=bwQ@S+Ia<eY'l)a7x$\"\n        \"seXR0JYC@6?BGLD=(o$Q;,s0`<-@[m;x5o':mOC59hsnB8i[_P5Wdn^<-I[m7YBr&6N]\"\n        \"F45I*rA=.F@Q5vIL`R,OXnQKkF+aQhL;pWeRK%:J@Z(Y/.j7YTr&FYHx6U`E(GXgiRTcT-A$\"\n        \"l+8Y.sVsF=tK`:JZH$YRAvmR^<e:(l;Y0;&73Ni2-Vu@?,QBlL(1jCYg3.cb2Qv_uD4Xo9w@\"\n        \"o=R2>B;&8q=PC#=H]HUK;VS2CEcXv@].jIp//3n^&5LZ9]xd+QGA6cefu[($XG4hDDDP\"\n        \"mb[lqA-7p03Okog`P7^-RDE,F0o$jVu^ncbOZ*&moWAd4@e&WA/\"\n        \"XXAZ+#gv%^@1^HfnZj2P6i;SVb^DuJ/$HXg<<9'EWOjV+Pf2iDv;dt=vGE5iX/\"\n        \"BZ>Y3w7bM<NiGq:w.h1D-4)Mb^Q\"\n        \"N$'O)x-;'HaW^ZeGnAw@3EtdbS['qg'Vi?@F2)qpE'[t/\"\n        \"F?mQht>LO))q(=8*(-IF3fHnU>G.[e?T2hsS>CL30Fm*G1G:UT4Z>bc?h..=$A)##WBt(3<\"\n        \"Yv@bX&'v#7X/2'9eC;$m%+87\"\n        \"t:4^2eNAX-6IuM(^,';QGN4/\"\n        \"1)hi(j&.x_#nN_[-mA`-6R7RlS^Gt8&%-'8M;^d)M9TsI3EG2&TA3b(N#ebP&Oe;;$+i?8%`\"\n        \"kD&H2g<T'0`u>#-SY##x#J>#xD?n0w)X,4HlKS.FQh+M\"\n        \"SFN$#=L2##)A8gL9vAme#WTI*CM#G43/\"\n        \"5jL'-)u$WSS71gZsR8g[OB,Cm5M(_l$mSH2Ku.ZU4SM6cM;$=rBv$TbpcEUm'O(B9cj'\"\n        \"90Ka<nZZENdA6##7)RW%jG;Snd[lr-<5Xp'cE^2'\"\n        \"JU1,-K].w>0/h2#an8B3@@Na(,bDM2J59f3GV0RN+87<.Ak3Q/\"\n        \"GaE.3$Mte)KC[s$,%5gLFUerSiX3A-Cxwv$6E-T23Bw8B^#@?.T$Zp5,o&217M38&B@r-4l:\"\n        \"=f4ZBnc4uDgUfTE(##\"\n        \"^uv(#Gnk185g9n'Vc=vG2G,##_8L'J@oX#$7r]*@X.C[Y&:d`f$9.(st6'*+$1Nulj<a9`#\"\n        \"Et5(SFm##9bLG-uv5,,42Dv$<AmC308fEN@V=u-/aS+4fTcO-OxMi-tCu^]:lvJ)N5@i.\"\n        \"Euh8.4X%u->9TQMIee./U5()28u-F[T'T)+h>il-b1M6a/\"\n        \"*rxufcS_1*5>##EAYN^L_6,$iYb?KZ+gOfYCP.)GL^*@n2WhLAaxuh297^#CDj:/\"\n        \"VSQC#>8'[MR=hm]U14,M.[,@5r2=1)\"\n        \"cQ`0;8k/A#T+>uu#)P:vGqlA#)RQ9/\"\n        \"Ul:$#W:4gLF_*^ME=f(#D;I8#7`''#6X`=-@=w2Mk_)Z#%(IM0n7;0vY/\"\n        \"+e#:gtgL-d(:%5Qaa4:>`:%$iTN(v($59`HOh#MEG*,V]x4fS<_T9\"\n        \"aoPh#5bu+3Tk$##?Tl)MCqf[#?,+,MvjnO(:'.7$N'5)Mw^-(#/\"\n        \"Nft#&C(7#Mqn%#cPX-Di#=.Dol*87o,Xa2LWc8/\"\n        \"M;gF4'^B.*hg'u$(<E.3O[g;.Mf%@'O2>HNxK?(&=a=u%e>,HM\"\n        \"eT+w:Nmx$U+DPCKv/E%MB[Ld-3K4I$<qDS[5R/\"\n        \"2'BOb*$djZT-pxY52Zq:a4jfG>#P6pt#xbR%=B_P&#<3XJ-$cj73Lw`V7MB1g(085D+jdGn&\"\n        \"/YChLW=&7p]=;D5?`9D5O@N:8H7O0P\"\n        \"$MlKPG`]2`IxMd%nIw8%gh5g)[F1^H*5oZ$eU]TI.HFHDH8b4DP,ko7mB8).)n`;&d1<T/\"\n        \"Tg5F?YpNT1@FRTKp-?T/9,4v,OjCT.mDlX7DOcm'i&q?#NMlb'h5gM'b4#G*qHUY$?]Xe#\"\n        \"p.+87;6@uL0)$@5#.^=pNT@L2Ma(T/\"\n        \",h`I3Oq*FR5gE.3K9p&?]?o8%c^,T%H=,caF.Q4B5-'Z7*[Y<B>V8s?Mv2.afRwH5B/\"\n        \"doLWm%n3kepT&?8L5<`GF?6a'V_>-W:6950bW'M0^Y#\"\n        \"?x###s7BS[:2oNOpYTm(IbUKCl^F,2>0_fLq<5B$3l:$#`8+22;JU(cHd7]2wF3]-HImO(@\"\n        \"uGg)`rY>#@lXQ#_l)T(1e/(LUq$ouQ#FTr$Q3%t$),##KNft#V=iM3YW]W7LA(@#j,$4*\"\n        \"gZ]%#`@@=$lmt5p&d<I3)h*u@u?3wp<76g)q:Ir8f#AZ5Sg'B#^r&]H)+b)3=3[u%]*XS(\"\n        \"EQqReQ6GZ.`4I-5x6Rt&R&vG4n#k$6?WguG]EEh(gk;Q(**9lL3(1kLg84`#R)+&#MBnJ6\"\n        \"-B,:(M,DV?E-53'Cq=?#Uc+Z,BsC$#n9*K3kQw:MJ_d)M_8lM(Ht&Q/5Z5<.=rte)/FqS/\"\n        \"p9KT%.m@d)dB]@#-WE$O14.Y2Ye%%%@&`13Wx6-5+'#O)4<^v7mvZ>5dE(/<gZVT2:e[W-\"\n        \"$`]B7pws*6jqMR'llCI$V#Mg1MOf5#MAYN^News$mAofLKEH=7>[Am/\"\n        \">@F]-<w4x#Z+f(7w+xA,c3[S#F],?-S6')?ik;3FkTpcFWU%##%^J,Ncfp%#42<)#B)m9%\"\n        \"Eeh$/:]M1D-Ba+N\"\n        \"3tad(^KLW5GKQe%d2g,Xmxe)*]Xv58vq5em1eDrr5?3QNGKB3M9)O7MK(nHN=hV#56t6>%\"\n        \"ierD5rFGc48f%T%x+swA$3Y.+f-lZ>U-B;'m.O(=/MkmDVf#U&)@,702l7C#U:KT&P8CI$\"\n        \"I5YY#4agQaa`d>[9i(/\"\n        \"DFCD$#xbwX$+N(v#=tlc#WGI^$qA:B3Dp&38r.9v-xEAp.DtSs$3.TjiixxF4fnlM(vkh8.\"\n        \"QLJ)+D<lj09$gx6744?#@N=I$_bdp7GX:q86(:uc_4fO#=;[t7\"\n        \",_Wj1`)>>#Ei)M-#8Af.bh(d*56Bk08,>>#FOf5#+)l`ORtlS/\"\n        \"?a.^#`'Cv-n-m<-c>:@-=72X-vDmw'SBe--9[+:2?(gFMJ2oiLOK.IMi-2mpCo:_A,4+:2D=\"\n        \"p+MH&]iLQLYGMoxR#m\"\n        \"U9iP0048#6gdDp/,[BwKO+]w'YOow'F=TfLFDAD*&#jlAXXRP&JN,/\"\n        \"(%7%`&GtG+%,qNWS6a=u-JvjI3^Lue)r,Q&'HEIUW(x$m/\"\n        \"HK1&$<LFN-*LG8.Qmsu?&xB'#9L7%#A2RS#l]7f$\"\n        \"e>RMK72/\"\n        \"87]xkh2mq:a4ex=T:F`JA+6_MuLA9t$#KjBB#m:F&#;a#X$KErk+4tg3'Q`LhL?+&7pgD^\"\n        \"7p[[n'&HZXs-#X+S9<T)B4>Fn8%WgY40K491(o_-p./YUK:8.9p9pr(C,;;A61\"\n        \"EsiS&Pk/m&Q@JQ0ug6Y%Y6Ic*tc:pLp745&B$N/\"\n        \"2Z.Pd2,u3[7*+f>Q;>^T[uEQENmWtI'U`E**2s>A4/fqV$:2R>#aa(L5m.^+*Uw,A#$hb<U/\"\n        \");B35c71M-Jir(@gZ=p&?O223BO78\"\n        \"7AGd3p?Z)4e(mM97p'-4[`wrSg,:>&GHbb+jo,J*q>H/\"\n        \"(aEYc,si<s8u6'l:;h-x.svgM'OOF0(_J)k'Y%P22u'YQ'[Cb>5d/5##P_-u3:8C/\"\n        \"#:rC$#=j>t$no1(&7MlY#.E&c$G[&79\"\n        \"@v;Q/9wZ)4+`OF34m^R/\"\n        \"#>N-]s<F7'*QKI4Lm(W>_.p6D2OJM'Jh3,24Nh;QC*Kh7OA<@#h,a))piQqIOFIp(,#:@#\"\n        \"HWxFiY1_$)Wa,>Y8k1w(/^Np'_Rr+V[NTEaxTvaNB#C@#,Tc+`\"\n        \"J)4>))tE@#-a5A#50_n+c't]+/\"\n        \"?t-$J9%m8R[u.hii#W$q<Suc$uI1$jX_>#:mVUM]r2`#k6Vc#6Hvr#WjW>#b(t4SKoB8.Z]\"\n        \"5r)WR&c$vlxGGih*u@>d3BGf[)22SN91MK]?P3fFNoM\"\n        \"LqOrLL*q%8W^qK*#;Rv$#w;3twKB'-rRAq)vkh8.R3AA>Ya=x#,.$G*g:]5'eVE@Xiw5g)\"\n        \"Cl/0<5$Ix.eY%l'arIl'%Ymj@bbrj94Y3S9;E$)*`%pl8i>[]+F^Sc;S<Qv.Chi;[0`w#$\"\n        \"h,2I2J'(,)$uK/)%%/=)U.`,)/\"\n        \"q?iL*NsH&p^daN(BW-&aFfJLJVNfLahuAGIK1D7[HmQaL^V8/g/\"\n        \"pM996Qv$C-Hb#,h%v&uv66>r1cMBhpRZ,#n.A,$aUU8jKKw#,xKf)pB2D+Z[Nn)\"\n        \"U1&K);oGv,1MBZ5B[H.)c.OI)do&+*$),##A<cD3p7C/#KAYN^jDPF%%;w0#m<9B3u9]5/\"\n        \"?'[)48%5gL0;^P:x4=4Ug%=Y/C2oT0Cg']I#G3'#9-(,)f[hf:kP<>,0AR]4jAZv.KoC*O\"\n        \"i_/:/NX+F/4qNI/SRCTAewUakDm19/Bqx['>G-Ur.Z*@5$4g=pbh>[p.k#;/\"\n        \"VY_N91ef^dTZwe-e;7F#fbQ$ZDNX@,0nb[7tNI7(l13(=RkK)3qMR)*KTtM02;f9&b(P&+<\"\n        \"REU.U/K@6\"\n        \"q'6gLHP-k0bP#&6`NvJ(^BFX$[,m>$9%vV-%/\"\n        \"5##K5`P-xHi2%BjpZ7Kg>A#7<$4,e39#,W4n8'`b:w,bH,-VWBBEaPNP3'9$qU)`f`>#k*R[\"\n        \"^(25##me[/3&sJCGXx1acid0d%<Ep;.\"\n        \"eES<-'T$/8b]HJVMb.W-[*iGb`x6#>fu6v#,%Lf)p?)D+X/&K)qBiGbYE)s@-`B8kc/\"\n        \"%H)hdtt$HiP:v$Jor6MPDYcadT`3m_(/:GPdg)4ZLucvZBX(f0cX(M@)/\"\n        \":GD9F.g3cX(Z1f34\"\n        \"^hOX(h6cX(I<&F.IZ$44i9cX(NQ8F.GkWA5F5H,*q*,p87=s]5?K4m''jJ218F8#6CpK/\"\n        \")3B%j:'oR>6A^kM(lRN;7l@nY6psXdW[h'a*`mp%4&:acW8Dc9&DN0,2Q*J8%G6cX(T+F`W\"\n        \"5LOX(dwFX(G,Guuv]Ss6(Qf5#9^/\"\n        \"Q]5lC;$'f'N2VaCD3)69Yl(At]#OG+oLeZ#xu]tSa#]f0'#]`&*#Jd0'#ui)'M0`d##%YW/\"\n        \"1%X&Q]b1s[$xUT+4LDC[-X)qY17s@X1Qle34$9(pL\"\n        \"J8Rx-RlIfL'Fo%FY6,,)Vs,/(5BT:)T^`C&fSoV%W'/\"\n        \"W-WLx9gW9Ss$?Nu-(4BQL;L+dk2-x1T.)9f],D:R205OVL3P(L71'Yi*%x_1X@s_\"\n        \"oM1WcsfL^f8%#>KP)MKLbn-7clERO,7A4\"\n        \"h[S3X*k]5/\"\n        \"oBN5]*OpfL0eeBMH4P(N,'QJ(0+j&53'=8%OD1[-Cl-x0QA###T4x&$K%h,N<=vWMTi?lL_<\"\n        \"*mL$F>,2aYo]%b5>##r#Ki#1h''#pvK'#x]:Y7@4Gi([**?#:EF.DE/1@#\"\n        \"Z?OX$cLL,DH5>>#=3+veTn8B3@Tkte<@Gd2clOP(-/FM2`ue:/\"\n        \"Yt_F*?\\?%90k%g+4BYB+*9)MT//\"\n        \"k=[7Du^B%.>^+47O(02gsSs$#VKF*(e`U4:<JE-t46TDLwqi+'ol_-'lI/G3;b<%\"\n        \"h<d*=HiRsQ)L4g_@E]H>J/9(%J);g:C5,t&GP/\"\n        \"BFHjePD*bIi&qu8g2ONPqV#VE8&;D^Trg$AnL7W%iLnc^%#_8YX7&8oh(SIS@#wi%L(F7'U%\"\n        \"u/4L#WMg9&0brD5wgIO(ra2hLKql8.\"\n        \"0_H)4VInN(X5Du$mm]R/\"\n        \"u5ig%7$XMC]Q&X9@LA(FXYge*@q:E6mkDY&G=KcEa]`c>&,Sl10h`IhlYEi2,3PCdx:LFEb#\"\n        \"B>#(?F'S;nE'S%A`'#^t(+*+>1=-X?>W%RDwh(d`^NCo++87\"\n        \"a[#K3c-Q'cNHnHQ:TNI3OA^+4/]NT/IhJ79ZFDH*Dc6<.uoAnNPZ/\"\n        \"i):+6&%Lj7g)fjU#$Vp1]$je?@&0T&W(VQ#01bH,4ePARL(4+fS)9Zm3'1cOJ,jqujB/gW:/\"\n        \"^EM3(DS`V(misa*\"\n        \"h-_sLmG5gL?BS)=AS*)*'D8T0[<=U(ObufC`lP?#8(t%=q$:V&FJ$p.hUXgLMa,Q#/\"\n        \"']9&U_R<$qi*87.O<$^93fu@</\"\n        \"jT`RCr-$H4xZ^SDl-$<bM1)DhgfL2P%.MICAvXKWU>^plNcR\"\n        \"P5wOY7GcA7kY&au$:rZu9b><-[$$c%W)A(s3n3G`^'UiB%.DF*^@f%#IL`Z#[[.&#,l0T7b#\"\n        \"p*%[9J8%$sEb-m--Pfa>'Z#bm3879FRw#c:h+$2:wS%;$cf(-jcW.[CNC?vw.+/m:.JG\"\n        \"&g[<UKG'HG/\"\n        \"Jd#8`3?Z$o=q,N1s9a#'^B.*A<x;-.eJj-<4Tq)t@>c4j@h8.x.eRMO,-<8xNCKEA4nXJG#\"\n        \"j;6m)k$6X>7$`MVii2Hgi;&%q5Q<FlrqLsRvjD2%$u/BV5,4di(*461Yj1\"\n        \"Gwh+MC:w(#l*pa#Dsn%#Gtvq&=ju>>%Y)#lv(:Z#JtRw#J*CL(U1pF*vglF#=HslAB8gY#S>\"\n        \"ID*CWL,)m&iD3<a9D5P6wGGVF+F3e#vC3o?B1AcQx4pTpAK2o<q=%mV(.$r-h_4rp&.$\"\n        \"/IehMhc.7;/Q(RLE8u%4[t%v&i*DHIU@8e,Y2sH,viXqB7ml-$O/\"\n        \"mYA+aE;MZcamLHKhkLa8;d*gp'B-HYpu&>OKD*bW9R)@be@#<6wk$+Jl>#[?'Y$I[/\"\n        \"f+Go;d%AT(9(fQ-D(:CL?u\"\n        \"l^e[-jfE.3'G3]-p?Z)4=Rd,*P#G:.;7@W$i9-ig=RPZu4MRl'p[Konn-Yd;-(/\"\n        \"->4]G>#c_h8:NQ/\"\n        \"L25LJZ&b15##iQ[I-6e<+.1wdiL`13d*_YegLmE_2:^%J@>GI@@#5]]E)38C@#\"\n        \"^^cJ(QG&)$TT<8p2[)22'*RrLbo]LA-pCp.x_t'4)20fF.nHg)gYhg))wZ)4Mh;wIsdRa+\"\n        \"FN$>K(N4B[@cQn:=/;MFd?PCFp0b[A2qgB7*sGLF3Tuk)RW5D/oO/a#rX^sLn8W'#k$mI)\"\n        \"uBaQ/WE)4']hOA>_A:>$cDCY$Tww+2SY15/+vn=3;=&(Mnjr6pwt%N2nl)T/Y<#;/\"\n        \"ND,c4$=_C4(g^I*9Ze)*ipB:%ghXa=$ad&4@.w#@<<v^=?$VL;EQ979>8S3'F1rj2+h(Z>8_\"\n        \"GVB\"\n        \".38/\"\n        \"=733&5@C,v8WCHB?Po*&-mu`$'SiCgG7IRh#Z),##kYd_#&C(7#FMlN^:Cn8%+25##^hG#\"\n        \"5U+,.)_;2T/PP=DU/9oAJ7WexO=maXuXUJr/25_f#miZ(#i-(P1-9>>>@(2$#8:YSu\"\n        \"nfl349,B+45Ww#%gs*xLG19L#UU_RXfJ9L5mhw[#p)02#7Na5]%$&##Jm_q2:37g)8T*T#*\"\n        \"f1,vjwJ*#9RR[#he''#hAaD#2Tr,#cQ)<#**F?#:G,##C.rr$e.Vp$Sx/5^X@[lpRtNs@\"\n        \"6/(s$>nI/V;I13?;?]'/\"\n        \"lJGq;U_Iq;BH=kF%g&eZWB%<&JGT##0l=HMJah[k1NG@NK;#gL(0gfLU&'c.iVOiP>[5D/\"\n        \"xk1N#]WX;M&o3h<3T/Yut2tr$7j[s&0r-W$3SY##1lCv#B24l(\"\n        \"%*oO(^X+F32+'u.ca[g)-2,Yu$JXrZ$O9,)n1dIZt:D-#)T_r?=ShS%p`xv$->P>#B?7_8/\"\n        \"I`YPI^f+4jPJZ$Q:4,)gL]vZ.*%wAsaAuLamYW.5Fo`3cEvu,Q1>'(m8QS&vq=&#GVP#k\"\n        \"GSjh2LvjI3ae_F*>-ihLW[-lLKP=:.a%NT/OxE+Pb^b;RV65v.l>_:%SY3uL[BG;/\"\n        \"dO5HjG5^%,J/\"\n        \"$b<9u(9.PruR(*Sap.HK,j#BWL7#<d8Q].W[`$d0^2fx_t'4X_Y)4TW=,K9vjfu\"\n        \"Y-.@0r4@VH`H4,2Pxkr-%/\"\n        \"2Z#@=@s$;@H9i_R[s$qI)'c%[)22_;kO(F`N3X=b89..)rnNBdaYH%=2'o?2Y(j%Aj4iM+=\"\n        \"F3Ot[^Pk5l0&FqlA#;RQ9/Z5o-#W:4gL>(i%MRZs^/FTvFr\"\n        \"no'v#T`(<6(NF;$UD:Ip$l0(4O^rFr+ho.#R5-$vMO]t#'(*)#*eoW7&Hro0^Y@d)u]>##3^\"\n        \";D-L%@s$&-####vYD%RZNP(I,.i.B%NT/Xe)b$x[NT/ZD.&4H#Y*c<R^k$3AXI)0X%],\"\n        \"P0&H;/RK/):l-W-EF_k+`69*</\"\n        \"9Nl*42.1*+5%s8hG'a+Bu3tQV$d@5&<gnLjvCd6C3rO1[5hu$lF@r8vS8r0Vhk8&d)]7*w@.\"\n        \"V8cW0o/hTP^4ffHv$pn7T%5jQv$$),##PPbe#=C(7#\"\n        \"3@dU7/\"\n        \",,##=sXZ#q],3#TPC8p#H@L2^$f0:qI(E4V*x9.;QsD#XhG'8&v=>5<i]N#12UVCm:rK4S@\"\n        \"h(jGC$##R3n0#w)loLngp%#cP(Y7rd==$SR]@#Y#Z>>F(2?#0*]9&k,d7&>x_Z#\"\n        \":Gp0#'a9D5VT6$cpXO7cXp8K27F'>.YYP8.s^wb46jGg)XYpkLOT;a48CLhLgC)K*\"\n        \"U5K7ABJ5R*NA(&OD05BGiBT0HR'BwL&cPs-R:pvL0P3(vc;Bq#V7>##7F2##D]*=7W1pL(=\"\n        \"9>oL\"\n        \"0wX.uVr%F.;H#MTfa[?^sNCG)to1Z#(nq?%-5###WQB$T?;I*RZ)V,2@b@JQ<HL;DIMt>^\"\n        \"aa<M'4tb1)?Dt5(=#Ot&Q0^M'CZq,)#v-s$ahEB-@BUh$lIMN9J-$Z$['ZvQdG@JQ47/L>\"\n        \":]HI$xu=']tP5xuD=9q#hSk&#<Ja)#od*`+1Ci<-kvJ%#=0D=%s9Vd*<V-^,n(+87Rk8B36,\"\n        \"$<-sc+c-g3/m2)_iq']V]C4(oRs$6G2T/4O:a4mp?d)@]WF3nin]4bIoT%5n&#,mWo-=\"\n        \"9x&]A:mi[8Oa%2+w:#W-LsD>2cw#&HUmdA6A.ov7p`Y^,26NH*dA6$AK$;?/\"\n        \"m9qp&ug<m;7f8A.pb*P:X/\"\n        \"tt0#LlY-;5w<-^J@Q0Hsn_+?nju-(W%-)k.?>#LWfuG0sf(jS+lr-.r<p%\"\n        \"=IN5&G$Bq%CI`?#8_Nt$w1fBfj-Fm/gE>V/\"\n        \"wwGH3MMBc$NhmVD.<--E9BFFE,EMPDn-38;:2o/5n*s:J=Lk0E%/5##0W,N#t7)g=/d/\"\n        \"q'r.F1#vRQZ#=I2%k05=1)rZv)4L/'J3AS'f)\"\n        \"ke)Q/J29f3?;Rv$oRDO^5=XT/\"\n        \"UYvT85^np0FBsD+.j(B#LURT0fbY-Mt.8A.K&Lv-W84jLN,C*#ou:P->P#l$;X%##`u>\"\n        \"T1qH7g)%9w^l*_Buuv#je-OMY##Erl$(rMw&#aKB;?#-qC-\"\n        \"Mk5`-PgFn<E)>>#@q_#$;w6qL9LCsL'xSfL,<IV#t$V[-C't92U(#:2$B7R<QQMqMq:\"\n        \"ru7I0x%=6RJM'=d?K%E:r$#Xjeh2v`S+4M50J3Yrse),Nrwcfa)HOt9=^62r0r5-1Z9C_-&/\"\n        \"E\"\n        \"@US'Ac$?e6wmde6oWKU%B*hM;Z&pG3fQZ*7HGOW6l8ZGO^]ftLpaMk4?9Z9C'G`+kx?eA-^=\"\n        \"eA-@?eA-D_CH-Sm8?0#3;:Sqw%vLQqepBt2fK#Eb(^#PWnZ$n8B@#Zps1u=@p%u62'C-\"\n        \"e1'C-2.r90GP(k^8f7/\"\n        \"(vT,edawD8pX'+O(8%1N(mmDG#,Q@uLBXVJuj^Qq2pXNq28ktq2OZ[`$F%Ar2x;oO(X$vM(_\"\n        \"m4M#;CVg#m.`wBHN')X5PkA-OOMg*'ke0:xCB>(nr3E(PZTq%\"\n        \"eE_1(FRbg(YaoHHlZ)22s$,g$/SB+*OAvM0^9Q)4(&r;.@.ikL]2aI30I?v$D`Wm/\"\n        \"@[Ns.U&<J<v<H>-kW^F5I/DB#+lXV/uqUZH&*<VKs+LlH&/Q_/]O/N-6O/\"\n        \"N-LafK5](]r%GSKq'\"\n        \"8nNxP.(vRf$uL`+FOio(+Bq0cf;hG*n#^D4o&^D4E8c;-([CtL&]CtL/\"\n        \"wd,*cOTs.j%LgMbbx],?;BqMT<g?PbUBb%qt](WVOKlf67RS%07###,mjh2-Ov)4HjE.3ig'\"\n        \"u$0PXc>dYlS.\"\n        \"KkQW8t8T;-ca)f3ha9B#K]T:#=jAQ],cUv#Hw,@5;Lq12g<_=%Y3NiPLCB(/\"\n        \"AcawjkNU;.?)=,<Q6`>$tm-<.wGgI*ce2q/\"\n        \"Qx0dj3sW:vr,Z`-fev?0H7g+M*c68%roD,s#)P:v^_N^-\"\n        \"Z2IR*dIKR*KJNfLl2C'#R5-$vvcEp#r,^*#V]eZ7$B(j*K]%g+rt4t.DbS1_:_r/\"\n        \"*ovLw.Gc%T%MHSa+]g><8k.Mq'fa+$#^9*K38'$@5;Rcn0MqQ4()^C3pu@o>SYjv)4xH:a#/\"\n        \"Pr_,\"\n        \"gmngu>Ep;.Vp,d5H5B.*_;oO(t&tS8*KP8/]W=H=[HTv$r#_#$QO0U%.d/\"\n        \"kbZx1*[F0PpUsU&8BOY9c-AX>^40<]_>'Q1U83jdw$o(re;7g6IDC>#FP[B5M=OShl<6I>-\"\n        \"3X3Om/ldq^D\"\n        \"=MspAbht)5+;8M*/_;k;DA/'7cihg4B`R_utMLh;5OIxIZnD#C+'Ku:HG,)+.x0j29CRA6/\"\n        \"^B%6Yg>A4[0,Vd3-O8%'/###fqD^6+DkL<4EZlA+,Y@?Y1&g+Rbvb>FZ@?\\?Ufr'#l^O]#\"\n        \"fh6w++86A#1>-##0[&;.ivAa+X$mD>5%BF#tLt4DhF,+#Nd[/3Sh*u@i&8F*?w$12*dLP/\"\n        \">rQ4(A(=3pLv$2hnlPr7/5>d3)+sRB[U1E4&T'i)Jx*-,gh[s$Z4(p$]exC#`Z*t6e4(<&\"\n        \"haC.;$/w3:9sKG.VGCK*AsA<9+tp[$m/Fr7a]/-<;'cY-NI#L2[bBe2A;*X.ffwS)/\"\n        \"ETZ-;gSF4P8o5Cqx<D'gCOi(M1s'#1@jl&W4Ba<9D(7#4`($#JMiq%>lD?#CL<5&7%@8%\"\n        \"8i1?#\"\n        \"N.i>72[)22Z#Q;pdIfC#LZ1T/\"\n        \"<#<F3fwSM'UmwgU)fEC3:m4d#Zc>EP^$T]t&5>##mn,D-W99S1Le[%#Ow`V7_`8B+h8D_&\"\n        \"GkeY#/ETS%:]EQ75Z)22(DHs-?6J5B3klrBuDqh$oPq;.\"\n        \"^;exG+VQd3SkGJYH)2J3,G4I$>V(i#_kuS(`tue)itLq@gZ#4)2^x;:'p('#X`m$vX`>j#d;\"\n        \"F&#@MI3'>G2q%OP1i:TiSmAhsG6UaD&J3S*+397p@Z5/86J*Sg'B#uktw:6Q$EEf/x['\"\n        \"Q#iA=4i$&%=#0eMK0tX'eg%-ErdLd>xKt-E=C`7Dd>&c=[/\"\n        \"=GMsD8#MO**$#TP(k^w&wT-t><q-IoaBo^Pg<6Nq8+EG=@xU8ukYA`MHh#+#a>Ck?.aK)@\"\n        \"FnBQg3j.#6YY#Kg*V03X2V7\"\n        \"107[%](ofL8]_p7S1Ke?>=6g)FN3jLX`3G#bf$S#+j/\"\n        \"X#NOV*KfBF(K3QrV.liOs%ZJx?9<q3?#DeNT%mA3'#tpxD5(_?ZOtH7eMD*QJ(PwAZ.BaB.\"\n        \"qihg%%bw^rdsvAJ19^12:`.m>@\"\n        \"D&s<%jd(k'-5`v#h<g'-IvTT.2[)22&2&b%&%T#>vn5Q8'aE.3>Fk=.=$ct$$x8JL_*5CQ-\"\n        \"5,hOSD-DF6%I&@LELQ9;X3@#E<g4=Z8WY/,V[(?kU_fF8()rC7L]BJ2tF60ApkfVp,5W&\"\n        \"P_.H+Ss6:&l[0F&b);K&smj5&B_aT%:l1Z#[vwZnfpMM/\"\n        \"DctM(cm3jLA0H%Rt;0,Rc^8G`$t1&+6Hso..5DL>Wwo(MKP?##AEtS%8`uJ2=F0R30L8j#\"\n        \"OTimLxBsvuV*:hL(Ua$#NKoZ#\"\n        \"&hl'$J6sJ2/\"\n        \"o.f$2kE)4b1iJ)nKn]$L_eFNDRF<%.=76NiS923icCM#S^V6LSwt3Fu$.YMi%4V7C1rv#\"\n        \"nnHw^21nw^ptMiMQnCD3Mj^#$x-V?^Rr?Vs5lnX`>**b0$P?_ui5VG#8J&[N\"\n        \")LihLF`Q##?erY#>Uov$*'vY-2P(g2LgJDbkY:d-r0w?0%Esx+0:BQ&7O&m&D[*9%MhCd-\"\n        \"uto'JYG<F3ldfw#X,;_=s$n.]<&7R`KXqZ$cxf19$mt10)qCR<gu4wLN(w##3B0w#mH;^O\"\n        \"0M,@5:ErA#*3%:0a8>oLpe=uLYR:[9G?6Vd8xrZdYw$##Y`m$v6>eo#jl22%i5fw%/\"\n        \"tp*#YT<8p1Zl8.=/9f32pVS/6')s;sF.@&2his%@'><B$)rP/\"\n        \"5,:9EOR93>UFe2L=E)IEDm8,M\"\n        \";h>oL/\"\n        \"(8qLqS<$#9:ZU77Wn<&W>n)*];TP8Mfdd+<&B.*HRIs-_KJk9=K*d=K[U@I;;\"\n        \"rDIYE7q9405G`pW]3)A>LV#U&Z++_w=h><mbJVQ,8wg57EM0?Dt5(stcH0<hDD+Tq#.)S3/\"\n        \"nj\"\n        \")0':fchC.3FHOJmW^'9.%a5J*f=M<-s:Ld18Lu8CqAQT%WBF=AqqZuqO8@sB-Ac1L57NYA/\"\n        \">_4t;u>3t''[`*<;t5((S2E<K?P3'LqaT%;Vco.(5_t@K3K8%3AXI)g<3:.H)rM'v'Z,Q\"\n        \"5o'<VoX524XGA%HeGIVCN+J0+_aM[tI(=02=MD'#@#sx+j?/\"\n        \"&=q`gi0BS9Q(nK^2'Oi_i:W:;v#dvrl8E`9N(dr+@5V3gp.=U^:/\"\n        \"9>LJbnS_:%.s<$%_?3r7Z^Tp7pZOR6H3)?'f%0*F\"\n        \"PX2^Hg>@QMCBf.DM.>)>@d]-P:0YH7R$=<-pB5n&*O*t6GvNR:KL]<.->@3'WD9v&\"\n        \"nxa9VgWW$#m0#b#<Oc##Ew%?#q85##T.i>7GSjh2k_Y)48%1N(f?OA4`kgSK:L9%M,QEmLq@\"\n        \"[tL\"\n        \"F9'%#U&c[#2q*h:qx*87kh'N2%B3I)X',f%fp*P(<A0+*G)TF4FlXcM)#MaN-G#=8efRb#k_\"\n        \")/O44#M#tdPf=dY4G#J2%I-S-%I-g$b710eDV7Zo's-HwC58tx8;-Y;9c.gWat@S-k,5\"\n        \"6[M1)lwH>##TC+VhZ)ju16cQu$5,>uR4^Z.:>uu#E=(*5C^fW7$wBH2jWnY#JAcY#9[aT%<\"\n        \"r:Z#61^gLFEj>7G;2R<hbCJ3qvi-M#WGl2Oe;v#%S#[L*GcWLCTS@KTe2kXHUFonxe1p.\"\n        \"=4Cfh1)s?Bw)M3Xr,n?B%0<A+&;MZ#9cc>#1`:Z#5.RS%3IEj(ESwQW'cE<%:<UD3RnF%$u#\"\n        \"PEVHd*3oTv(NY<OOEr7$PV?DoIxX;%Vq;r]Gq;8^Kq;jECG)ur1Z#2Y(v#i`fGM&XnO(\"\n        \")WR35vK^-qAn*WV(I7@#Z&HV#A$;cui+,##&>eA-L(#W/,:ZU7(1`/\"\n        \":L=,3:ic*87s4Ph#-8fh2XIZV-:eS@#(?(B#+vIl#,GMO#vx0PfA/\"\n        \"f(3M05##?98X#0Cr$#fd0'#Cv9I*b]%P'\"\n        \"`smQ'/\"\n        \"3n`*K3#R&RcF]>@kfi'7uSq)T0no'Q<w`*T#@h(M8*,#A=Qt0P8V#AtZ)22A[7B3*\"\n        \"jumsj8d8/OM>c4<:b=.vBo8%?/'J3Y(^L(2tSs$wV3C$F)HlhuK*M<HUpp.>WdO*u'KEj\"\n        \"AV<J4snGX.S)i)3[/Ao:.lgd)kbdu-I/\"\n        \"6veD2iv.WFs9%=3fx,uB8w^<#F+r>Cd(j[Uho.7v<T'SYv5&3V:;$E)sc#K(..3$Ek>\"\n        \"7Pxkh213ng)F$nO(GF,V%Dsse)eH]@#JUV:JH22B%\"\n        \"21Ic4N:CX%Yo0N2Cxv[-))D*<?G9quaUx%H[.Q(H_:=T9=tJd+77(g2&)?>#<&E+r/\"\n        \"e'g(YA6Q/\"\n        \"@c*9%w)QG,)6F,3%3F,3V'?W%wv)2O):`[%^3G@G,P[;%^1H,43n(7']777;Q_ug,\"\n        \"nuE5:u&>Y-kONm9)uu@5GX[Y#Cl4^#:?#p%vZ53'PO-v&Ionw>e?lsL@MJv74n^H4(roL(k_\"\n        \"Y)4bP/J3`A)6&vkte)48f77V7/SBO@Nj3#dfFFAMj+3[JYF+1$9Z7d)mrLLqx+6g;>>u\"\n        \"PNW[I/\"\n        \"L%1MwDLp9x,v)#sE*?5F@r$#V'+&#K2UI*77f?#Xr82'Lx*A>E7ro(i3PVHI%ro(N38@#E[\"\n        \"Mc>]1`[#XnPY>b]N.)g%=hYabW#A8k41M$m=8pu-MkLm$/Y'X51IXITI+4k>Uv-\"\n        \";3(sL=AwAFF/OI%^LYC-]Li::5=x^%=r4CJu&wqM/;X24uFoC#)K+.#80CG)5g_fWot/\"\n        \",)2Z[s&7d=?#EYlr'7:35&gJ8)ciRC8pt2,T.K;gF4i]dt$h$s8.)So6DL2?,>q,3[(aSSfH\"\n        \"w-R'ITU4YHgX#Rs=.dV-4C.?5tD-(#Uo;c+pDUJ+eh9#?G>MZ>^E+sH=VtE$IFYY>S5H?$\"\n        \"1mLv>;Zk*$Ys4/CJP#Z#PN.)$PZXkM#,T7.[Y:mLfC^s&7;1FcKOB(4u;$+<1v.K:Zq'I,\"\n        \"Mo0o00EsX@XlUK4XiFZ%l'm;L2(:H2,O$u%OuRlhB3Ub,uN7v67m_h*nbY/\"\n        \"<ULl8JBL`7:O[05:QE/\"\n        \"Y6k,>>#kDfTiVGc(jN)%)*8tG7&J`Iw>3f(?#OIe;8to]G32'Tp.(o0N(iAeU9\"\n        \"'9aP('k.`>$8]-PHlk&+NAc(jtgB,FI_qR#-YDG+hL3ulec*87Ht#PfW#'v#A^?D*H,N2K?(\"\n        \"[S%88oo7b4W;7Q22e$F?gw#73xq7&I(j#$#Yu#bZIuL^N(`jqVr-6vwfi'6s<T'udKn&\"\n        \"]umpI0<5>>q1+87j7x*7cErI3aN:l:$KK/\"\n        \")E+Cu.l(d%Pf7BAQUMtv-%0MFrPr'd)ffoEnE%SEnw%2R<jbws$=7@<$=F@<$dW6T-oX5l$\"\n        \"Em^a4>=3:.grG*R(DLfevCEQ4sdr#H^/+`a\"\n        \"6Mu,8#OO&#*C(1&:&+87rZsb$nFVi#RfSgM+8I>>]Rho.AVTm(#E28&X44x>aF:W-6Aw8%\"\n        \"Yx`;7J7nq$sqeg)KsaI3K^^e$SnNpBIv:E3D0uI;NwN72xVe:&9RF`/`0*Z6,1h^#_*bp/\"\n        \"cGHt7^:w#6G+QV#Aen1MRoK1&$w$)*/K@W&Y9K6&?s9s$p`7X_.8^CJwM[L(JdZ(=+/\"\n        \"$M&ZUXv.']V+(a&E+r`xQEn1D18.TXqZ)aPG?,+`@g:Qq$w-c`.<8l9N)N0vCd2VU%1MP3Z<\"\n        \"6\"\n        \"Zk_`=+Kf`GZ(H(H_44T9R#uMMOF$##gX8&vt=MMM>*v&#PO,I)Rt5R&e#vA,1AmTiK2+87$^\"\n        \"EQ7bv&7pW1pL(c?D$%BAUv-hsXh:729v-X]h8.-cfFFVBmA4'>`IE1$9Z7SP&G3aw6@d\"\n        \"+%g/\"\n        \"<ShJoLK5*#7:DcX7j*>w7FGUs-#KNjLu^9,N7C=V7h=Pv,I7X9%>TF;$9T%RN;Q=Q8-8C#$,\"\n        \"%8U.Ki9u@_KX,%jTV:JxIa+3;><BQaVHusxCVp.9^V%4,r<BQ+',[7T)c8.Y[I5&\"\n        \"X/\"\n        \"CO(Q2tp%xYu>#=s>5hfcJ)l1BF:.2);P(4V_U77]r_,5uM;$[cCOLT_S_u=?7_8XdD+r`)`\"\n        \"Ee:%N'#FJc%?GfLGs)Y#K)K_h(E'=A]#]W*&+o-#l*iZ78@ilsI)^3]VH*?r($/dCv>\"\n        \"WS-g#'6S>#IAx3%sg*u@P`-u@/v:T.;.Th(M9-<%qg`I3I/\"\n        \"9f3&w;gLm?C:@JQfNidfOl][gD$5?l-23EF`FGobxF%]ni0*Ean-=/\"\n        \"@1tHnoIM1c>6N'rC0<&8:E)5c=o?$YQ5j'#]0<&\"\n        \"gRYEebms[t_iGPSM,@D*+^%@>Q_[<$<]*9%63+ve8,6T.PNv)4nQ*'%0uGg)8Q[N53F4JE7>\"\n        \"5.P2Uu(/UB$T@gbIDR+H#1/9vc3=xfE+r:+M;$0->m/T'Ot$INpq%g6N6.viCp9F,>)4\"\n        \"9d0<-D4]T%SXTx$AN0X>hA&n07&uB,<CU+<4rkUC7VF.h7_?`4lwn@uY6kp/\"\n        \"'#J^.05YY#@Lr23,LvU7$JBp7-S:v#5:35&AP@m:(u]G3^n4g1c.:mF;nDo#$r7P#OTI+\"\n        \"MMQG&#u5wiL\"\n        \"Ke<$#PAvX$.Z1)3mg3v#3=WP&F[ws$%T^W$EQ0W-j=>-tkNXVR[mVa@#TF$LB>'tLReTP#,\"\n        \"fOQ#mp=Y#U.wUNv@VhL_.3$#>,)U%tmN7%WFE5&n>vd4u20Z-2cg/)w9$`4r_@@#K7xpU\"\n        \"-ZH+A$9?f[lD*s$R+;juB2,:;+5a`#.Keq05@dU7FX3T%Bhj5&lGQj0Zf'N2)wrI3OYGc4p/\"\n        \"`m/H#(Q9Vwc6'TIJdI=,2w)R4Q03RP,5055>Yu#O4Iuwq4:03),##?BmD-cU&5;`w9B>\"\n        \">x:Z##PR,;03j1)]H7g)8T/x,DgXiTu=bI#'WAeu4[b-6gAt[tq(6G`;e/\"\n        \"2'A3.s$PP=gL%w@j%[h(Z5W3p;L/uIG4ir-D3&BS05/j<9#:OI@#f/\"\n        \"BkLKk<$#K^fW7NC.w#2.35&YV*e)\"\n        \"2*naN4ks`</7wCuDt+@5KUM7c`sDM2DZ)F3aAoO(P*s8.XmwiL%Q^Z-nQ/\"\n        \"@#54$wA8S^>82]<UErNQI#*)K9rwjNgltq=V#ULYcu(oe+MhxZY#nrp:#uFlJ&*Mc##jh53#\"\n        \"6e`=-Jr`=-\"\n        \"?#&Y-2dMR*k?t92;qc7eg].GMuD](s[WM#$dfS>-GMS>-NW4v-Wkh=M].LRNF),##GvrA#C-\"\n        \"(P%U-AK:7*UHk&?$Q:T$JAG@f1fMp%eu:`nSk=[u/X/iq^0L>oT$5%?-C#$CR8#d^''#\"\n        \"F>Hb+uR1K(P9mR*EULx2hxM0(B7dB?t>_TIK3pQ&L?R['8@SX'f4gM'wf*87xJ46pE8Bk(\"\n        \"rZ4N3v5)@5KGS^$]p4<-?#K(%hY[hE*6,^Z$7]&6`aDd2L->_=rCH(BZ$AKDnq``+7j1T/\"\n        \"sn:d4YMw/(lGi02guGTs<+Wo]WM*.)AA?w-%/\"\n        \"5##w8$C#*]-*0YKb&#G@#e)W`fJ.KVD(&HZ'5pCnWnNg7cG3q>/23<ED]$jl=.3ek/\"\n        \"W$g(.[-6$wC#jG2&7S9wo1Sne/*F@r9A+4Cc3\"\n        \"UfuS.&5>##jg^b%jB9G`^hHP/\"\n        \"nqEV?]lHT8_csc)@SRZ7>?dp89erl8mqX70s$P%$FRO:2A8BJ3Aain'&%x+27'xl/\"\n        \"imEL)*YZZ#tk75&O7Zd$:?%12xq,F3[Y7B31SDs@pBP>H+'_Xo\"\n        \"u?i8.7W2''pI)W-@8E'o&ErU%j9*>P$>i+>A?:dDT0g:T55M/\"\n        \":1goDG)0E*GeMl4<M4t^@&6ko(wqea+^v612F^_l01nv/\"\n        \"M7_#7)#J7G>qVEj0L-Mc5SClC=%08DG]_v`G`['xA<wbI5\"\n        \"Zq;D+;w;]5^W0;.>H:;$XJ.)*iQh8TFN`i2Y`1A=j1ckU:+64(Ql*%$`-EX27%ZG2vCEX.q_\"\n        \"Od<<jU&G:<5s.3eYv.>`HW.Y)0CO%2HD,h(DM%7Z*@5nl^t@M&EH(bY)22&R5u7v^8U/\"\n        \")^B.*ipB:%R`?<.LO'k$6c#po&M1f3U%6s6D;qX@O^7v55',2MbCb2(CGWt.q,=H*B0aD+'\"\n        \"lLrA`oqVA/8s&7Yfhg4Z7vT'ASDI$Y8p:f;w/N0owOn:qwu/(XQT`3U^p=cmI72'GSAE'\"\n        \"XD7W$cUWeju%*9.OM>c4)u[s$1YNF3g'5j1FtH,*6XA`a5W[%-?10l'?nQAO1v39/\"\n        \"=lGi^mU'F7mEYQ#P6j`%a:EDu.Deh2@a>_&sZ6=/U[_t6OT@C#0XH>75Zn@-I+65/\"\n        \">tDi^`bV8%\"\n        \"JE*##+q<8%<Ts5&Bw](#4V=wTL0QtLa')'#I%###(J,W-(b=R*;PoA#Z/\"\n        \"PG--&ukL-u=9#=XL#v`7n0#WnSgMi+p+M:;.&MTg'Q]mVj0>#jN#v3eR%#6;7#\"\n        \"M7uduuRgnwu_:WD#0Z<T#\"\n        \"]N`t-^KlM;1XZ?$0135&;'`Z#Y-O_&TdA=uU;'`j)dDoe4=NP&B#RK%#rDW-W:QO2XKYw$\"\n        \"v9FQ/eujo78=vCj%Ee8/TAJwg.8[M'N9&b=cR_J:^^35/QtKR*h5%#5teN;7*O-)kC><R*\"\n        \"6nLR*NC&:2G/5##aS.K1I_R%#L%b0(-bF1:bd3L#`q&U%Yi]E[,a9D5*s<3p-xkh2ZH/\"\n        \"i)sl:*%LtHg)KdQe%MoRGIf&Y<-U*tb$[RNt>p,]p&j?p1p348p'b<&/1)X]0:`'TM'Jw/U%\"\n        \",vGT%/^Nt&4RD/\"\n        \":+h]5p?q'*)fsahFA@uc-2#)a6WKH)4<#<F3x7Qxkl]%W9#[3M0w$,?]?<1%%i)GM#G04&#\"\n        \"1J#lLQsU%M/1t$#J'jV7IH]0:D/Kq'WNKU%>HRw%IpDAuh?PtC+O8m8\"\n        \".p]G3^%Bs.aF.X$BMeC#:$Q;;^2VM9_QrAG?P@E?`lvP/f3RjX5qC`afo$9.fs)Dsx#/r/\"\n        \"I%6N)dhsE,en;8(j)c]#&5NJOPw$*.P%IZ=Jn:T/ecWF30nj8.3v4c4V^=N(m#fF4^-`;%\"\n        \"Tl*T%:qB^#nr=a.hv+K3`HP70XY5;_dU?8N%r91NN)E[.It%[50s5#-Inn5/\"\n        \">,1RB;;P`uC-wpGu?S'(I#vw7<`gn;UGZ:Tt4NO'['(1(9>N7:u&&7p0WTI*hH,e;[\"\n        \"JCxRmAvx'AM;ku\"\n        \"9kk'Q@9M($>,E+rCCxf(/,V`3/\"\n        \"fZrHF.QD>x)1<HiZq%.fEGA>+X?_>&BLA##3B>,._+B#k%;8.3$RC>pX_<-QLwH+NX#[\"\n        \"5HVh<$H'TM'R*(l'V.Ro'P6wo'C]wS%T#@h(GmwJ3<doH3\"\n        \"vOLe$Jo*EMjD*22aecRu;t7krd]d)M'qB-)v&&J3_R(f)jrX0&h]d8/\"\n        \"$Ah8.l(9f3=Dn;%0nt:.uW;,FTbgH2KLXL+IM3E#3<x^%k9K725#A^$&2Nt8Gw$h(3+*W-f*\"\n        \"=S(?-rE4jOSC#\"\n        \"IoN'efn:D+qA//3L.-5/QeC>P^[-5/\"\n        \"BLgr6:_-K3n2j-,c6AA#ORt',?2+a4QJf[>qne]#;,5GMA2YY-&tNS(j6I;']6q#?jll%&\"\n        \"T9*K33@:J5]]h8.P`k[$p7aa<+wUNtrKJW-<w`Nt\"\n        \"VSm<-CQ^W$FeW`<J$I^5tnZr@Y&pR.3PRf3@&m5/\"\n        \"^Jcc+o#;a<(*CB#L<x^%f*N13T<141P3wmsMxG,4#m9x?_QB(6+lYsepSh(#aD.;6kHJA=?\"\n        \"Fac2N@m/*1]s?>#6pi'Lr<x>UA@8&\"\n        \"3a7)$-I[,3(s^Znl_Q]6RMab&_K#=-)fK`$p]B.*hg'u$da:T%?h'B#pOk*,Xk64(i(cC5M=\"\n        \":O;H'sGDU>7D%EA)&,/k+5CAg[L(*.cK1$kV.3'Emq.X[LCsd)+FH&j;V.H4L:@o:`v@\"\n        \"6cqp$7u]a#G#W)A65J9&?:@s$ko*87aMpY]]=EC=&D6k_dtg[9`ukRc+A[Nm.uhQs-r+>5,\"\n        \"Y6G`bCJfL@$00:Ax(Z#HLRw#.rQS%%=oG*IHA3/t/oO(O;'M<lAQ[$vf8^4e$H<B9/9q#\"\n        \"n*ED#=9EtAp,;h*c+ax4`82s7KfA[-UiSY,4gw8'PR+O'MQN'%&]<h>Pv&7p^Nf;-mvJ_%-\"\n        \"lh8.Y<fD4YLH=&e8Lp.>h(q/_eHf=J]5)%mI8WB8nuA#dtZA%L/5##/5YY#[+>56Mww%#\"\n        \"hWR(+-],;74-'Da_DK;$=TZ`*,rxi'0fVW-/C$vf,MJV)QT/i),l+R:J1S6/\"\n        \"ISoi0PL459Y+avSafd0HUCB`Nv(/s$9#RfL/\"\n        \"fgi'i2q.riLp,2MXI%#;K4h):1F9%L-uO'pn.F<VsSwP\"\n        \"bapEat@7:%>R[S%IF7@#Tsu/\"\n        \"(<+.s$F>;9.LT(9(xK$Z>k,pG3j1'Lt6q1N(2=@X-uLP5;,`RM0a^c8.$/\"\n        \"UM0h)LMqr>q0#%Ys.($q>.qlxN-#Tp*J-O>Ru1>-sV76@M/:9^P>>YZUfL\"\n        \"6OLp7ms6B5swXRuG]ET'lWk=.]BF:.N#G:./\"\n        \"a]1PV2bRgC^)s$Y9`ZKVSn[t38q;-PYY13>9/W7F+`v#/\"\n        \";vp%',,##wbpp7lc^j(G^aI3NiGK&><6`eA[o^-O:r`?AKiN#LMdJNZ+x4r\"\n        \"G8<IcN?uu#_Y]4]ODqP&`U18.'9'/\"\n        \"1ks^v-3_.##_Z>r%eL-p.p.CU'HCrv#e]0:fRKMA.^]#K3[v/\"\n        \"K2uE`a4ZRs>G:Uvv$<>Uv-ZD0?nxC.$$Dd07/1F'N0hMu/<sF.@&+6-gNJtSd%\"\n        \"2I7hq%kDq-W_EN0uxMB@>9HXT&,FjN&hC3E-6t22/\"\n        \"-[KCqF4-MQJ);?bCXs6S[@%#&^Q(#&Q5Y-khY;-llFv,0H8G*fw^#?E1i;$MN=;$mrGHp?D>\"\n        \"8p&d<I3*T)@5]oSh(LWeh2=BIIM\"\n        \"v#(f)kHTZ$Xw&-*X1NT/P=On-umwU7w%Z#>MiYR09;u`>vq/\"\n        \"#'W5;Z7F`h>,niX87mnD%6bJ[RT1w]v7qA>t7c5-k&nhHI4kIfWEXKHP4<V/\"\n        \"J;Q0,Y-tW(D=EX<v#NI8s5FP6A6F6<&5\"\n        \"f.Lj%Aqe(jlaAJ1e9Ng*iD.1(V6d3'FTP3'ebHP/\"\n        \"%r_v%cG*p%2G#i73_.0:PFbx>EDdX$f.:t@[Y:mL3`_#A,V`29P*dG*En<W-(_\"\n        \"vcG5qafQAx3$@(a_s-=o8c,[W##7,;,R::?Mj7\"\n        \"<)p@5/\"\n        \"Eo'54Hh^-O4%##V.xfLZw'kL64-&#&'k@,Q-=9%A@oj'.`1?#C+=x>Qd60LmekgL4io#Mdc=\"\n        \"t$2RO;7=S`b[`.@T._T/i)hLNY$kVdm/Epb-;#e4fQJp6.'1xuKPa[=,FW^ET#\"\n        \"g@ie$CNEkOt6;Z5SdoW7VJi;?cR2#G1f_;$dg^f+F2v>>G,v#>J(_<L$,F#>uCNuJ2O^=%,`\"\n        \"NN0vc#lEDtBwPS/F1>Zv&k12/ZCR2SlkD/48f$@.fBJ]ic(jllIV6;C+9%459>&Y*RKu\"\n        \"aX[W$7XDt-:g,W8gd3A'=d(:%)j_&Z(?jP(K@5+P6o(#,c>rC#M9S4L3LE1#LaB#vZrT^#\"\n        \"6rn%#x>$(#r`:7/jSSD$H2j]+M=/Q0[mn-)Guc##M.2Q8K.$Z$pLrP8e)H,*)c7j9ir:T/\"\n        \"IB`a4@'l/\"\n        \"1UpHl1Xh*c+?&0;:.lYW-WD;C&sfC:8,CuQAXG1hLd+xP'a1xM;G6K`,6+nP(r)IC-$iBD=\"\n        \"5FRA8lU$,*7WHije*ST%l(?>#'ZZc)g*85&XfW]+&M[8%-DF&#o;=wPeWx%#\"\n        \"*H@Uu(xt6p#WTI*)b9F,CJq;.LF^[>+aZp.J)fS'9YK/)iUte);=Vh3lJ9ro;b1v%HO@<$8/\"\n        \"Qp211+w$r:CqDG?$,)/PVwD,LnlL3[#w-eosI)v?pH<`kR*5%eq97K*tp%a(<QJ]onU/\"\n        \"8R=%vuh%1vU`ovLs/\"\n        \"s<%%x%m&$,>>#q=9%t#xU._2Cjl&X2wK#m[MBkmljh2B<_=%_,_]9ETDXa>E#&Y5H%j:X(`>\"\n        \"?s`9(/GKkc;%&2m&wF:9&Z;wK##u_X:5q.B?VN=OMfXm*v)%T*#\"\n        \"TXI%#n6qI)oJsP&#&+87lI9N;]KSs$]]d8/\"\n        \"gsdI<C2s20_g`I3<xZ3)W&eA4=8t#?Toke)W&%1(F.aB4.D%Z7B6jd*jK5j'i)M3X6]/\"\n        \"F@%T[`*:-5d*7Q;a-qB#F@W49G@GA#ENUAM(,\"\n        \"%S0N2`Z0,5*g.1(wZ:hLhTWi2Qe**,[Pb/\"\n        \"Mo&sK(f91_AgS3_AopSc;O]<_,E;p^+N_5w5Zf]B+NsuD+%N1kb(Hg.A#WTI*\"\n        \"nIASEl4fHdl)_$7UV9N2Y$N)+4vdw6*Ax`=aY-<.=ZP=$\"\n        \"@S5RELN&REEU^o@K^p,*.C#n&XRjrepvOa41h5N'i9E*%L;$4M%,<Y7m6d(-C_YgL51&1(\"\n        \"Vmr,28_[29D'hr.Qbc2(ZAmR&tRa%XdCs.LD[78%XvC>5#)P:vjfv+)0e^rd-3[xO#b[=%\"\n        \")A+/C-(35&i;WU.tVVc<(d'F.Zp&F.<r:F.4aOF.$/\"\n        \"LF.dKUa#hTx>-O^Xv-T0O$MYIM9Nm=6-N<+H?,'9AF.t+5%t5;gw9&0=F.Qf',DUVY6'3.\"\n        \"GuuqhVs.@;#C/X;t`=AT>AYRXYVR\"\n        \"p$WPKFFB)tGHM$#Q&G?-ZjF?-1rF?-T0Z@.5`:;$==MMU1]TSSjC_VI)Au8&fR9)N_\"\n        \"pIiL0oQ5.3LR+OC1Ck1aGf9VQm=_/\"\n        \"a&ics'RjR<0x0HM68_W.E[n[#ANuG-w`U).3xYlL2D-N^\"\n        \"LfDR&%ax2)tf>oLl1_KM%;TrMMFj:mFw:B#jM;$#O#:W.>I7%#'Tx>-*`=Z-etmkF8RLH&Z@\"\n        \"t(NrPEmLCQ<UM8=vWMl4?Y#hmP[.x*C:%tJIU0_RR@#0UR[#ubhXM_SBX#spXH(]v&F.\"\n        \"-F@F.NYPF.GBJF.bBLa#5.kB-I^Xv-5RSvLt;:<NL$VdMwt7L*7(UdM=)D#vtaK#$JKx>-\"\n        \"BfXv-pD<jLQ0'L_xiS=u&4MI3Mm2c#:o0r0-o?uudRs`ucw]a##5(@-PL_w--(VINWcr28\"\n        \"$1Kg2@e,PfI57;[FbP&#s,7'&:JBp7ao(R<YDrKcLi__/\"\n        \"U%r*OHOH(+MPk,4j;5]uA%F@pl`w+MUwDK_tJW@t`aqKMW=6L-b+QaO/f^X#Bt`/\"\n        \"2rn9Xq.TVc)l@m-H5@j-HIhv4]JqD9&\"\n        \"0]gs1MCmq7%l0^#OQx>-?]=Z->V*wp>q%F.*GH^,^n?q$:9A9vmKdl.%g''#BSx>-[#ai/\"\n        \"`Pe-^C4@s$rTt#6IT>/((K=_/S(`_/>8Q]M6Zw1KjZ>/(T#fC#r>kc;_Uh(j1:NP&X,[0#\"\n        \";ga89K8q#?iHci#ddRuL,.LxX<;qCaRms'/\"\n        \"wHW-$ET=K2*75eR*kpWL#)>>#uYPF#&DIU0Eqw@#%####NQ4oLJ,+JMZrd##D-+s$-<4gL8-\"\n        \"6V+'Sjh2R<_=%?5T;-C`O_.7JE5Nk)&rL\"\n        \"ePQuu'<G>##i'lN/\"\n        \"R)JPk0LrIbbkI_?t[:MowqWLgw&RE4^+REk;&#,P0csL8ecgLx_>2$H'KV-o<Ii.Fw*A#\"\n        \"JErI2+9kZ#o=D$#$*S>#)E?n0'?qW.LBSt#/xX?-*(Y?-`vM60Fl_;.\"\n        \">gmF#PxC@0msBk=q12@0,mG@03.N@0eFM$#*$J>#T&Y?-(4S>-LwX?-0.uZ-qFQ@0OSY##\"\n        \"fASj%5+9T8.)?DaAVLG#t%2'#sgGlLA)MB#hML@-)tfN-5tW30>ZL3$9^)c#q#2_AQon34\"\n        \"fJL,*hHvrROa,1)3.GuudRps$*es`<oR#Q8Or?w^=]MX18PVX1EklPN?7qm&*j9[0D-IW&C.\"\n        \"7]M6+v8Np)epMU#MD3(tK2'LJ?T.DM:<-mwX?-nK@3'qM/'+C3Ta<CnI,8s_e&,j?/&=\"\n        \"8:22U*Sv`4&vaB#^%S_#Y%OG)I$-D-0qX?-wwX?-<(Y?-_jmT/\"\n        \"T:d##31CG)a.7K)J'$5f<_IJV:;;MqY>p5'g:4gLU9.*N$rv##$_,TM@h%:N6-,KN23LQ&'\"\n        \"wlF#3/C@0hfu?03%3@0\"\n        \"AVH@02?dv.S;Ca<9[bv.%BGA#%E+k18a^w#a3F9/\"\n        \"1HM($9p1x7I)7m0cU5l$V2=dM<AGA#4+Uq26/\"\n        \"058heKO.7)dF<?R$r2Xq&T.&g''#Q:t+.0kveMh?IA46dVG)YOSn/V^`h4JsX?-\"\n        \"D(Y?-c,uZ-avvJaT,:w-eRx+MF6]:Nc/\"\n        \"=fMPKpf1n?F5&D^4Z,M=dJ2uOr]5uX=JiHhP&#%.uZ-TQvJa3iI$vRC>b#/\"\n        \"3Llit%2'#9ktgLA)MB#l%*u.Ev)&+K(aw'gZL3$4N)c#6LuJa\"\n        \"2>x#/TEE5/fon_A.;3/\"\n        \"(_Z[`AYb-gLNd=vMJHcc2MM3@0%U]%#-HbV$6dYp%E^&KM0ub;NnrKgMH3pf1]E%hL,\"\n        \"kx7RQr/5^HhP&#-S6t.sL&L(U7@k=,iek=iIi;-tmUH-T.uZ-/)L@0\"\n        \"OrI_#Wr3,)v8:w-='[38^x*2_nZ6s.%_Q^#3?F;$c6ha?up,D-X(Y?-c_K98r6AT&p(wf(,.\"\n        \"HJ`]lhJ).`QH3[B9Q&8ILg(3Bj)Zup,D-[.uZ-p:f2`WVOjLcBX_$[:Ds-Sa+98=Bu`4\"\n        \"e#+.M8d&NM)Tbn-H=2qi]&t5(x`Bc+hbKO.>G)G<*lGm07E)[KC_ub#uBrc#='Fs-C%\"\n        \"LM9rkM1)M54%?p^Lj#T,C>#iZ[>#qf]M9#+&F.H?I,MRrVPM[v_>#O*>>#mYPF#l]c</\"\n        \"lU&E#\"\n        \"j>#29nl&W7U29e4]R<j74R239aC?X1InNt-)V.jMxIHuuIFIN'0xo,&Iwlxujs.>-r3>H&\"\n        \"xhe.UaIqE@>iG29Ka8F,>MKt#jk%t-mN@NOn^QW.w*d,M(Av*(?k_iB@qS,*brB1&J#Cm8\"\n        \"pY&W7#)2Y1O[)n8iC?X1%ZPt-1@'HO>s'q%.Wj,MP0+s'Hlf`*Ys@_#G7gv%6^VG)[[fn/\"\n        \"CbIm8pWa;7I:eX1eufv%/Dlg3+Q;a-@VSk+F<8-49[b)#,b?X.7]rj7iLcp'JPx>[;e'E#\"\n        \"q5M#8l@IWA?e'E#Oj;a-NtETBX^nI-.vBt''CKcVh48w^MKxQ8PXa;7B%eX1j.gv%LWu/\"\n        \"Ee]-(#lUl.:,@%J_[A#s$>Num#m,@s$f86)cP$lh2RxgI*.MdJ_jAw=KCKgc;t4G99>KHT8\"\n        \":):c`b:[:2C@+;2uhVxS:0:&6*kpWL@+R:v;Cic;AC9_AJhW`<Z(tQNTIE`<t40S[$3wu,+\"\n        \"3`v%XVt]%H;QP&nJr2h[MU=ce^ot-6T5p%n/9o8xC[3:$#E4'PF`v#W&F'%:U-_#4uE9%\"\n        \"]Pro&F5-c%O,Guund]=#diW1#XL-]/\"\n        \"TJXA%E265&OP@4Fr;9o87-YnEqg,&'-o7W.1&^6E$Mf9C;q330:wSU%T);o&<d^F%-\"\n        \"g0TJKt;`jO`t;%8_]EnwM5=-$,^g%_9YN'p<aQ8*RJ)=\"\n        \"BCMppWKkl&UJb0&J,Y:vY.>>#JNtK&hRN-OQX;;$_QK%'gwUl8uEA)=p&'S%;^:T/\"\n        \"laMgL@L^)(4T+/:I]X&#>WtdX^#wF#<%[s-36]0:YsA)=q+*J%Zqfm&jWtg:Vx+)OpZ/\"\n        \"<-<oks%\"\n        \"l;^s7udkxuWoAr-xa'@0nJg;-t,Br-.TxktmF>,MhL/\"\n        \"Z$ieESI1h<W-s#H_-WsX?-ihi&.lm^x7LYN/2/\"\n        \"k`j(b>33r3v_x7<tE#-o3hp&*]m<-Z%[m%K]wm0Y%[0:c9&d<5tlL%/XaY-\"\n        \"v=29.Gt#PfNXoV7s%?<-0(2i%Z%Dn8+B&d<U2.>%Z%Dn8'B&d<V=LP%wCcC+7Yru-W>q*8^&\"\n        \"`JsU_sv'U%OG)OE7Z$vjv&mEISZ$)tER(5pUx7$Hm;%Wsdv.&pJ+iO)1a4q:`N:Y9o`=\"\n        \"/ta2(3'51'RQ@,MM_Qa$=vKw@qq18.e)_p(OD7e42pUx7McjJ2S'51'hR>,MtEY[$RJev.N'\"\n        \"$H3e*FQ/jC5M9*.Di^:g$K1gHruPKo/87Ii=]2`<_6rfetEGAJH;JrK2FP+c)A9.j):2\"\n        \"C@+;2>Tm7M7.aKG74tK53ZT:vU.[]-?=9_Agw2AF1nAp.^%k]+lsQ9TEEo(<;pV1)]C$?,`\"\n        \"91b#i:J7#0=jl&Z^:]XfA#q&RR+a##xhwL9nDcDqENlSGH&v#86cm#kj>3#75vW_cH7g)\"\n        \"D[d_#3K[RM*?dJ(<l72K4ullTKqL#$]m7jL.wHj#t^B%M?i)?#>U^s$cF=LWo-2J#>oZs-\"\n        \"e0t,<Xs2W[&fC5/_dc<Lj*N]F;[7?eSXQuu]9#**=k&a<27e;-*tV/)?@'q9loiK(v;L6/\"\n        \"#6YY#C:a'M?<^c$9ER-Za#,N^@f6T%BNv1%5_?,M-kw^$<(=99hPvj'<3*-M88'W&]/\"\n        \"L0)44Puu`^:B+.8bv%1:-x7p02J#rL]v$NCGh24RN<-%]mL-av8V-f'9V-%jmT/\"\n        \"?%M;$*,nS%\"\n        \"@JvX-$Xg9pxqJfLk]jj&R^f;-^0G`%mdhdF)L;U'2PB,MlRsY$FnPj;TNhP'L/\"\n        \"###4t,,M0htg$A549T<sXW'^&A,M1mw^$B=F99GXOO'Si-X-==R@%Ak-68U&u?0TuUZ#\"\n        \"shRQ-Nxsb.\"\n        \"I7_f#tKkr$9't4S)O7$$x:+$%DGq<-7Ip4&;Ohv%W6LEu<(co7r-15^vf19.;Ne@t(Su8&?\"\n        \"jo_M#j2e3WM2p)M)Jj%;1fc2.u`5/dfWo7O&^o8jUF&#ng6s.Y0VSSD7ws-O1u[Nt*.e-\"\n        \"?2HbY*xEh;oW`DFeTcF@C-_h;6C?X1Kms[t/\"\n        \"P3F@8D0XCmEJYG0CCeZS3$Y]3?or'U(Vc;MkB#$1RN39XeNYd,J-<-*:-e%H5V_Zhs#j'\"\n        \"wqWH<?Z*#H/s?E<)ikxuM`BK-5pG69,[*#H\"\n        \"$X*68aAEEGDjq:9-Z*#HY/\"\n        \"PW-`cEEGXj0^#(1a]=JDSig-l68%Ajqj$P]K##J86)cGSjh2N`0i)WlL5/mMW/N&f/\"\n        \"tLN[ZY#0,U8%VKHDNTT@v$<CmlTZ2Z&Yx5`.$-br0#E)>>#VKmA#\"\n        \"R:x#/\"\n        \"iDgM#=R+naVj[d#dc%l#?t0hLv[J<$M;h.U?[i[kbI9w-qVkL)'aE.3s8<Z5X>Zv$It?F-$:\"\n        \"@x,)9tQ1Y_x:9B3aD+@+v/MA#Bd#o$S1$A/XB-&-vd4M'.f,x*r[,/E9n1Uwa[7\"\n        \"&]tI)[fm&#FZU)O;J/\"\n        \"PfZ*r;%uIxEGpff_8g33vQ26W:'ejOP&2_j`<0KgE<+3BE<c9iXSUuko$TRR&m3YqM#\"\n        \"Dcxn02=jl&:3Ta<0QpE<`ctcMk)&crwA;p8t4ZU8t*3vQ$@4r2Tl($#\"\n        \"NJL@-TJL@-__5q:gZVq2J9f;-qU&$&>#7Q0bD_=-*q<w8UrvuQXsgp.X;$;%%`)YAx(S^$\"\n        \"vcKJVqs.<%Tg$Jqr/t5'k:4gL.m$F<fH)'OtUWb$uA0S:3YqM#GP&r/C=.s$/VA2'oxt_$\"\n        \"n+O&#red;-NcM^%8,NS@t(d8.IAaE*#*/\"\n        \"-M(@<0$+#A>QB54sR:E=@0sugo.mY`c<=#c598,YjD`0pg8H)nYQGAWqM[+eS%:pVG)_\"\n        \"wk01<JWY$9G.EcRML@-#cV&.hlVr7U>k^,N9Ta<\"\n        \"HxiG<P>'dMW(wXlqMoM1s4ZU8UvmYQH/\"\n        \"Hv[H&jE-GoRO:LiZYQjQap77O7W]RcwZ88kZYQJ9ks-SIs(=TtkxuKAJ3%^bci_3EkfV)\"\n        \"8ni:8ZVq214Es-Qf2FN?a;g(HN^a<<2QF<i]@#>\"\n        \"N75DkAKbs-hY;r7*oB^#*-L1;^H.Q0w3#,MF08YMig-N#o8:w-BRq]?pvv5/\"\n        \"VTjh#6+3-$LrC$#?@dU7>b9#(JOvY#)%x`&R<EDu&2Ya2Ii`LDlhYgbo6Sr8xuDT)d;s#9-\"\n        \"2%r8),$?\\?\"\n        \"XBgm&9o:p.B.m[6oAu-$1aeh(d][h(%2Puu1mg:#BtGXN`JVXQ^&6v$vSbWQ^=(rDnNv)\"\n        \"4eO>T/I5^+4L^#h)2a6a3CEEr&/k.9&^c*.)G2<v5vWmJ3YoT/3LhEo(aP@L(Pbr$#)b6S[\"\n        \"c)`xXMYt;%[PVQ:QYHI8QHE`Y+9&##/Ww8'5I/\"\n        \"2'5_j`<4=WP&Re:o0q8)x7ur5U8OXk]Y:^TX1M/,##^Od##sM#<-i%w9/\"\n        \"?98X#'$ar77XOAYD%RX1.ggs-H[ieNZLX%vV0_HMF1-a$\"\n        \"kwvo.QBM^#DriG<9-5H*M^5b<.EAn0X,Guu+Q5+#<:d';ic1pJMN<kVC50i)KFjc;5]Ie-\"\n        \"Ye`>#Sf=>P/SpoS<ZRj:H&dxO$tEj.XZPF#*v&$.wgfD9P6F2_YbXlARP=U&W#RK#m[MBk\"\n        \"$tK=%R-HD-VPToLB]f^$?B.j:`DuY?qNCAP)N>W-j9J3ribHe'/\"\n        \"JB,M*C=g$a'i?IA6Wb$&4-3D>mT)9-C^;.Uvkw&Eq%##;83,MLfsj$5N4kV%QM]$B<kGW+i?\"\n        \"8%6q7R:d2qm&mt>X&\"\n        \"sPu.10f6G`)(cYQRu&p]ZVE'HXQkl&>2wS@'0/\"\n        \"F.iw<9.e(*&4*Lm3DeULp$<FVw9<<k;-esHm%T]q,+T,:w-eRx+M<_Er$ToN-F0@.5/\"\n        \"q0dV-902Wf<^0d8=w4dPP:Q:v1VmX$gC?ME\"\n        \"B>)^:f)$Z$XY7s7.D8Z-*X5kXdbBwp+Whc2Y$k,*t[9dMua'&vk-Zd&P_+6#I*Dj:BS=q&'\"\n        \"C(;Z?H4s$KtPk+(1H&4^C(7#HXI%#Sk(F*l9o>#@[n<$v>q6VwtTb*qKt#Gb*Wm/:v8+4\"\n        \"+87<.F*8##H<tW;xt+.HV^SG6U26G`b<K:AN_;HG6#]k00@qS-9;cG-oAcG-sD(d-bldw'\"\n        \"UO3eHYR/2'?YkL:'nPL2eNAX-i1<_=bPrRe_D4KuoBWu.N'%DE&a^w06sX$#>_;V7J#]t)\"\n        \"/Z/\"\n        \"U'>T`w(uD5##$Qq>7?O*M7W1pL(wu.&4Df*F3XL3]-ntv9.jPe)*`ikM(]=ogOj5S`E+n@N#\"\n        \".a0?Jk55>uiW-NS-.1B#X<StEf9%HMM39VMI)3$#5hJ(M4G)h)j5$?.Tj?H)5-A0:\"\n        \"C_Y87'QE8[2?b]k8+.W$BhaP&87.<$3Sl##LJTfCJG'N(SLiHQ8+M8.4iUP/\"\n        \"`pxw#X*nO(I182)peww#6M/+PSUgRk+SHu;B%)`TL.YP/\"\n        \".C..$]UW]=$6I>,*<_s-h@^2#>g;A+'A4/C\"\n        \"u.),2Hrp2)x8g0:^Msx+o(?>-H,<a*x/\"\n        \"ps-9L*f*Sbes-3Had*O(Qa*VnWW-s&tR8AU6c$InO?.(0+BSg;tGNB6SE5WF=733<3;&60;\"\n        \"RJI-4A&$3*:/PFq7N_QMr0Ex^9&rl:s1W@Bk1\"\n        \"C@Sn9KE0?&m'$i(ut$3B]0;kDd?xo%>7.<$^8xN%R5cf*0&F^4jQ/\"\n        \"@#Eg887EK`@bm@E]419e19$lE]4p5`m/\"\n        \"#6YY#7D(7#L>cX7XXaP&.sL?[WSZ_#[PVk#Qtap#cb:Z#0c:Z#4sx(%\"\n        \"rZ)22oiNW5>9=r7B`JmCXbHuudN2,RSNp^g.Qk]ug$:@/$ke[u?./\"\n        \"vLa1,xu(NvtL#+t$#+$=?#)YaT%-?OZ#:]EQ7AhQ/\"\n        \"lcu<_$.R1N(vg'u$tIs4'1f]l'C;dill3@x%WAN1D`8<Du\"\n        \"('QPh.PB<81A6$$.U_n#kV#3#-Rw;-#0m<-ofe6/\"\n        \"LBSt#91$&M=o:E86USSSV6e<%G8eK%=@es$oXt?Kln2H34*x9.#^Xa=P[ViuF;k?MkYn[b/\"\n        \")4^#+u,D-d+-D-Yu,D-hS.U.45YY#\"\n        \",:S>-xN+_.#<Jt#-WV625FmU7@..s$7iLZ#xS#p%lHJj(-J_6E`.Z:.I)TF4L[M1)%NhM'`\"\n        \"N&^6/IaZucia?VIfkO0/QaHMm8vf($M8GMm#kX7TM###Z`m$ve;Vc#fZI%#jd0'#_*Vb*\"\n        \"R4TD*]PFX&w,4L#Vo:$#=#,=7Ywv/\"\n        \"2%9VRuT.3v5Wh`8.iD.&4ND#G4-a0i)nfse)JCn8%h[Rs$>5m,*_/xF4eg;9/\"\n        \")DK>C0E.oL,u1T.+(%UCbE*j0%g#d;iUh<?;%N58V]B$HgLtMB\"\n        \":e#VBtS_1K.+1'#.7WP&Rr[fLPK/\"\n        \"s$p_(##xjJ]=*:Kip&<;R*dK@W&2S(Z#&O#F#v7v3#eqO=ckm>wTY$vM(C3t-$J6K[#^\"\n        \"A16R/Mk]BeE6G`$X(s6%pH1pJ<5v6*p?2C*OAO(6mw8'\"\n        \"/Zat&T9BQ&Q1oJjNw,@5Pn8B3vL=F3^a`I3_O*R<*F1N(:,u>-Rof]K:cRxQ>$5p'+d9G`w*\"\n        \"v?#Do]E#`MMEME0n+#nrGXN?vVv#aepC-.EBoLm+'oLEcQLMiB#a=k);L#^`tSNP'qK-\"\n        \"CxBo-dJ8_8ot[?ThL.<$8J2U%1L[Z#D3N^%)a0&cM8T_8I7^;.KU^_-=d$V;Jae8S4[fi'V/\"\n        \"R`tB#@++<;t5(>orj*TQS%#rvwY#:kF(=wH)s@hvk;-,IbU%5f*^G+M_s-oH8;?d3J&8\"\n        \"d^$fOnE7q9uM&s6tZh1D,k:kDEniF4u$VkLm'g+Mnc5oL(oE$#Oqn%#P.bk'>Civ#=G7A#Z(\"\n        \"?>#-o_kFoZ)22BQq>7PVR6/)CuM(XbTvHU=RML?`=B#L[sRRfJ:).<_,XNw@qS-WX)Z9\"\n        \"djUPKO3sp&TYW]+f]qKGt2.)*Mx=G-a?LlNtL#o&lPq;.j<?DJhAeM=g#3;8XcFuL=\"\n        \"hIUJOnL0EE_di2GF](#SA?$vR;^i#eAO&#Whl**G*$r%n?Kb,dW1k'6%kp%.v&7pX#\"\n        \"rlJrQbA#\"\n        \"N,Hs-`%d59<^Ca4l7[8/\"\n        \"'XTT%NlTxROEt_APQRQ+P?-F%sEfYu,JDW%_=1Z5$oQH-A-]rdfv@HEi$gJ=hLRa?'FFp1#\"\n        \"X&%@%/5##mKo9$T16c%4uHP/Z?Fa,_4(.*.Bx9*$f,?*5S[)*\"\n        \"YN#V%Kliq%CHbv#fc*87)`;L#+Ngs-^d+f**6/\"\n        \"-MHOKs7UAJ#.h@XI)BxncI]rqb%IU_6:JQT&ODp$K#u^Y'?L>;I$i<(nu2v_3>fk,-EFd.\"\n        \"1Q0+(5EvhZ.'/N2nWcX3G`I&[`*8#=T'\"\n        \"-3=Z#)h[0:?4wS%>YK-QEhbd(WE&7ptf_;.PIr8.cZv)4K)'J3IKV=IPv;Q/\"\n        \"u1VJH'Bqd7whDK#&Y<341>..s?M/\"\n        \"'+Jm5IDnH00en4&M1N)>>#?dGp.=MY##jNmHH)HH$vEA6_#$$x%#\"\n        \"'<:T2l'Ch(_Xkx>WDrv>n`*87O^'v$f,t]/(/\"\n        \"E]$CxO7'WS(X6`_tEGqQQ>B[QgX<;&x[>WuHsAS,p7;J)'`5oP:I$Hq:t6l8_EH<Lt<-\"\n        \"vHUE<Vv0[66dHL:5[w]8J%aY7I9&Y6k5>##\"\n        \"<roe%dFM;$K2w%+,Mju5Ftkp%fZ;b*F]=877x*i*8ma]FMoG:[,7_B#wR18.22c/\"\n        \"(;2vZ%L6wGG?'Q;pNT@L2wINKXm+f)*O9NR8ePfMbDqxctvAgc*kA/\"\n        \"LOiN?W.lbx7M+<jf#g_(q'\"\n        \"RQSt-fNDn:rad7&#<6K+u_cC+V90W-I2Z9VcR/\"\n        \"2'A?@<$*KY##.V+Q:;IM=.8jS`E,#C8IB>^Y#Q)1SI83x?0VLG3`XNPHVBH>>>;+\"\n        \"4HVjGeGMdR5L%G5^F4:.4]-J#G:._T'r.2f'eZ\"\n        \"xfUm&9Rf@#B-$humRPiT%9Xhu_xT%drOk@Osti]+?+2$/I+65/\"\n        \"'@+j'?F.s$wt(##+f68%YWTD3[Sdh)nA$?.XT#r%NgL0(5]h::oU7UfaJT;$9koM'ARn8%;\"\n        \"l($#7LG-&+_YRup-Q]6\"\n        \"v;&iLX_vS/Z/\"\n        \"6c%>Ir8.RGgI*)a0i)s<_=%GB(HIN)'ZI1SxfQIj0c7]daSWB@58.G4AUdjJmfrAARfLjG*\"\n        \"i*aH_+rc3ai0IZ&E5*`]<?31)/:g^x='77jD'Ouc?#dM6PJk,21+SIJ@>\"\n        \"#/VcDvt$A,^3]VH1r6s$U.SVH)55##Jd[/\"\n        \"3Sh*u@w:a5MfWAT(MlnPhu-Ds-*,.T.::b=.W0D2%erIU%MAXI)V9xo%5@23*Y3jGD7rl0,/\"\n        \"fIg1BNDH,YBOgCtm12:$KcKC0xl%5-D[0+\"\n        \"<m'G+C`pF47Re?,8oo)=^7Ex7K1<`#MAG##.FmU7:f1?#Gqs5&4r$s$bw$q$+rT#5Hax9.\"\n        \"Tq[/ND,_*nD+8ucY[C=u$$:ucab3B-KIcs/EjxW7ZqE9%RO#A']CRs$K-=gNIp7N%x[e[-\"\n        \"PHF:.HAY)%'SHQOp2#A9HN7%_niS;$N0$tuic%^./\"\n        \"g4u#3AtV00.HU7-'),#..668;L8'dd]]+4@U]?TMlXI)f;CU%l=j+Vb#0qCCp;O;`[;I.\"\n        \"KbwuL+.),a:[K',0:BQ&6Isl&U]MFn\"\n        \"k$'s($RU[UxVRBS'djZpGMnr?dC>g;Y6YfLj2wT-EVcL3G&>X7XU3T%0XS>#btap#@L>87[\"\n        \"L(ak-MlY#qSEU-&T&7.EZ:mLI-E.N^usI36)w1c8^W7PlfS;$RTH=uWU$6%.Bvf(3*e4]\"\n        \"--gN-J.hw/GP(k^B_W5&Q_R+%:?Ym/\"\n        \"D=9E#CMdn#kCd-.Qps7Mwq9&vt=MMM;k,6#uDnA##1m<-mu,D-56ZD-u7t71O#$6&-B%)$g:\"\n        \"EDuGq(M3]H7g)o:inY[(RP#ITcfuA*>>#^Sc;-\"\n        \"^DtJ-o^W#.v2*jL:kEs#QWJv$t>t5(kwC)+E%F9%vHXM*?tSM'gJ8)cY@OYT]CVr.\"\n        \"LuRVO3rkT9388+%e,aK<n]q-$+So3$>;RI+'sj&$aaeC&R-$#5#0g),:A&&4$L/\"\n        \"(&sKq&#;_e[#\"\n        \"*%-/`JY[s7TfNMBqQEU&/K@W&9GTY&24]h$W6P/\"\n        \"%=9,W-B'FL#vkY)45d66PT5Q;$$:duPrOk@OJ(-5/\"\n        \"ena4o'X=L)3^[s&DaVG#C'kt$rMeg<w#_l:'*0Z-DN'GH<#0i)Ag]87)<oO(\"\n        \")<>t.DLTLOD5s,)6d`RWD&6kWrDF@$esI=urt)W-Kh[_SfL%_S>Q0pRR?Z-.YsnuLeu^g-%\"\n        \"Bw'8led,$3(Um:bhtA>@=OHVO]w3)vQtCMc3-AN5XZY#:i###v%-R<1Wc'&kcp2)10&V'\"\n        \"&x$m/@V>>#;.Gj'=Wc)%mZ)22S^:L*)(ue)d#_#$,mZI%X1aN#67VeG'BbnDqRa9/\"\n        \"dSA,Q<@_W?r3'pR@O`<-5U<7%FS5kt%3w%+H(Qj)L-K6&1B]7(#](p.D]>>#.Y(4Fu<Yk)B.\"\n        \"(`.\"\n        \"D$nO(H<au0nR(4ErZfI#eambrG=:_#mv&E?;tUd#U4B%OhA6##<f'S-Ze$a.Bw`V7LL$A/\"\n        \"7vAu&j'cgLLq&$PQ8on&A&Is7dLNqDY++8MrB4kN[n'B#4dD+r/e'g(r6c(N-eC<?0nq;&\"\n        \"V(;K&lf`>5a9&A&UYI>M@e=rL[J-_#];q4JD@[[#/\"\n        \"l7p(eEw`*rpS[#jKw=JFFe[#1xR5)fHw`*tv][#no+k2Sf/\"\n        \")$?.Z>#Su#e4OSjc#UMn`*-gCQ/Sh*u@P1%K37Iu'&):w;-FQpa%\"\n        \"Tk&>.@;a0E]2aI3[./T%&>-##9;V$/\"\n        \":h<(5pGAG'BVld3*&BmUE=S?KNd3c61Ox4CZ+v`4dM>g,'w^Y#WxE+r1X@3kcdJ%#`doW7q.\"\n        \"(52_bCRf3o`=$h;ID*Yq%p(X(PA%&=7g:u(-g)\"\n        \"V,1=-bVO0&1Z-T%kBOr;n%GB%TjG#7?4DmAToGF#vN3_5ij,K1J[N;$KM:+6[q_`=V3]\"\n        \"4EkcK+<QUKZ-'AlY#f.4_$nJ9Q()HgD<*DlY#9KZ_'hLSg15Z)22']Wa2#Yj=.$wKl:9DuD4\"\n        \":mGh:4>2N9^):?A=TB12JsLkrm-2U/\"\n        \"kNiqWuaUL3-(_R11ttf(E63S&bIZY#N'%DECD1G`<nJM';2Xp';^c6&9x1Z#0fET%J']fL0[\"\n        \"FDu%*Yh(aVwi$c<N1)k_Y)48URX-vf&GllkUfO\"\n        \"Nu8H#d`CN#[xcd$fJh'&x<w0#xjJ]=^.C'#GV1k^7=3T%Kffd#3x8K2g;DD3Me;v#mq9-M3#\"\n        \"q^^B5cQui##Yu&>uu#KXW+VFBrY,'`<j$p&=T'`VBU'_+lV-HAh@#nx*87#ZT=cL@BIX\"\n        \"Ipla?.pxvC9'2-@kpkC&Wc.SnvH8:(?6Uxkt7)(&098^4/\"\n        \"4?;#uD-(#<&vT%s5dJ(GOnA#jIAR&rFW/\"\n        \"1*Weh2Fp?d)$`^F*V#^q$3(,oJ0;7gWerFc48L-d*`WGT%4]:+rD**'OhlJ)G\"\n        \"6WsEG^Q_#.$7R[n,_C/\"\n        \"3kgT>.t_7X'K<t5&B.A#.JYRT8lEhiD.CkPCitoY.mS*C$ZWl`0xJ-x6;+#31pc###c:a%\"\n        \"vZkJa##[I%#w>$(#U=01(R>4mps$$?\\?;qAJ1F2=x>3>];35Z*@5\"\n        \",%4H5F9eh2PNv)4wH7lLhrSN-4Le20;5TF4=U^:/\"\n        \"iUP?gYGVD3k<5T%4iS@#01l<LM7>3D6UQ)4be@H$s2d<8uxK>IKm.V/oGhTI/\"\n        \"jiLDZ--)6l*8L*4Uef*S9`F6&*Af3d:uD#`V?:.\"\n        \"bT&##?t3xuXdMs#_pB'#McDC,NXZ*Enkiw#Gq%)?9/Rs?,vm6J`VWe),cNZHu;WY/r/\"\n        \"V#?Nt8#0OwWB#ZZsb$tA:B3P1%K3E/Rb%gFQlL]F4jL&V8f3SS,G4Pg)<.Ak3Q/\"\n        \"Tk1s&.AXI)\"\n        \")R]p7X5Du$Pa&bEGYKhEJSZH;/\"\n        \"*'cEP.m,G[J)43FwEC50]OR'b3397DUKU%R_1e3u=YD>XhPm:mkhs-:j]],G1N3M(Y._#\"\n        \"ZRR[#)%/5#]'SW-c#3ebpvN$#V?f[#9(jk#s7+,2<PSPf\"\n        \"g]p;$'F@,;(kKj(44m5;TA3ebDi*F3LE`.&DtE#Th18eb][6#u51mju`;qEc#E'wu^.rZ#\"\n        \"c2h'#pcLR/j1mr'fIPc*hsQ;%0Trc#VNM4BUs$AIf5E&(r#'<-]c/s&](/T%5N6Dags2$5\"\n        \".2iu7d:k8CO4:]$`RsWHw.hZ&ZBZ9/\"\n        \"YjxrhtQlY-j&$k0*<OY&dG:v-NiAHFVGw3::Hbw&U6T:vQfE+r:+M;$9a]._Sr?T.<M9:#>\"\n        \"Wjb$UaLkDYOQ##2DHr7OWLkD1b:quA/$tu&5)c<\"\n        \"%BWP(='@s$9(`Z#V.i>73eDM26+3:.`Ug/\"\n        \")+e+c<ImkAu$<McuS0ofLH^nX$&-mQWL:)##2:U#$csdw',R7_]cc2SI7#P;$Yosx+.dr?>\"\n        \"Lb0IVB.@s$^3ZR_+s=8piv=u-oE;4`G)0i)\"\n        \":1F(=gZfD=YoF(=p4(^u^L.c7dA2kbREPj0:FmU7UT,N'0s-W$G@Rs-`6C1;-p=^A-r8K%\"\n        \"ifS+4tlalAM=oV%8%8c.ZC>H3PLs(+3'sDTHs*m1^hx=#`4W%v:*8nLCUv&#gJcq.];Dg(\"\n        \"Jo0#&^n0#?:7iZ#Fiw[>HkkA#:$,0.l1q,N3&#`QGKEl%=Egw#Jt=i:MwN72xVe:&5F+D/\"\n        \"^-n>6_5'X%0XN;$-@Xv.IK?31=P3J%sg:E#HH4w3l.pGMMu[,$[GYl4_QSW7(vR].ebZk,\"\n        \"N<0Q&<prc#ta%*+B7.W-_nG<qZGp0#@xkh2tuOf-jQ(KE,aLD3NNH&PpU<nLA'@\"\n        \"6SJ40YG5lB']M5YY#-ND+r*eAwT>i-5/?J9Q(o,4<-H0,n&/s<A%_?]$T/q'*)+re20Ar29/\"\n        \";wZ)4\"\n        \"4v-iN[kW;(DH=$:d.oN#(BwT0DH823oq-X&`d$l1^0iE4A?N[?]L:)[=#O@?[A^]?r2-=.X*\"\n        \"l31Qi3N19P;#6n%eD+$]eL)e@9>,=otx>UtD?#<oUV$PuXkOlvoM(QCS;%lb%5))&[i$\"\n        \"BHEjLCro_SpOx(<uiSw770;:#bd0'#m4s#$RqI:7hYCx#asH#Qj.GRfJrh;-u$?=&.6%O;\"\n        \"HJ,d$.BLL:F01,jv)`0<bF-##LBSt#?'Pk%ubNe+A4Fk'd-SW$%qI##+/Qt$NKW6p.%cL2\"\n        \"ftC.3afL4&91;W-^VCO&ZOQB?i:3D#>VC*Qt&:b<a1`_?m0HO(h82T@]-e(>d:<oS&\"\n        \"UVVVIr82@Oe$L2YZM&Ade141)>QsWS$[j?eY6(#uV_n#g>T2##)>>#'BSt#=.m<-k/\"\n        \"m<-H:Mt-\"\n        \"o/$&M.1dWMnsfwu1&/VM?8A.a%5YY#5/x_sUx1G`f$.5/j;?Z.')t;&)N5##IL`Z#Bt$<&t/\"\n        \"DK&P;ed)..^*#QoiU%NoR,,j^qGhnt+@5=:.JGZ>sv>r2'Z-n&Z[$Vj`s?*4f%ufU]/1\"\n        \"p>9]37V98@nE7q9V%u?%NRAQ/gUKrVbV_*msXe5/\"\n        \"#nl+#*v&kLb(9%#hoUY7DR>b#Fw59@Q5g<7<@aP&9@no%0]1v#H<$2W'[j>7,&+22sS(9(\"\n        \"Ni*hYN0;hLTm(p$Af):T[*jV$(f6,R\"\n        \".^?/\"\n        \"?dI_B#%`,r#sJ)W#0VZh-Q7I[TT+35&g'0Po;'$,+>At5(r49f)3%96&Dees$rvwY#%qO=c$\"\n        \"'f/2rFte<jb?dbwtte)WXu^.^`;<%eOfn#Y*X59WK1)JC?0@%JGd]#Fm'*R6OfBJ\"\n        \"5:N6:RLoUdn%cREB%io.'[x0:xB.&,1ljp%nuXnE7v*87H0+N(c-NkFr`cs-3X#M;?;T;.\"\n        \"8E9<-r)Y820@LjKP@=T;+QIm:<m5a53^@_P)$Hh+Lbg53qZ%+4THWl?Ub0^#LOF,10FmU7\"\n        \"0B3X&2IOe%hjm-&u-n3F6ud;-sq5L/\"\n        \"&6+#,vv3f?;k=p8n.^m)xocx#sMc##MgBT%vm[>#3<F;fwFm;-CLC51?r*gNW<8G`N7&Xut)\"\n        \"s*%^^X-H:d8>,Uhi,+D)H9%5sF?#I^5R&91M?#\"\n        \"4SUV$<uVcYhJ*22tV(9(fQ-D(&BHE&]#G:.Zv.N%R-PT%md[qMm2AUY#)oIH93.:8<R9l#*\"\n        \"kPn:[-.YB3&MYKdMZO0L5>##LBSt#bCR8#]^''#uv4X7[,uh:x5^A,lx,o16[pf1kDHj:\"\n        \"K`dj:lx*872D?@7T;u3hx02%?Q[#+#xBi8.AIj$'3`B+*X>u`4.K$N0')TF4QEp;.m+:KMt(\"\n        \"f)*dQ*iC>1Wm/tQ[<^wI,?,A_hK2p4TF*4$7]%e)7e%5@q-)Gwvu6p`>f*^mXx#Bae'7\"\n        \"6aMoemk^+*dhKRDlVJ%8?,gb*ew1KCpj[#%>.8G`DN(,)iUr/\"\n        \":2MuY#-Sl##pt3>7>_e[-(1,.)54>f%KjTv-4SY>##TC+VeKF:@Q+N(,`shn#1r=Y#I>1#\"\n        \"ID*qhpPpi[%o&E+r?9uf(\"\n        \"Il@S@h-?=?2ASV6KZ$W*UvR:7VVZ_km4gF*0xh?#q%8h(4^r?>:hsl&.?(<->aP#>6p->H%\"\n        \"rCD+O`-2C,R4#$Mi1$#6OO;p%1%K3T:Im/LW.@5+?2AAfaTg-eJI90g(HH.D@n5/AoHs-\"\n        \"mWJs7YBd&4f+US.`hKe61%:gUkZv'4qYlX78LupeMWh=%;&S'7QXcg,Env]H$(m%5,4%,4`\"\n        \"td.#EkuRAB&L^#+1A5#1@L#v7%*)#Q6W'M.98>MxRTY,Y_JlotF[w']2H6#%/5##0pO:$\"\n        \"nAG##[?O&#`6ib*#_1?#JN(K(=[#&+H@.-)4a^29Nf?-+c]]+4=O3QL0X1N(vg'u$To.r%\"\n        \"LbSj0J`CsD0;-PElhx2E=Db[/Q(XW%1VsXoA+a5/q_6^S`[PB=IN;qM3=%##D*?qM-d8>,\"\n        \"fsB^-9al>>;9_b&q?:h&e_aP&2uUv#6j?R=o.N;7?5_8.M&EH(kYpaNdX5p%=oN&=;O^;\"\n        \"ee0sxbPTJP=L4UB#kS+##-fc(j.kU%-T1sQ&<ZlY>;F/2'n+S5'B,K>#/`:5/cc.-)(.r+;\"\n        \"lT'-OeuVRup?iU8/duD4)p#f*4B:q.ZE;,Pa9V1:hr2]H2tiduF(jb3.Fh41so+##l,/\"\n        \"9vf8TkLQ0)'#(*+d*kF@t-kF^&#:2oNO^:C)N[2Tk$'EsI3vj>nC%lXI).%6/=+`N/%.m@d)\"\n        \"Uu;d%6x:>&x-O3CH;*t.ckG-3A$u`#G:,R:qk##RdOLtU#^)eEb0Xk<mv1XU6Y8&v_fn[#][\"\n        \"2E%_f;6&,UEs$dPM]$U6xIAW(d<.__j=.:,e6PMLn4>hMFAuaA.uLf*co7$qgYZbVTE5\"\n        \"[Txs$;c,I6+@H,M<v1N(^(n/\"\n        \"ljGNNj@,'##R3n0#YUYb#gB%%#QQSW7Q<CG)2<;$#<2JH##1FI%1>CK*j(WRui(Yh(\"\n        \"TH03Mt>#p%l[)<-,Wh-%owS9CX.=/fGYi.G:Ed3=dPUlST2BwE\"\n        \"khm=&6,W,LMIr%,L.-5/[/t^%&/\"\n        \"v&#08.Z7+k93*taWJ4FL=b3L;Rl305toA`@'>?uo'npW%0O1,OA5)\"\n        \"v5N4BXS3XA36o23vwt6pveAd+4$+d*jIAT%KW7A89hhHFg7l;-@;&p&P?==$\"\n        \"ft7T.4N9T.-wl3'$Ihj(lZFt$2M4s6n:6Kbt9(2BdM?R&q#C>.[do;.W8D0(WYgD5s,b7%:\"\n        \"lQGcVV&<&@-^6&:5hs$ko*87LeRm(FTNI3NwT_4?r0?5b%uPFl/[Y8R_H(+_.Q;$6BDY<\"\n        \"_/\"\n        \"kb*qWL3`wgWM0:qX3AKt%##pqffLh<t=c:&0<%+oko7bNY20pG5T%wjlF#C%uI#KsX?-FxX?\"\n        \"-j(Y?-XgmT/Utn<$0HbV$)e&S%MP0nJ`;G&#(fIW-v`q-?T(ESI@BYW-nkMlaSo@S@\"\n        \"]%cI>lAk_$JpF5/ufFeQV)fCs7F2j1^IugLW`A;6GsX?-3/uZ-IuC@0Q/\"\n        \"#3)1R@*(Xg:>-t2QD-C6QD-aJ2&.+Eut71i:5glsx2)et?>#]54/\"\n        \":rvx:mB*KM'GZ*T%Y;E2G/;5xoGJ&c=\"\n        \"`[Ma+:6-WI1n>`#^>K32afhZ-B(ZjL=hCa#:#R4$k/\"\n        \"`$#95`Q&AK@(+SugDNP(087Gr`BGZFGY%)86e-/\"\n        \"e3Y-^=+J4^E9x7D<&a+ZH_)=DuA,jYl^._vOuj43*tV$O.D5/;2_8.1uDSI\"\n        \"t'+<-/<po%.xhp&bS$,VHsX?-UVgoL[_k3N8m`,)MN^a<)Im68^s<W8Z&h(MZWv`M?:/\"\n        \"v7>q0#v;^aE*%[H9@t06X12Ng<Nadn*Nbg7d)6Cxi*.&Zp.SBSt#6^'@0O88^#&?(4&JR4^G\"\n        \"EdO&#FAwW%jC3G`o8:D3R2#B6[WE1#*-pb+/*,##5fYY#Fqn%#C>Dt.Y]O#/\"\n        \"gY:B#@fgr0[G+<@7wo7/vI%_#T'TM'P$uk'Ndgq%QNIZ5SB:B3(R%@5ou#:A,XA0h)/\"\n        \"h;cF8g+4%+YA>\"\n        \"r>@b41Ep;.+aS+4@?**40H?e%4N?T%2Nte)1*/\"\n        \"i)bKb]uKlMi2ZRV,+So-b'w_3S8qNk)3fQE?6A=pkK5LYND3r:Uo[D)r.C%j50w19-@nWB$-\"\n        \"4f`'+@SHW.X,76:)6R:&]mv+#%nl+#\"\n        \"UKEh#L[u##b?O&#o,GX7'H))-`1j6&V@eS%naSP)D_$HD:%d=7e?f#Adc^a2wF+F3$$nO(Y@\"\n        \".@#S3w]%2[WS(t?i8.-?eC;[xT=84U=VLDOSx*FoiT.ZCXU_6Kw>ShO2G`$B6Vd0j6Vd\"\n        \"$&mRA3<R#QS3Ya2u(e/X$rJfLR_-##xx4>#ca1rapUK97;Ik3.5+W'M/\"\n        \"'MHM*vb1#Fx(t-uOc)M^=KVM3V_2#:fG<-lmG<-jZGs-hEQ&Mx<vWMEL<0v,hG<-nHg;-?4:\"\n        \"w-DT:xL'[MXM\"\n        \"64B1v@hG<-3[Gs-qxE$M:G1[MNk<^M-&Y5ve6)=-QnG<-RnG<-Pv,D-iiDE-lnG<-xO,W-e+\"\n        \"EX(w1=GM/XlGM/UY,MZFXP&M1=#-H(X>-N_P8/KhL50LqhP0_bpA#-9kf-MV9F.bBHR*\"\n        \"PM_e$;LV-QY+)2Mm8158*XG59%OcP9jw'm9K#E2:QY=,<@,XG<Gll]>J1iY?E(.v?(,H;@\"\n        \"MLeV@NU*s@EL&pA@CA5B4C9/DlDVJD%xC;IW7VPK]NxlKr<82LP@3/Mw+TSSnxOPTo+llT\"\n        \"jx02Uj)OMUk2kiU/1./V/\"\n        \"U&)XmiCDXsI<>Z7HUYZL[;DbL<k>d;.;;$4KUX(J=ee$cg4LG?$bq;Y^UX(mX?F.fPNR*c#\"\n        \"VX(q)__&ecZk=v8__&qTSe$Ej(JUaS^V$FS0cinX),Wg9EVn\"\n        \"2P9Z$A-fe$pJVX(jgT_&uxn+MWw@8%M$.m0k*C2:)5dV@KiUPK)_Yq;HuV^MQ$FBMuJuCj.\"\n        \"IQv$hfC_&XU/2'W*GDOqfSv$stH'S1Lr:Qa&<;R&3Wv$KoZ_&h>ce$T3BwT?vCfUsO-/V\"\n        \"P]Xv$MPce$=D6@0=C5]XxLwCWd`?AYJJXv$gFde$f@V+`D%CJ`NacofZ6,5go)dlgR/\"\n        \"%2h^=P`k].DSoke%5p(`bxubi@3krsxr$aSU_&@U6L,&MX,MZFXP&M1=#-H(X>-N_P8/\"\n        \"KhL50\"\n        \"LqhP0_bpA#-9kf-MV9F.bBHR*PM_e$;LV-QY+)2Mm8158*XG59%OcP9jw'm9K#E2:QY=,<@,\"\n        \"XG<Gll]>J1iY?E(.v?(,H;@MLeV@NU*s@EL&pA@CA5B4C9/DlDVJD%xC;IW7VPK]NxlK\"\n        \"r<82LP@3/Mw+TSSnxOPTo+llTjx02Uj)OMUk2kiU/1./V/\"\n        \"U&)XmiCDXsI<>Z7HUYZL[;DbL<k>d=@rr$4KUX(J=ee$cg4LG?$bq;Y^UX(mX?F.fPNR*c#\"\n        \"VX(q)__&ecZk=v8__&qTSe$\"\n        \"Gp(JUcf>8%FS0cinX),Wg9EVn4cp;%A-fe$pJVX(jgT_&uxn+MWw@8%M$.m0k*C2:)5dV@\"\n        \"KiUPK)_Yq;HuV^MQ$FBMuJuCj0[2W%iZZ'8&2f+MXejfMIW(5gXA%2h.nIp&KE5qVW(rr$\"\n        \"0tnP'W*GDOp2Op&aRT?gJ))JUlDll&mwT?gJ`wCWrk`cW<-BDXu0]`Xv9x%YxKX]Y(-QV[3(\"\n        \"Mp&_.de$6ITX(1l[_&2o[_&3r[_&.>de$<[TX(4XW+`^VO]lbbhxldBASo(OAPpn#Ylp\"\n        \"ipt1q4<liqLuu?0w]MX(uxn+Mpw@8%*=vV%wTqA#@_`=-.H)a-n%V_&:/\"\n        \"V_&5Q^e$CoNX(8Z^e$:a^e$r'#44L9Ce?NA,LM_O6LMSU?LMWndLM3umLM`$wLMa**MM[\"\n        \"03MMlHWMMaNaMM\"\n        \"eg/NMX<pNMsA#OMoM5OMpS>OM#5;PMB`%QM/\"\n        \"(SQM<.]QM^F^-#cuU?#8mG<-3Hg;-6Hg;-I``=-IZ]F-E;)=-:Hg;-0S-T-J>aM-@PKC-d``\"\n        \"=-RHg;-SHg;-0kq@-VHg;-WHg;-m``=-\"\n        \"[Hg;-[CdD-dmG<-K^3B-lHg;-$<)=-oHg;-8Sx>-wmG<-rHg;-uHg;-vHg;-NF:@-,nG<-'\"\n        \"Ig;-F<)=-7(hK-8(hK-[*)t-a-W'M3/6`M[2?`M6:H`M_DZ`MmPm`MhVv`Mc])aMei;aM\"\n        \"foDaMguMaM0&WaMo+aaMj1jaM#D/\"\n        \"bMoOAbM']SbMrb]bMtnobMutxbMv$,cM=oS+M^-E$#nU)..fFbGM0-VHMU]3uL[AE$#hMo5#\"\n        \"Po4wLO/E$#qox/.XOLXMraVXM<niXMusrXMv#&YM\"\n        \"x/8YM(ToYMxZxYM*a+ZM7g4ZM2m=ZM3sFZM4#PZM0/\"\n        \"cZM5t$7v;1&F-anG<-[Ig;-%+>G-mnG<-hIg;-2bU).lYajL95aEMW@H>#A_lA#)Gg;-(C&\"\n        \"j-w3GR*-1Be?.+_HM:E%IM;K.IM\"\n        \"7W@IMD^IIM:j[IMA>FJM(/\"\n        \"L0M#7,,2_vaJ2SZ&g2W)>)43oZD4`Mu`4aV:&5[MUA5l@nY6a%3v6eIJ88XPaM:sN$j:\"\n        \"oNZJ;pWvf;#Tk]>B1*s@/l[PB<CxlB7Lt2Cc/12'o)Y_&3Lae$\"\n        \"6Uae$IPJR*IK8REEvQX(:bae$0NH'oJ2=eZ@&_q;dIKR*RTbe$SWbe$0f&44Vabe$Wdbe$\"\n        \"meKR*[pbe$[kOe?dVZ_&KTn'8lJce$$iSX(oSce$8g=F.w:[_&r]ce$ufce$vice$NF/:2\"\n        \",][_&')de$F$UX(78VwT8;VwTY9^_&Uq2ci1)T]l[Ohxl6=2>m_kdummKEVnhBarnc9&\"\n        \"8oeK]oofTx4pg^=Pp0mYlpo,u1qj#:Mq#d6JroPm+s'2Ncsrli(tt(J`tu1f%uv:+Au?IG]u\"\n        \"6<+Q'hfC_&an/2'^N(EOOQKM';.$_]9er:Qa&<;R.&1Q'KoZ_&h>ce$/;]EeG8DfUu[?/\"\n        \"V0Is92nPce$=D6@09C5]X%1EGWflQAYv?J_&1Gde$f@V+`8VBJ`NacofZ6,5go)dlgR/%2h\"\n        \"^=P`k].DSoke%5p(`bxu#YkA#/@DX-aSU_&/\"\n        \"dU_&.X:_A*i9HMM>FJMHDOJMNi0KMK%LKMKx90M[iji0XOpA#mQx>-b_`=-PGg;-<9kf-\"\n        \"sNV-Qb_.NM*$KNM%*TNMj/^NMK6gNMQZGOM\"\n        \"@aPOMG5;PMJGVPMEM`PM(SiPMMYrPMN`%QMEr@QM@xIQM4F+RMlL4RM%RhSMW&RTM].[\"\n        \"TMr2eTMPD*UMwh5WMn$QWMo*ZWMj0dWMj7mWMk=vWM/C)XM/h`XMmniXMs<JYM7BSYMLlh[M\"\n        \"sLA8#F+i?#S<)=-JIg;-c*>G-?QKC-Y<)=-mSx>-fa`=-c<)=-qnG<-ev,D-vnG<-#7@m/\"\n        \")0)0veC+.#smG<-nHg;-?;na.98J5#AY`=-&6]Y-Yp]e$)-^e$jJ0F.IvA0MZedi9q<C2:\"\n        \".>H;@/GdV@Q%VPK8:22UwOLMUP<4^cig2GD3]mQa-.Higef%2hlO9GjdtHYm/\"\n        \",iiqVt-F.`$>qVT9n@tFmIaF+.il/\"\n        \"b39qVD%Re$'::R*Up(GV,:*,WwH-aFA.Mq;oQ&J_@iBJ`dBASo\"\n        \"3xk`FZ>s92&PkGM/UY,MPP#v,LqhP0[(oW_h#K]Fe)i-6Z4DMMqg/\"\n        \"NM,0^NMp,K3MCX=D<)gk]>:cH;@/\"\n        \"GdV@<u)s@AqTJDW7VPKQ,ulK:B92Lw+TSSjx02U-uLMU@_iiU*LADX0-:>Z\"\n        \"@7;Db>IR]cKj_lg3('2hM5/B#W-W'M9YK_Mv+E*M:.v.#WHg;-?e2q/\"\n        \"M+c6#Wrt.#6[Gs-cJ)uL9We;#RAg;-bIg;-+Tx>-dIg;-HA0,.`$VEMK@H>#/\"\n        \"(lA#)Gg;-sTg_-xu^e$IoYw9\"\n        \"b_.NMx`POM)5;PM0`%QM0+J6MV:;GDI?6,EQ%VPK4092LuJ%#QqoSSSrFhiUpX),W*\"\n        \"LADXN2;>ZFI;DbKj_lg.C:Gjl5q.r`+l^G&>U+`5pc+VunQe$4T+F.[,DcVHdK_&_q(qr_G@\"\n        \"`W\"\n        \"BLE_&d4Mq;_Yw@X%C]`Xv9x%YwB=AYbq(^G^O[_&A,>F.$vce$%#de$&&de$')de$(,de$)/\"\n        \"de$TX/:21l[_&2o[_&3r[_&.>de$(XXk=vf;qVtZ&J_70c?K2Jde$4Pde$M,FL,Z29_]\"\n        \".<1:2A7q'8[qee$uLGL,Aix-6b-fe$2$9@03'9@0/\"\n        \"L@F.h?fe$+rGL,>m1:2w`VX(kE]e$uxn+M&x@8%.b7p&;9S5'YQmA#T&uZ-t[NX(>`NX(3K^\"\n        \"e$4N^e$5Q^e$Td8F.Ug8F.8Z^e$\"\n        \"KUGR*JJ,REx&h#v$4LJ#JDQ&MQI-LMkO6LMSU?LMT[HLM%iZLMWndLMXtmLMl$wLMa**MM[\"\n        \"03MMr=4)#qU3B-^Gg;-v(>G-.wX?-#.A>-u5&F-v5&F-(``=-mGg;-T]3B-ulG<->wX?-\"\n        \"W]3B-rGg;-Xjq@-Yjq@-MRx>-0Hg;-UwX?-8mG<-9mG<-_E:@-5Hg;-H``=-I``=-=hDE-E;\"\n        \")=-HM`t-4xE$MIL3UM_P<UMSVEUMT]NUM$dWUMViaUMWojUMXusUMv''VMZ+0VM*29VM\"\n        \"]7BVMP>KVMj0dWMc8mWMfJ2XM1O;XMpTDXM3[MXMxaVXM#h`XMfoiXMusrXM2$&YM&H]YM'\"\n        \"NfYM1D]4vTFHL-E-a..D($&MT9t^M`WK_Mf&-`M)-6`M*3?`M]8H`M_DZ`MsPm`MaWv`M\"\n        \"o])aMei;aMfoDaMguMaM0&WaMo+aaMj1jaMx=&bMGD/\"\n        \"bMuOAbM,VJbM']SbMxb]bMtnobMutxbMv$,cMunS+Mn^i/#Peg,.i&GwLlri/\"\n        \"#Q&6;#k2YwLnfi/#mEQ&MvNvwLiei/#(@gkL\"\n        \"$niXMuS&7.to$YMO0'5#PX`=-(nG<-ASx>-$Ig;-%Ig;-&Ig;-'Ig;-(Ig;-)Ig;-TF:@-\"\n        \"1nG<-2nG<-3nG<-.Ig;-(v,D-r'hK-G1r1.01tZM4G1[M5M:[Mojg_M^(-`M/-6`MB3?`M\"\n        \"]8H`M#Qm`MBWv`Mdc2aM3j;aM5vMaM0&WaMi+aaM,2jaM?8saMw4aEMK@H>#M-mA#)Gg;-::\"\n        \")=-f=n]-4,(@0.+_HM>9iHM?\\?rHM4E%IM5K.IM6Q7IMUW@IMV^IIM9dRIMKa@.M8xNM0\"\n        \")eASItDrOfOHE/\"\n        \"2kDbJ2SZ&g2TdA,3%8$d3W)>)4X2YD4lru`4aV:&5[MUA5K*0^5PgV'8^u_e$vH/\"\n        \"LG.l2@0#WAL,uR>XCvU>XC(BIR*mL`e$Tok'8u3X_&>F3@0Wxk'8r[`e$X2%44\"\n        \"Y5%44MO;F.0Cae$U64@08*Y_&9-Y_&_v,:25Rae$HMJR*IPJR*=4G_AEvQX(F#RX()6T+`\"\n        \"G3TJM_wjfMS[/,NTeJGN$0hcNVw+)OW*GDOX3c`Ov=/&PZECAP*g`]P]W$#QP:B>Qjx02U\"\n        \"ckQMUf0NJV1CefVpX),W3UEGWx'acW#1&)XfTFDXu0]`X2qx%Y&qpuZ'$6;[amF>d4[=\"\n        \"qVCOGkX9mDrdRA[ih`+9Gjfb1Al)_M]l*hixl]X->m_kdums^EVna.drno^&8oeK]oofTx4p\"\n        \"g^=Pp0mYlpo,u1qj#:MqxYq.rG%8Jrucm+s,;3Gs'2Ncsx(j(tt(J`tu1f%uv:+AuwCF]\"\n        \"uFG5sI.RI'S_/d+VDUrsIQ[@ul>ADcVFOMsI?WvOfv'acWA?JsIP/$)3x9ADX,Z,qrh+XxX\"\n        \")bOAY0_5R*(P[_&A,>F.$vce$%#de$&&de$')de$(,de$)/de$TX/\"\n        \":21l[_&2o[_&3r[_&.>de$(XXk=r;UwTEYeKc$$Bf_4DBJ`5M^f`ok5Dk^K7Al/\"\n        \"qM]lBZjxl]X->m#qEVnB)crn\"\n        \"dBASo3d^oo5v>Pp0mYlpipt1q,m:Mq?VViqxYq.r#YkA#MJU[-`,^e$:SNX(ewg-6,r9-M(\"\n        \"kpi'>TO2(?^kM(4B0j(5KK/)6TgJ)Uc-g)VlH,*9pcG*LY)d*<ibB#0%6;#t-q#vDCg;-\"\n        \"j-A>-RGg;-SGg;-#wX?-VGg;-WGg;-k_`=-`lG<-ZGg;-J=K$.I4UhLD=EMM_BNMMwIWMM/\"\n        \"OaMM,0^NMv6gNMw<pNM)B#OMnG,OMUN5OMvS>OM?ZGOMXaPOM(SiPMYYrPM`(SQMN.]QM\"\n        \"14fQMV:oQM9@xQM:F+RM`L4RM6R=RMIXFRMJ_ORM>fXRMFkbRM*0f,v(k)M-^;)=-RHg;-\"\n        \"SHg;-#xX?-UHg;-VHg;-WHg;-uHrP-YHg;-)xX?-[Hg;-OPKC-^Hg;-bp)M-cp)M-0/A>-\"\n        \"oHg;-2/\"\n        \"A>-wmG<-xmG<-eKHL-tHg;-1a`=-vHg;-&Ig;-GfXv-[Pc)M=)X^Mm8u5vEhG<-Z<)=-`<)=\"\n        \"-(#Y?-)#Y?-[Ig;-]Ig;-qa`=-`DdD-n<)=-cIg;-eIg;-fIg;-/Tx>-nnG<-\"\n        \"iIg;-v<)=-Flq@-snG<-+b`=-&=)=-wnG<-rIg;-tIg;-uIg;-vIg;-)IwM0<0)0vxa-0#)\"\n        \"j](McYmDMQ@H>#)4Z;%LqhP05:lA#jGg;-(Hg;-^``=-pmG<-QnG<-[HwM0*i&.vog60#\"\n        \"S_K%M==e3#RAg;-mHg;-%C]'.rDuwLFw70#tHg;-uHg;-vHg;-xHg;-*Ig;-,Ig;-wPKC-8*\"\n        \")t-wn4wLG-6`Mb2?`Mdc2aM`vMaMn%WaM=>k<#]Kx>-rtcW-qB6L,&PkGM)UY,M6Ptl&\"\n        \"AKS5'rDnA#7lG<-;lG<-=lG<-8Gg;-:Gg;-lD:@-KC&j->hOX(RS_e$SV_e$Xf_e$;Tr-\"\n        \"6bOW_&`%`e$a(`e$Esr-6K/s-6xaPX(mL`e$oR`e$pU`e$A+;F.G=;F.0hX_&A8JR*0Cae$\"\n        \"C>JR*8*Y_&3Lae$6Uae$IPJR*?\\?Y_&:bae$$7W3k4em'8^iRX(RTbe$SWbe$I<LqVaMZ_&[\"\n        \"pbe$C<n'8dVZ_&?='44w_SX(r+[_&*ILR*oSce$,OLR*w:[_&r]ce$ufce$vice$NF/:2\"\n        \",][_&')de$F$UX(+we-QLCee$SXee$rCGL,Znee$_$fe$FA_'Sms^_&jEfe$mNfe$t^Se$S(\"\n        \"(AO2Co1KgJ+F.e;d+Vv?J_&k<>e?gMDcV$GD_&MMU+`obDGW8=e5K>d/AF+0hYZPRMe$\"\n        \"4CTX()/\"\n        \"de$+5de$-;de$tFhw91BQe?:1]_&c-]e$v+4GM)XlGM)UY,Mt0mf(M-mA#2$H`-.7OX(\"\n        \"iBtEIIvA0M8r//1mVB,3pr>)4`Mu`4]Vq]5xVnA#J+kB-?jq@-Djq@-Ejq@-BE:@-\"\n        \"9Rx>-:Rx>-ARx>-FRx>-GRx>-6;)=-RRx>-SRx>-7CdD-QmG<-RmG<-lRx>-[mG<-VHg;-q;\"\n        \")=-V,kB-W,kB-x;)=-`,kB-T9RA-0<)=-:Ig;-uKj:/`/4&#OvV^M77u5v@hG<-Z<)=-\"\n        \"`<)=-$Tx>-]Ig;-pa`=-hnG<-cIg;-eIg;-fIg;-hIg;-iIg;-L:RA-unG<-qIg;-rIg;-\"\n        \"tIg;-uIg;-wUGs-'&rxLN7fK#0OD&.<QERM]7BVMpTDXMnFHL-l_Xv-mk,tLqIhkL_[>.#\"\n        \"[W$#QpX),WS1/F.li`(W;fM/\"\n        \"M.D_e$kF`e$).ae$i;Pe$-PVS%+^2GMJGce$mMce$t[@ul&4<,WQYPe$9.]_&Vbee$%]>L,\"\n        \"v+4GM)XlGMKx90MC^#pLaedi9q<C2:S?mA#.mG<-/mG<-\"\n        \"pwX?-v;)=-w;)=-W<)=-LIg;-SIg;-x/A>-lCg;-P>aM-QG&j-0[/\"\n        \"eZN]s+M#`OL#PW5,NQW5,NR^>,NvB&j-0X/eZ,Sj34t*h(NvB&j-0X/\"\n        \"eZNYj+MQXEUM$fXL#Q^>,Nv9aM-Q>aM-\"\n        \"Q>aM-d-ZpLU=aM-kZV6M+C&j-1[/eZiruKG>60eZO`s+M$fXL#Q^>,NwB&j-1[/\"\n        \"eZ.Zj3O($r9M&n_9MHW.DN%YeKc2NBf_kWT-Q4a#G`wJM'S:rZ'S:rZ'S:rZ'S:rZ'S8fH'\"\n        \"SXQmA#\"\n        \"+'uZ->u_e$jC`e$fK$GM-ZGOMx`POM(SiPM)YrPMFF+RM;L4RM0-#O-_1#O-kUS?MMh`\"\n        \"XM0niXM<v:P-eUYO-gh:1.$7%bM(8W*M02c1#E-4&#8HbGMRh2vuihG<-3s%'.?sKHM0-VHM\"\n        \"O'(SM@w70#u'A>-]``=-uwX?-TZGs-D5UhL>/\"\n        \":.vNCg;-^Hg;-fHg;-jZGs-SxE$M(=vWMdL<0v$$)t-L;@#MB_W0v`TGs-cS-iLw)/\"\n        \"YM4*YZM15lZM`E23vLZ`=-VsUH-@<)=-HIg;-\"\n        \"IIg;-KIg;-LIg;-c7&F-^iDE-u[]F-r<)=-]QKC-(ucW-kb=R*w1=GM/XlGM/\"\n        \"UY,MYGXP&S?mA#T&uZ-mS^e$SnGR*gi1@0H5_e$jM9F.Ob)_]IvA0M%s//\"\n        \"1'hqA#h-A>-i-A>-ZC&j-\"\n        \"sNV-Qb_.NMN$KNM+*TNM,0^NMK6gNMQZGOM@aPOMG5;PMJGVPMKM`PMLSiPMMYrPMN`%\"\n        \"QMEr@QMFxIQMLF+RMlL4RM%RhSM^&RTMi.[TM-4eTM%E*UM'vsUM+k5WMt$QWM7+ZWM&1dWM\"\n        \"p7mWMq=vWM/\"\n        \"C)XM5h`XMsniXM#=JYM50sxLV,u1#1Ig;-3Ig;-4Ig;-S>dD-@rDm.H&9M#*S%:.[fgl/\"\n        \"9_TJD=q5,Ecj$#QpX),Wt'ADXm2VAP[#^e$(*^e$.<^e$/?^e$0B^e$JmtKG\"\n        \"^X(AO46ZAPV_SX(vP/\"\n        \"XCv1a(W:2JP^Nacof^J`lgdBASo#YkA##Gg;-)Gg;-N&uZ-3)(@02C-IMA>FJMY1_KMZ**\"\n        \"MMqg/NMq5gNM#5;PM)YrPM*`%QMK&RTM:3eTM^ojUM9i5WMq6mWM\"\n        \"l<vWMx)jxL.812#GJA/\"\n        \".c(HtLX812#RHg;-*tA,Mi:12#%nG<-vHg;-d8K$.6&7YM0a+ZM,m=ZM/\"\n        \")YZM^J)3vD)02#PSx>-<*)t-JYajLX-b^MZ,6`M$3?`MguMaMh%WaMj1jaMk4aEM\"\n        \"VNtl&0tnP'1'4m'6TgJ)7^,g)8gG,*QHE/\"\n        \"2RQaJ2SZ&g2X2YD4]Vq]5@]oA#%S:d-L<PX(lI`e$mL`e$oR`e$9i:F.:l:F.;WQX(0Cae$\"\n        \"m[FG)nhBJC:U9/DSQUJDTZqfDC-6,EQdmcE\"\n        \"W[NJMRRjfMS[/\"\n        \",NX3c`OZECAP[N_]P]W$#QBqIJVubdfVpX),W'1EGW#1&)X#8DDX$A``X,_x%Y&qpuZc2n#\"\n        \"Q#f/]Xd_vr$0tnP'5LTJDA^w]G@><R*E-be$F0be$I9be$A#Lq;Z1KcM\"\n        \"TeJGNdNGDO7]O;R0Is92(uSX(uT:#QYC[_&ufce$vice$_hf=c>QY]Y)htxYc%suZ'$6;[*\"\n        \"9dV[<V4L,)/de$HA>F.1l[_&2o[_&3r[_&b4V+`Krfl^ZI,2_=MFM_J%ci_?`'/`4DBJ`\"\n        \"Ar^f`Kd:5gH7n92LCee$C4<VH]Fl%lxB2AlZFL]l$Uixlg^=PphgXlpj#:Mq.)r.r)lkA#\"\n        \"Xe0o-krU_&Qwk?K,r9-Mrjpi'>TO2(9KkM(4B0j(XOpA#SQx>-B:)=-UQx>-8Gg;-R-A>-\"\n        \"4OKC-^:)=-RGg;-SGg;-+E:@-vQx>-e:)=-`lG<-MOKC-blG<-%Rx>-v-A>-_Gg;-`Gg;-#.\"\n        \"A>-dBdD-x:)=-mGg;-oGg;-2.A>-Q8RA-(;)=-GwX?-*Hg;-;;)=-6)`5/Zk9'#H3nQM\"\n        \"9@xQMeF+RMfL4RMBR=RM7XFRMV_ORMQeXRMFkbRMS2eTMDK3UM_P<UMSVEUMT]NUM<\"\n        \"dWUMuiaUMWojUM%xsUMl%'VM#,0VM[19VM]7BVMc>KVM3i5WMe=vWMnH2XM1O;XMpTDXM9[\"\n        \"MXM\"\n        \"xaVXMAh`XMgniXM+trXMv#&YM$<JYM]H]YM+</\"\n        \"#MZVC2#Zc;<#0KCXM2;uZMVjg_M;&4<#(<^M#SrcW-=r_e$kF`e$qX`e$).ae$4Oae$kGce$\"\n        \"qSPe$+(u=YxwWw99+'44Z+t+M[PhM#\"\n        \"-2fYQ@Nx>-)]=Z-]x.F.[.t+M_J^VM(J^VM(J^VMQV_2#i6K$.AHbGM0-VHMHdhsLiW_2#^\"\n        \"mG<-a>Y0.CNX,MpO#v,eIJ88#Tk]>*>)s@7Lt2Cv;o7RnWae$Y]RX(cSZ_&klZ_&lJce$\"\n        \"&oSX((JI_&[DVlJ5,5SR0V=_.e2A5#Zx(t-Bj](M2;uZM3>l?Mb]ID*5:lA#TGg;-i:)=-^\"\n        \"Gg;-$;)=-wlG<-rGg;-0oanL/bN^-xiQX(CpQX(]sbe$pVce$/XLR*rVPe$<Rol&/kR5'\"\n        \"Cde&G(fUoRX%H'SS#?SIF;#sIIVuoJng2sRHK<F.]fRX(RTbe$C)XY,3P=;R)F6sRIDce$]\"\n        \"HvOfwb-/VZdJJVxw;,Wh9BX(33EL,Qt'44-M1ci<Vbi_4DBJ`5M^f`aAdofNh+5gKj_lg\"\n        \"Ls$2h]X->m>)CSo[Iw1q)lkA#LJU[-e+EX(%JbGM/XlGM/UY,MIQ#v,J_1p/\"\n        \"_QiP0`Z.m0L+pA#Z:)=-t.Yc->u_e$k9PX(9[+:2843@0973@032BL,r[`e$;JBL,@YBL,A]\"\n        \"BL,6HQX(\"\n        \"L(CL,5Rae$7<fw9jN<F.R#Z_&lT<F.`JZ_&klZ_&,tDL,Qgn'8xbSX(SHv-6tcce$.+BX(\"\n        \"07u=Y<0R8SgCZe$Y.;MMqYGOM4F+RMqT)xLeh5WMTl-3#-V^SSAW=Z-c%/F.a=t+MUuHN#\"\n        \"/f,pS(^t?09%wr$.b7p&/kR5'0tnP'5LTJDKi0pSR_nEI&&.JU)1e/VI0SlS[nSX(4u[_&Z/\"\n        \"NR*K@ee$c-]e$v+4GM)XlGM)UY,M&X92'GqlA#5Gg;-M:)=-MGg;-g:)=-klG<-kGg;-\"\n        \"#Hg;-)Hg;-*Hg;-8,kB-SmG<-,F:@-kmG<-kHg;-lHg;-xBg;-/h3j.Yk>3#CKx>-?<.BM/\"\n        \"XlGM^3jvu<kgJD284L,H)RX(OpY_&V/Z_&RTbe$`=sRnp)ZDO[xPe$*l4]Xo[vfV$(Re$\"\n        \"r]ce$;p=F.$D[_&ufce$vice$xoce$;KEL,$vce$&&de$')de$BaEL,,8de$:UTX(9/\"\n        \",DEP)4AlaXL]ln0ixlg^=PphgXlpl5q.r#YkA#Yj3f-krU_&X`v92.+_HM89iHM9?rHM5K.\"\n        \"IM\"\n        \"7W@IMJ^IIM9dRIMM>FJM2J-LM_O6LMSU?LMT[HLMohZLMXtmLMg**MMfHWMMkg/\"\n        \"NMF<pNMsA#OMnG,OM1N5OM)5;PMB`%QM/\"\n        \"(SQM<.]QM2:oQM6R=RM9eXRMM2eTMpJ3UMXP<UMSVEUM\"\n        \"T]NUMJpjUMZ+0VMh19VMeh5WM@=vWMnH2XM%O;XMqZMXMusrXMv#&YM$<JYMB6&#MF=I3#\"\n        \"ja;<#HHbGM.wCHM/'MHM0-VHM^R,.#N9xu-_,W'Mk6mWMw0HwLW,I3#&<)=-4nG<-Za`=-\"\n        \"KIg;-dO,W-Yp]e$)-^e$Mp'@0+l0-M,92,)Ac<#-YH.m0ZD:&5qnJ88q<C2:#Tk]>)5dV@*>\"\n        \")s@KiUPK:B92L,Z,qr#Q>PSq=LMUl4hiU$_9>Zi%(mToM*eZSTFYG=O)mT%-be$F0be$\"\n        \"EYhl/ce&DWtfKe$e)VX(ga^_&a']e$K/\"\n        \"gKM0+J6M@Lr(ELrqlKi&%#Qvk),Whx02UOqwoo0dn'8v-'SnnTarnc9&8o8FD2U%OAk=\"\n        \"VDgw9+7./:/GdV@1(=2C/&52UD]Op/DLt+M6AwN#\"\n        \"i;)=--/A>-(mWN0p?cuu:.d3#8h](M(RcGM/\"\n        \"XlGM.L>gL>De3#C%mlLA'MHM0-VHMvUG*vZ6)=-ZRx>-&pj#.(xE$MQWqSMF^$TMs&A0#_4)\"\n        \"=-@W)..t:)UM'Q<UMT]NUMWojUMviEuL\"\n        \"g>e3#`ZGs-?;@#M:o>WMn$QWMk6mWM:D)XMnH2XM]UDXMqZMXM(bVXManiXMusrXMv#&YMw)\"\n        \"/YMx/8YM$<JYMjUoYM-]xYM*a+ZM=g4ZM2m=ZM3sFZM4#PZM_8v2vd;7I-7nG<-aakR-\"\n        \"K/A>-@<)=-7[Gs-^'mlLhqE^MK'X^ML-b^M)t$7v.Z`=-b2#O-XIg;-/\"\n        \"G:@-[Ig;-i<)=-sVYO-tVYO-r<)=-)0A>-hIg;-+0A>-rnG<-:6]Y-'Z'@0w1=GM/\"\n        \"XlGM]t2vu'7)O#LW=Z-\"\n        \"f>^e$1E^e$+W7kX2C-IM=W@IM8^IIMbjJ%#s>:@-c@On-X^q-65YZq;H5_e$cb%LGJ)^\"\n        \"KMN7hKMT>qKMdI-LM3P6LMSU?LMT[HLMDodLMdumLMf$wLMa**MMb03MM4AO%vjU]F-j:)=-\"\n        \"_Gg;-`Gg;-#.A>-2rUH-mgDE-+)>G-kGg;-4Rx>-mGg;-#6&F-ulG<-Jjq@-jt,D-(;)=-`]\"\n        \"3B-c]3B-_8RA-)Hg;-<``=-?``=-SwX?-2ZGs-F&mlLh;oQM9@xQM'G+RM(M4RMBR=RM\"\n        \"7XFRMV_ORMUgXRMFkbRMiQhSMg-J0#%V3B-LHg;-X6&F-pRx>-RHg;-SHg;-69RA-VHg;-d;\"\n        \")=-YHg;-ZHg;-[Hg;-h6&F-]CdD-Q,kB-mhDE-$VYO-kHg;-(a`=-)a`=-Hkq@-oHg;-\"\n        \"P9RA-wmG<-X^3B-3%;P-*<)=-uHg;-vHg;-a^3B-,nG<-K.:w-H*ofL1Dn3#(Gg;-0Gg;-[\"\n        \"g=(.&fiUM(*jxLgEn3#a>Y0.NNX,Mn]ID*/(lA#GlG<-ZlG<-klG<-tlG<-)mG<-*Hg;-\"\n        \"t10_-nWae$Y]RX(cSZ_&klZ_&lJce$&oSX((JI_&DAX3OB+f+VFh<wTJxP-Q.FZ;%76xP')(\"\n        \")t-2EQ&MRe=rLEQw3#7'$&MKWqSMF^$TMIp?TM5'A0#eX`=-+kq@-TZGs-,T:xL8/:.v\"\n        \"XCg;-`ZGs-IxE$Mln>WMn$QWMi$6wLIKw3#qCdD-tHg;-uHg;-vHg;-xHg;-qPKC-)Ig;-6<\"\n        \")=-1nG<-2nG<-3nG<-4nG<-6nG<-0DdD-8nG<-:nG<-7[Gs-D@nqL%rp_Ma,6`M03?`M\"\n        \"dc2aM9qDaM#vMaMt%WaMl=&bMw'#GM^@H>#A_lA#*lG<-/lG<-Xe0o-,E/\"\n        \"F.,uKHM13`HM3<`-M%:2,)=p,g)8gG,*:#)d*SC=#-sQY>-5jR8/O6eM1QHE/\"\n        \"2qVbJ2SZ&g2W)>)4-]ZD4\"\n        \"`Mu`4aV:&5b`UA5ciq]5^`6#6?bS>6`rmY6#o3v6w*K88s1J59(k_M:)t$j:oNZJ;pWvf;\"\n        \"9g<,<rjVG<AYl]>PCiY?0P)s@3l%pA5(]PBZH#mB7Lt2C@:a+Vo)Y_&j4t-64Oae$YB4@0\"\n        \"HMJR*Uh;F.JSJR*EvQX(@BY_&c9<F.7ZWY,2092LVYQJMkEkfMS[/\"\n        \",NUnfcN%9-)OW*GDOfa(&P5,EAPba_]P]W$#QcqB>Q-PTSST-RPT4:iiUsO-/\"\n        \"VtXHJV=hefVpX),WE6FGWx'acW\"\n        \"MZ')Xt'ADXCH^`X2qx%Y$_9>Zc%suZ-66;[>3OKV-k?o[t3[V$B-Se$.aU_&/\"\n        \"dU_&6#V_&'StOfLmi`FZp]JVupae$E-be$F0be$bhCL,,RGG)mE4/\"\n        \"M_wjfMTeJGNW*GDOI=P;R(p3L,\"\n        \"3W=F.7XoEI7:#AXu0]`Xv9x%YwB=AY9=]]Y.?QV[`.or[*?28]7mMS]2dio]3m.5^4vIP^\"\n        \"Lpil^a[,2_Hwhi_9M'/`5M^f`w3HDkP[nEIe)VX(;Vx-6[qee$=3n3Oms^_&h?fe$2U@F.\"\n        \"EP*44w]MX(uxn+Mpw@8%*=vV%9HrA#@_`=-LR:d-:cv92.+_HM?<`-M892,)H5hJ)I>-g)\"\n        \"JGH,*:#)d*etpA#(]3B-^:)=-RGg;-SGg;-88RA-_lG<-`lG<-G+kB-6jq@-OOKC-q_`=-\"\n        \"(Rx>-s_`=-WOKC-x:)=-mGg;-oGg;-2.A>-Kjq@-ARx>-*Hg;-G.A>-6)`5/\"\n        \"P.4&#Y3nQM9@xQM_F+RMaR=RMCXFRMP_ORMPfXRMFkbRM<-J0#ecq@-@PKC-d``=-RHg;-\"\n        \"SHg;-*F:@-\"\n        \"VHg;-hUYO-Qu,D-f;)=-#Sx>-[Hg;-bhDE-Vu,D-WPKC-lHg;-0/\"\n        \"A>-oHg;-8Sx>-wmG<-@xX?-fPKC-+<)=-vHg;-Tkq@-,nG<-K@Qp/Ag60#BD24#)*)t-/\"\n        \"@gkL5#5$MqV34#P`Xv-\"\n        \"Z4k?Mh]ID*5:lA#TGg;-oe%Y-PmHR*$hPX('qPX((tPX(#wSq;1>*\"\n        \"RMAL4RMCXFRMi7BVMpTDXMsg`XM4[2xLZ[<4#,x(t-M0xfL-_<4#.lG<-/lG<-<L@6/\"\n        \"F4u6#cZ<rL.^<4#jE:@-\"\n        \"EHg;-FHg;-[``=-E=D].I><-v@rX?-&F:@-XmG<-YmG<-%4:w-n0/vLr%'VM2>KVM3Y$/\"\n        \"vt5)=-lmG<-hHg;-3aU).W5UhLVkj0v&Cg;-tHg;-uHg;-vHg;-jPKC-(nG<-.nG<-)Ig;-\"\n        \"6<)=-1nG<-2nG<-3nG<-YSn*.JPc)M#0cZM=5lZMC<uZM9A([M:G1[M5M:[MLu$7vdqX?-d<\"\n        \")=-XIg;-MQKC-[Ig;-O(hK-WQKC-(0A>-#b`=-hIg;-&b`=-kIg;-x<)=-@Z=Z-V_Ae?\"\n        \"uxn+M-JdY#4O?v$5XZ;%<0WW%XCa(W8]v92+l0-MVcTM'`dmA#=:)=-,uDi-(IGR*HLGR*\"\n        \"IOGR*8Z^e$R9@L,(?j'8AqKe?H5_e$OjD_AU+W_&v@2@0RS_e$SV_e$2k#44_FW_&`IW_&\"\n        \"`DE_AbOW_&]r_e$>^r-6Kxcw9.l2@0svHR*v$7RE*mAL,(BIR*mL`e$oR`e$8f:F.qX`e$:\"\n        \"l:F.MB,:2,[X_&0hX_&3qX_&Yg,:20Cae$?q1;6,OCJC9LtiCk;;/D5LTJDZmqfDI?6,E\"\n        \"VmQGEKQmcEFH2)FJ;B;Iie;5KE_nPKVGOw9XT@XCpa<F.RTbe$SWbe$*/\"\n        \".:2Vabe$BSAwpiwO1p,g)&P;>EAP$T`]P]W$#Qo?C>Q3cTSSZ?RPTwVkiUsO-/\"\n        \"VtXHJV=hefVpX),WE6FGW\"\n        \"x'acWMZ')Xt'ADXCH^`X2qx%Y$_9>Zc%suZ-66;[?B9HWE>SY,xEwr$0tnP'IVuoJ1^^GW@\"\n        \"WBwTMD(J_22bi_4DBJ`IW(5gef%2h#YkA#i@DX->u_e$qX`e$r[`e$s?[w91>*RM;L4RM\"\n        \"7XFRMi7BVMpTDXMsg`XM.[2xLJ$O4#d$6;#XmBHM/'MHMh92/\"\n        \"#T4)=-EHg;-HZGs-a15)Mi8kf-*i[3OfmDlfp+alg_Fm9Mnet+MhojO#4ivcWQN>e?D:@;$(\"\n        \"+?v$*=vV%40op&L2`%X\"\n        \"e>^e$<YNX(IE2;6OV^VIF;#sILlL5K0?CX(.3?e?PSCf_:.<)Xo-]_&4Pde$)vw-6Oq]_&\"\n        \"Qw]_&QtS_&X%v1MTMsu5ws;,<x&WG<:U9/D;_TJD#1&)X$:ADXx9ADXU-xooB')qr.kR5'\"\n        \"LhNDXiJaq-C9,qroek+M7wa4#owX?-J/\"\n        \"A>-HIg;-Wh&gLhm6]-W9X_&4Oae$qSPe$Y#]'8uK:]Xp,Ve$JUx(3%C@;$2'5&Y^)^e$&j&\"\n        \"eZ7:3LGqkk+M`+t4#Dq@u-)5UhLbwXrLkjDxL\"\n        \"=1nlLL^$TMPvHTMcwqd-xPk?Km>Aig%j$jq?nTxXIb&&YJh/\"\n        \"&YLERe-)5d9M61wKGJ0v=YJiWe$Ljt?0R9pl&>e=X(/\"\n        \"?^e$+GxFMx-v.#Y4)=-W.A>-EHg;-FHg;-xqN+.$mGTMq,[TM\"\n        \"'?f0#mpX?-Z=Y0.,G;UM$<JYMA)YZM.<d6#Q'A>-4Ig;-#lq@-K[Gs-=`K%M^'X^MR-b^\"\n        \"MM3k^Mu+aaMD2jaMj.WEMEbTM')lkA#6Gg;-E:)=-TGg;-V$H`-T0X_&x<X_&S%R`<6*@GD\"\n        \"=q5,E_Ec`O7>&#Qvk),Wt'ADXB&`^YihnRn/<pl&8eRe$/\"\n        \"?^e$g-_9Mic?SIF;#sI#OOAYZ[5L,afFL,I4Re$Qnk34)/\"\n        \"XuY)(>Ji-kR5'^h8#ZjkujtQnk34TF=X(LgY_&_SuKGo=s1K\"\n        \"aAdofKj_lg$kK>Zim1kXL:s92$$n:Z01F>Z.D]'.%a5TMY^_sL.L^P#7scW-nWae$]sbe$\"\n        \"nJPe$U*(44U*(44U*(44p:WlJ]@;5K$_9>Z<ekYZp]^e$TY_e$nO`e$1Fae$7Xae$]sbe$\"\n        \"nJPe$4WlQa8iTrZ;*A3kgoTw9NfD_&':E3k$:LcMrk`cW0_ADXu0]`Xv9x%YxKX]Y*?28],\"\n        \"Qio].dIP^0v*2_HNcofKj_lgZFL]l[Ohxlg^=PphgXlpl5q.r#YkA#=%^GM'lpi';^K/)\"\n        \"7^,g)8gG,*:#)d*QHE/\"\n        \"2RQaJ2SZ&g2X2YD4ZD:&5[MUA5`rmY6a%3v6l3_M:m<$j:oNZJ;pWvf;/\"\n        \"l[PB0uwlB21XMC3:tiC6UpfD9qlcE:$2)FQINJMRRjfMS[/,NW*GDOZECAP[N_]P\"\n        \"^a?>QnFHJVoOdfVqbDGWrk`cWu0]`Xv9x%Y&qpuZ'$6;[-T)<[s?x(3Kr?SIF;#sI)(LB#P.\"\n        \"4&#nQERM]7BVMnBdwLXsg5#d$6;#5qUvuNCg;-1YGs-@XajLKWqSMF^$TMIp?TMovHTM\"\n        \"ak<^MIkw&M)ZxYM`ZxYM1_p5#_.or[hnGp/\"\n        \"$*pEIk`g-6$1l+M1_p5#`4xr[X=n]->Qg-6Qb@;$x4J8]^)^e$*0^e$Rg68]ohF8]rXNX(\"\n        \"KE2;6P[Mq;E;#sIR(M5K0?CX(3b%_]`%Df_\"\n        \"CTK_&9.]_&4Pde$0[G8]/\"\n        \"q]_&X?(2h)lkA#+'uZ-DUW_&w9X_&x<X_&:0Y_&;3Y_&#A[_&x7I_&m,UwTm,UwT^V?-m;@\"\n        \"2P]`<US]mcRS]mcRS]ApY>6Ui^VIfV[S]cxgK-^)LS-P6)=-\"\n        \"&3e2M>l,6#mi[S]cxgK-xk&V-n0-h-KaEwT&7l+Mmh4ZM?uGQ#mi[S]e+-h-LdEwT'=u+M?\"\n        \"uGQ#noeS]=KBwT'=u+Ml[]#MGm@(#PNh/#E;#sIKc1pJv_<X(J<be$j:YY,EoND*TdA,3\"\n        \"nE?/;1(=2C7_5,E]W$#QpX),W+Z.5^v4(Ha3t[V$B-Se$#/xFM/\"\n        \"XlGM6-VHMC'(SM[wXrL*bf#MK@VhLe^$TMM^_sL4&?6#swX?-VmG<-RHg;-(<)=-tHg;-\"\n        \"uHg;-vHg;-xHg;-(Ig;-\"\n        \")Ig;-*Ig;-+Ig;-,Ig;--Ig;-4iDE-anG<-[Ig;-gIg;-hIg;-,0A>-rtcW-e;^e$;SEX(.+\"\n        \"_HM:E%IM6Q7IMCW@IM>^IIMQI-LMRO6LMSU?LMWndLMXtmLMY$wLM`HWMMl;pNMmA#OM\"\n        \"oM5OM/\"\n        \"(SQM0.]QM2:oQM6R=RM8_ORM9eXRMQJ3UMRP<UMSVEUMUcWUMViaUMXusUM:-\"\n        \"0VMb19VMnH2XMoN;XMqZMXMusrXMv#&YM$6&#M];H6#h$6;#lsKHM:e=rL?0H6#J?\"\n        \"gkLQWqSM\"\n        \"F^$TMJvHTMm]NUM/E23vg)A>-YxX?-Za`=-Q*`5/\"\n        \"404&#C'v1MIV8;6;LlA#wlG<-4Hg;-f;)=-#$)t-c0xfL&0Q6#1:@m/\"\n        \"Sj9'#9[u##klA,M'7Q6#]crmLNj6TM[p?TML,[TM-?f0#\"\n        \"AY`=-/kq@-/a`=-#Ig;-&Ig;-5tA,M=1Q6#Mpx/\"\n        \".4$a^MY&-`Mm&h(M%1Q6#onG<-qtcW-nxL_&07qHMVhZLM,qx/.=VMUM,LA/.kZbgLC8Z6#/\"\n        \"Gg;-2],%..NpSMF^$TMJvHTMZk<^M\"\n        \"K'X^Mi%<*M_Nd6#73u6#hHbGM0-VHM^UG*vgH`t-/\"\n        \"S-iL?3:SMdWqSMF^$TMFWUsL:<d6#LHg;-#xX?-rHg;-tHg;-uHg;-vHg;-xHg;-*Ig;-,\"\n        \"Ig;-npj#.=`K%ML4k^MZ,6`Mn2?`M\"\n        \"guMaMh%WaMj1jaM>MHL-xBDX-e;^e$A4>R*.+_HM<Q7IM=W@IM>^IIMQI-LMRO6LMSU?\"\n        \"LMXtmLM`HWMMl;pNMmA#OMoM5OM/(SQM0.]QM2:oQM6R=RM9eXRMQJ3UMRP<UMSVEUMXusUM\"\n        \"r,0VMb19VMnH2XMoN;XMqZMXMusrXMv#&YM$6&#MaSm6#73u6#iHbGM0-VHMe*2+vo6)=-2]\"\n        \",%.0NpSMF^$TMHj6TM[p?TMJvHTMPD*UMRP<UMT]NUM7lj0v44RA-tHg;-uHg;-vHg;-\"\n        \"xHg;-*Ig;-,Ig;-_kq@-anG<-[Ig;-gIg;-hIg;-lO,W-e;^e$R)(@0.+_HM;K.IM=W@IM>^\"\n        \"IIMQI-LMRO6LMSU?LMXtmLMZ**MM.IWMMr;pNMmA#OMoM5OM/(SQM0.]QM2:oQM6R=RM\"\n        \"9eXRMQJ3UMRP<UMSVEUMWojUMM,0VMb19VMnH2XMoN;XMqZMXMhvdT-e)*q-g0BX(0fWlJ]\"\n        \"7Z+`4Y?ulPRjfMTeJGN:u5K`7pO+`;<xr$0tnP'5LTJDQ,A&G+O5R*w0GG)C2^VIF;#sI\"\n        \"HMYSJWfXJ`,Bbe$YvbcWHQBDXu0]`Xv9x%YxKX]Y*?28],Qio]/\"\n        \"mel^3xWPgivFk=MFee$mfNR*[qee$g<fe$h?fe$869@0kHfe$w]MX(+oBHM/\"\n        \"$;-Mfjpi'<ggJ)=p,g)>#H,*QHE/2\"\n        \"RQaJ2SZ&g2X2YD4`rmY6l3_M:m<$j:oNZJ;/\"\n        \"l[PB0uwlB21XMC6UpfD9qlcEQINJMRRjfMS[/\"\n        \",NX3c`Or6GAPba_]PnFHJVoOdfVqbDGWu0]`Xv9x%Y&qpuZ;(Qg`_5WY,YF@SIF;#sI\"\n        \"IVuoJ$-/\"\n        \"g`>_O1pc3kfMk,UiqZ;vof`MC_&r##s$0tnP'`Z:EOnCFlfhpDo[k7_MU<lLe$K*d-6eZ(\"\n        \"DWrk`cW<-BDXu0]`Xv9x%YxKX]Y(-QV[lp#pf_.de$6ITX(1l[_&2o[_&3r[_&\"\n        \".>de$<[TX(4XW+`^VO]lbbhxldBASo(OAPpn#Ylpipt1q4<liqLuu?0w]MX(uxn+Mpw@8%*=\"\n        \"vV%wTqA#@_`=-.H)a-n%V_&:/V_&5Q^e$CoNX(8Z^e$:a^e$r'#44L9Ce?NA,LM_O6LM\"\n        \"SU?LMWndLM3umLM`$wLMa**MM[03MMlHWMMaNaMMeg/\"\n        \"NMX<pNMsA#OMoM5OMpS>OM#5;PMB`%QM/\"\n        \"(SQM<.]QM^F^-#'jNT#8mG<-3Hg;-6Hg;-I``=-IZ]F-E;)=-:Hg;-0S-T-J>aM-\"\n        \"@PKC-d``=-RHg;-SHg;-0kq@-VHg;-WHg;-m``=-[Hg;-[CdD-dmG<-K^3B-lHg;-$<)=-\"\n        \"oHg;-8Sx>-wmG<-rHg;-uHg;-vHg;-NF:@-,nG<-'Ig;-F<)=-7(hK-8(hK-[*)t-a-W'M\"\n        \"3/6`M[2?`M6:H`M_DZ`MmPm`MhVv`Mc])aMei;aMfoDaMguMaM0&WaMo+aaMj1jaM#D/\"\n        \"bMoOAbM']SbMrb]bMtnobMutxbMv$,cM=oS+M(4>9#nRL7#a4/vLf)>9#lXajLxaVXM$niXM\"\n        \"usrXMv#&YMx/\"\n        \"8YM*a+ZM,m=ZM)t$7vP@:@-anG<-[Ig;-I_3B-dIg;-s<)=-hIg;-lO,W-kb=R*&PkGM)UY,\"\n        \"MtOtl&C^4m'7^,g)8gG,*Ac<#-j;F/2XdaJ2SZ&g2X2YD4`rmY6eIJ88\"\n        \"49`M:sN$j:oNZJ;#Tk]><u)s@/\"\n        \"l[PB<CxlB7Lt2C's[1go)Y_&6Uae$CpQX(?\\?Y_&1VZ'S(M&44^iRX(RTbe$SWbe$Zmbe$[\"\n        \"pbe$7%'443&6@0lJce$*ILR*oSce$20EL,w:[_&ufce$\"\n        \"vice$Hf6@0,][_&@C]_&cg4LGSXee$lcNR*Znee$d,CulTTjumjTASomp=Ppp5:Mqm>6Jrv:\"\n        \"+AuRGIQg*DXY,>*0JUp_Qe$JDU+`lFHJVre;,W95H_&nE>e?'(*J_J%ci_F%CJ`jTASo\"\n        \"#YkA##Gg;-)Gg;-e3]Y-^Ep34J&K0M`DWY5j.7#6,k(m9q<C2:3T<,<x&WG<:cH;@/\"\n        \"GdV@F$:/\"\n        \"D;_TJD^IVPKLrqlKV?32U'cLMU;$')X$:ADXV<S]cef%2hxYq.r^iRmgj3K1pR,#s$\"\n        \"0tnP'W*GDO,w5mghpDo['o_MU<lLe$NdHG)obDGWrk`cW<-BDXu0]`Xv9x%YxKX]Y*9dV[]\"\n        \"T&@0)/de$6ITX(1l[_&2o[_&3r[_&.>de$<[TX(.UZ3OQH0YlbbhxldBASo(OAPpn#Ylp\"\n        \"ipt1q20YiqU@xlgV]MX(uxn+Mpw@8%*=vV%wTqA#Ah%Y-0Lk?K.+_HM:E%IM;K.IM7W@IMD^\"\n        \"IIM:j[IMA>FJM(/L0M#7,,2_vaJ2SZ&g2W)>)43oZD4`Mu`4aV:&5[MUA5l@nY6a%3v6\"\n        \"eIJ88XPaM:sN$j:oNZJ;pWvf;#Tk]>B1*s@/l[PB<CxlB7Lt2C)/\"\n        \"=igo)Y_&3Lae$6Uae$IPJR*IK8REEvQX(:bae$0NH'oJ2=eZ@&_q;dIKR*RTbe$SWbe$0f&\"\n        \"44Vabe$Wdbe$meKR*\"\n        \"[pbe$[kOe?dVZ_&KTn'8lJce$$iSX(oSce$8g=F.w:[_&r]ce$ufce$vice$NF/\"\n        \":2,][_&')de$F$UX(78VwT8;VwTY9^_&Uq2ci1)T]l[Ohxl6=2>m_kdummKEVnhBarnc9&\"\n        \"8oeK]oo\"\n        \"fTx4pg^=Pp0mYlpo,u1qj#:Mq#d6JroPm+s'2Ncsrli(tt(J`tu1f%uv:+Au?IG]uX`\"\n        \"n2hn12`a6+v7R@cN2h>hil/\"\n        \"v'acW$:ADXu0]`Xv9x%YxKX]Y*?28],Qio]X.GDk0Is92aN^_&\"\n        \"[qee$IOq'8d3fe$sSVX(h?fe$kE]e$uxn+Mdw@8%*=vV%@]oA#@_`=-1Gg;-7Gg;-8Gg;-Y-\"\n        \"A>-WlG<-RGg;-SGg;-XGg;-`Gg;--Rx>-rlG<-mGg;-oGg;-5``=-*Hg;-;;)=-6)`5/\"\n        \"eQ?(#84nQM<R=RM7XFRMEeXRMQ&RTM34eTM,K3UM_P<UMSVEUMZ+0VM[19VM]7BVM?i5WM:=\"\n        \"vWMnH2XM+O;XMpTDXM3[MXM%trXMv#&YM$<JYMJH]YM@lh[MQ'X^MkXK_MY&-`Mm,6`M\"\n        \"0He7vPk)M-enG<-jnG<-mnG<-jIg;-mIg;-(IwM07G^2#m(b9#^_K%M?L<0v0Cg;-pZGs-&\"\n        \"J)uLuHmwLu:c9#I/A>-Da`=-:nG<-dO,W-Yp]e$)-^e$dj7L,IvA0MQq//1S?mA#i:)=-\"\n        \"v-A>-plG<--.A>-wlG<-.``=-.mG<-;``=-:mG<-G``=-KHg;-9,kB-&a`=-3Sx>-#nG<-6/\"\n        \"A>-V/A>-X<)=-xH`t-2+ofL.kU:#(Gg;-uP&7.=tKHM0-VHM8)(.v]H&(MaFg;-smG<-\"\n        \"0/\"\n        \"A>-P9RA-9nG<-HIg;-IIg;-KIg;-LIg;-uSx>-_nG<-PXhlL`00_-fcU_&*0^e$Av^e$\"\n        \"jJ0F.IvA0Mb7158vE(m9wNC2:/#l]>4PH;@5YdV@6c)s@W7VPKM%72LeJSSSjx02U31MMU\"\n        \"4:iiU0-:>Z__:Ek_NGG)0dm+MB-Se$RTbe$r]ce$tcce$ufce$vice$[)]'8$p<YY0Q28],\"\n        \"Qio]/mel^35XM_$=_'8PY>F.:1]_&?Zkl/VA%2hZFL]l$Uixlg^=PphgXlpj#:Mql5q.r\"\n        \"#YkA#.Gg;-0Gg;-1Gg;-6Gg;-7Gg;-8Gg;-QGg;-RGg;-SGg;-XGg;-$S:d-ZvtEI]@VMMx;\"\n        \"pNMmA#OMoM5OMqYGOM:aPOMM(SQM<.]QM^F^-#mh#V#8mG<-RRx>-SRx>-B;)=-O.A>-\"\n        \"?mG<-QHg;-RHg;-SHg;-XHg;-ZHg;-[Hg;-1F:@-tmG<-oHg;-&<)=-wmG<-xhDE-#iDE-+<\"\n        \")=-vHg;-&UGs-WT^sLNwh:#VmG<-RHg;-rHg;-,tA,MTwh:#&nG<-d8K$.2'7YM0a+ZM\"\n        \",m=ZM/)YZM/#5$M/xh:#PSx>-@N@6/\"\n        \"sl9'#]$a^MZ,6`M$3?`MguMaMh%WaMj1jaMk4aEMVNtl&0tnP'1'4m'6TgJ)7^,g)8gG,*\"\n        \"QHE/2RQaJ2SZ&g2X2YD4]Vq]5@]oA#%S:d-L<PX(\"\n        \"lI`e$mL`e$oR`e$9i:F.:l:F.;WQX(0Cae$m[FG)j]EJC:U9/\"\n        \"DSQUJDTZqfDC-6,EQdmcEW[NJMRRjfMS[/\"\n        \",NX3c`OZECAP[N_]P]W$#QBqIJVubdfVpX),W'1EGW#1&)X#8DDX$A``X\"\n        \",_x%Y&qpuZX@(&l$_-L,3>72'P.r5K2HOxk.n]_&Oq]_&Qw]_&PnJ_&+EB;$.tDAl^)^e$*\"\n        \"0^e$@4GR*/?^e$<YNX(ME2;6(_ASIF;#sILlL5K0?CX(r:Uw97_Ef_-rGAlo-]_&4Pde$\"\n        \")vw-6Oq]_&Qw]_&QtS_&X%v1MTMsu5ws;,<x&WG<:U9/\"\n        \"D;_TJD#1&)X$:ADX_XL]lFbQ]lHnd]lJ#Ow9+#P9ik>1Yl[G[e$I$w]lQ'GG)1u>\"\n        \"qVLFOqVUpm+Mu4.;#T=Y0.E2tZMHk<^M\"\n        \"K'X^MAJk<#%+HV#crcW-QX`e$4Oae$qSPe$sA5LGsA5LGsA5LGsA5LGsA5LGVsm+Ms3?`\"\n        \"MD77;#r@lxls@lxls@lxltFuxl9.Yc-Q:$LGVsm+MD77;#sFuxl9%>G-t3Yc-R=$LGW#w+M\"\n        \"r'h(MU?5&#UQE3MDKCsLLRm`M4Rm`M4Rm`M.Rm`M.Rm`MVXe;#&e=00Aj&.v:Hd;#q+Yg.V=\"\n        \";0v_hG<-&<)=-BB#-M.Rm`MVXe;#,Z=Z-Yp]e$)-^e$Mp'@0+l0-M,92,)Ac<#-YH.m0\"\n        \"ZD:&5qnJ88q<C2:#Tk]>)5dV@*>)s@KiUPK:B92L^<GDO9uTSSq=LMUl4hiU$_9>Z`0arn(\"\n        \"2hrn(2hrn(2hrn(2hrn(2hrn(2hrn(2hrn(2hrn)8qrn>M7m-]i`Ee]/n+M(Yv`M(Yv`M\"\n        \"(Yv`MPf3W#(8qrn>DrP-(JrP-)S7m-^l`Ee^5w+MPf3W#(8qrn?M7m-^l`Ee^5w+MPf3W#(\"\n        \"8qrn?M7m-^l`Ee^5w+MPf3W#(8qrn?M7m-^l`Ee^5w+MPf3W#0lv8oSOdl/)1ixFpFh2U\"\n        \",nX4oXm]e$(*^e$J;_e$;q1;6G?6,EJ`:5Ki&%#Qvk),Wl)GToK$qOfjl^V$8eRe$.aU_&/\"\n        \"dU_&Ljt?0=Vrl&MpS5'0tnP'OKhJDR6&@0Zw;F.._jSoPgS+`OV^VIF;#sILlL5K>e=X(\"\n        \"0_GG)N@3/M''lfMTeJGNW*GDO$N;&PZ[5L,^vbe$4qDo[jfooSnxOPTk+LMU:S1/\"\n        \"VnFHJV]v+,WqbDGW(:acWaDCDXu0]`Xv9x%YwB=AYxKX]Y&kK>ZwEAk=,R7_])/\"\n        \"de$<*MR*1l[_&\"\n        \"2o[_&3r[_&4u[_&n?g=cYRGM_8Dbi_bs./\"\n        \"`L7CJ`Ar^f`JZuofZ%(F.I:ee$K@ee$LCee$vM7]XhbP`kcTr%lZFL]l0$jxl]X->mo^&\"\n        \"8otuGSov1)5ps,>Pp*ZYlpipt1q.)r.r(`bxu\"\n        \";LlA#G&uZ-aSU_&/dU_&b&P+`r[%m&A_lA#/Gg;-1Gg;-,uDi-r1V_&7W^e$8Z^e$/(el/\"\n        \"cL*d*K)sA#x7RA-5OKC-HGg;-d.Yc-.D_e$SvD_AbBHR*29r-6RS_e$SV_e$AYcw9cr=XC\"\n        \"e'PX(`IW_&aLW_&aGE_Ac'=ulm>:#6k7R>6`rmY6a%3v6'=K885%K59o1+m9,rF2:l3_M:\"\n        \"5B%j:nE?/;$r^J;vjvf;KG=,<kUYG</#l]>c$jY?e6J;@`-fV@*>)s@?:&pAAL]PBT6#mB\"\n        \"34O2CrKM-Q8*Y_&&-^q;'0^q;AjQX(6Uae$Uh;F.TZmEeEvQX(_Q4@07a^q;7ZWY,1'tlKM%\"\n        \"72L]lQJMqWkfMS[/,NUnfcN7p-)OW*GDOfa(&PZECAP[N_]P]W$#Qi-C>QdHVSST-RPT\"\n        \"o242U%_RMUl4hiU)u-/\"\n        \"V*(IJVI6ffVpX),WQZFGWx'acWY)()X4mGDX+U]`Xv9x%Y$_9>Zc%suZ-66;[3ppoo0vt?0?\"\n        \"c72'=?.&GKve]Gjhroo%-be$F0be$M_x(38Wu1KoE4/M_wjfM\"\n        \"W*GDOU-xoo*kAwT`8n+M/\"\n        \"r3<#I;-5.4:(XMZI2XMH_W0v]3RA-9Sx>-Qkq@-T)7*.O2tZM4G1[M5M:[Mak<^MNrE^MK'\"\n        \"X^ML-b^M]8H`M>d2aMZ#EEMP7-##M-mA#/@DX-e`U_&/dU_&\"\n        \"_`T-Q>6EJMJuBKM_+UKM_(C0MJr//\"\n        \"1i%r]5:JoA#^Gg;-k:)=-9E:@-8wX?-9wX?-3.A>-rGg;-;.A>-@.A>-A.A>-6;)=-L.A>-\"\n        \"5Hg;-7,kB-jRx>-RmG<-lRx>-`mG<-kmG<-,/A>-\"\n        \"Q^3B-x;)=-S9RA-tHg;-0H`t-B+ofLgx<<#(Gg;-.Gg;-/\"\n        \"Gg;-0Gg;-(],%.tS2uLcv<<#'sw6/\"\n        \"P&6;#8LCXM:#PZMNk<^M^'X^McYmDMK@H>#)4Z;%*=vV%GqlA#S&uZ-lP^e$M7OX(\"\n        \"MD_e$g-PX(kkW_&kF`e$#r`e$).ae$*1ae$8?fw9S&Z_&,5.:2klZ_&kGce$lJce$xiPe$+\"\n        \"0`EecowLpO#[e$<YNX(r<=-mW0BSIF;#sIl5:mps?x(3QJDX(E;#sIo812qV:@k=Bfrl&\"\n        \"=P22qe>^e$7;hl/\"\n        \"eE.F.E;#sIOiuoJIu22qBkCL,H7ee$I4Re$<)_V$&gLMq_,^e$Ljt?0HMvo%Q]nIqQ'GG)=\"\n        \"K@&GF4Me$Z.KR*I9be$XYRX(+2t.CeEKGNMZ')X56uxY&qpuZ'$6;[\"\n        \"5M^f`0c*5g[]QMq,Cee$lcNR*olNR*o#__&p#U_&.(L-Mb&QJ(Vvxc3_iQ>6gEKGN`N(&\"\n        \"PqJhiqhfC_&Eu72'=?.&G16ciqU*LS-KB-5.jT#TM]vHTM3*/YM/)YZM?A([M;M:[MHk<^M\"\n        \"UqE^MK'X^ML-b^MUanI-3ce2.lp:*MgD5&#St9-MqAMG)_iQ>6[^)B#F&mlLIXFRMXusUMl%\"\n        \"'VMo7BVM$CdwLDVt<#ja;<#LIbGM*_uGMV*WvuZ6)=-/Gg;-<:)=-dwX?-EHg;-HZGs-\"\n        \"GEQ&Mx>)3vZUGs-H25)M3A([M:G1[MHk<^M*rE^MQ'X^MQ$FBM5DWY5GqlA#dlG<-wlG<-\"\n        \"xlG<-:mG<-;mG<-#nG<-$$)t-`p3$Mn86>#3Ig;-4Ig;-ig%Y-CON_&oWOOM/x.qLtBQY#\"\n        \";mG<-=mG<-]Hg;-v)`5/\"\n        \"clVW#JICXMCbE4#Fx(t-M6UhLbVv`MbSdDM,hji0;LlA#TlG<-qlG<-6j&gLJ;-##'9BoLL,\"\n        \"[TMI^#pLq6mWMG8x#/)J:;$$(Re$-2;ul&+?v$/FZ;%0OvV%\"\n        \"O?2B#lf.;6?KS5'0tnP'OKhJD:^CX(Zw;F.35hl/\"\n        \"Epw]Gf@Qe$E-be$F0be$Jbx(3XRRMLk6$B#Zu-:2RTbe$TZbe$vs<F.+'`9M,=r:Qc2N;\"\n        \"R0Is92loZ_&h>ce$80$@KmMce$ZPgw9\"\n        \"pVce$'rSX(_]gw9tcce$ufce$vice$wlce$xoce$fmTwT,R7_])/\"\n        \"de$<*MR*1l[_&2o[_&3r[_&4u[_&n?g=cXPJM_8Dbi_bs./\"\n        \"`L7CJ`Ar^f`JZuofZ%(F.I:ee$K@ee$LCee$vM7]X\"\n        \"hbP`kcTr%lZFL]l0$jxl]X->mo^&8otuGSov1)5ps,>Pp*ZYlpipt1q.)r.r(`bxu@W)R<\"\n        \"h7H>#aSU_&X.o34OY1vu<6)=-LW=Z-f>^e$1E^e$+W7kX2C-IM=W@IM8^IIMbjJ%#s>:@-\"\n        \"c@On-X^q-65YZq;H5_e$cb%LGJ)^KMN7hKMT>qKMdI-LM3P6LMSU?LMT[HLMDodLMdumLMf$\"\n        \"wLMa**MMb03MM4AO%vjU]F-j:)=-_Gg;-`Gg;-#.A>-2rUH-mgDE-+)>G-kGg;-4Rx>-\"\n        \"mGg;-#6&F-ulG<-Jjq@-jt,D-(;)=-`]3B-c]3B-_8RA-)Hg;-<``=-?``=-SwX?-2ZGs-F&\"\n        \"mlLh;oQM9@xQM'G+RM(M4RMBR=RM7XFRMV_ORMUgXRMFkbRMiQhSMg-J0#%V3B-LHg;-\"\n        \"X6&F-pRx>-RHg;-SHg;-69RA-VHg;-d;)=-YHg;-ZHg;-[Hg;-h6&F-]CdD-Q,kB-mhDE-$\"\n        \"VYO-kHg;-(a`=-)a`=-Hkq@-oHg;-P9RA-wmG<-X^3B-3%;P-*<)=-uHg;-vHg;-a^3B-\"\n        \",nG<-J%uZ-KGPq;?8[(WsS<^#hIde$Vbee$/\"\n        \"aW?gt_ii0)lkA#]Gg;-kGg;-qGg;-)Hg;-4Hg;-kHg;-#6@m/\"\n        \"Me60#O8>##-WjY.(1A5#XfG<-arN+.f/tZM3>l?Mb]ID*5:lA#TGg;-\"\n        \"i:)=-^Gg;-$;)=-wlG<-rGg;-$Vg_-q/Y_&5Rae$CpQX(]sbe$pVce$ax]#$k/3L,UFjl&/\"\n        \"kR5'=?.&GT]x>$X%H'Smm9SIF;#sIIVuoJ1Gt>$HK<F.]fRX(RTbe$Y($_]8bn7R<4D;$\"\n        \"IDce$]HvOfqO-/\"\n        \"VZdJJVxw;,Wh9BX(33EL,Qt'44-M1ci<Vbi_4DBJ`5M^f`aAdofNh+5gKj_lgLs$2h]X->m>\"\n        \")CSo[Iw1q)lkA#LJU[-e+EX(%JbGM/XlGM/UY,MIQ#v,J_1p/_QiP0\"\n        \"`Z.m0L+pA#Z:)=-t.Yc->u_e$k9PX(9[+:2843@0973@032BL,r[`e$;JBL,@YBL,A]BL,\"\n        \"6HQX(L(CL,5Rae$7<fw9jN<F.R#Z_&lT<F.`JZ_&klZ_&,tDL,Qgn'8xbSX(SHv-6tcce$\"\n        \".+BX(I+p=YUf=Z$gCZe$Y.;MMqYGOM4F+RMqT)xLPXZ##pHg;-ERqw-?35)MUYZ##bIg;-\"\n        \"oBDX-Em7L,'#N^,tq(/:/GdV@34O2C4F=X(7Xae$q25@0cSZ_&-wDL,t+I_&P]:;$w;S<%\"\n        \"a1;ul1oqr$/FZ;%0OvV%Uv*<%lf.;6?KS5'0tnP'Dr%ktg$E]F[D/\"\n        \"&G(i#^G.P,F.E-be$F0be$Jbx(3XRRMLqmr;%iP6'o'AFcMTeJGNW*GDOxA)&PRhq;%=vbe$\"\n        \"4qDo[8(qoSnxOPT\"\n        \"k+LMU:S1/\"\n        \"VnFHJV]v+,WqbDGW(:acWaDCDXu0]`Xv9x%YwB=AYxKX]Y$_9>ZjGVV[-Dsr[*?28]=)NS]\"\n        \"2dio]3m.5^4vIP^8>=2_C(b9M7(]_&aAY3kK&FL,@hTX(5Sde$Rb5;6f])5g\"\n        \"Kj_lgLs$2hX.GDkdQ:R*bl1X_Xhee$/\"\n        \"?1:2[qee$i5VX(sn*Ratq*RarPVX()lGL,h?fe$+rGL,r,__&9C?L,uxn+MqIdY#/\"\n        \"FZ;%6tVW%;HF8%,E/F.,uKHM13`HM3<`-MU:2,)=p,g)\"\n        \"8gG,*;&vG*RUn92bqQ9i>6EJM#EOJM;j0KMKx90M7iji0N-I21TFhM1d)F/\"\n        \"23JcJ2SZ&g2TdA,3DF@)4dT]D4f`u`4aV:&5b`UA5ds0^5IT*REj6PX(_x_e$`%`e$#WAL,\"\n        \"2sv?KmlE_A\"\n        \"+k/\"\n        \"LGkF`e$4Y:F.mL`e$#`>XCu3X_&J^$44jpTk=(tPX(`:l'8cCl'8_is-6).ae$<)JR*?2JR*\"\n        \"S04@00Cae$;q1;6f'^MC9LtiC's;/D(&WJDB$qfD7_5,EVmQGEUrscEFH2)Fi@C;I\"\n        \"@NkPKZ/\"\n        \"W'8LBbe$XT@XCpa<F.RTbe$SWbe$6Fu-6Vabe$d%SX(Yjbe$Zmbe$[pbe$h,AXC]nOe?\"\n        \"Q5gw9mmH_A$-(RakGce$(CLR*)FLR*HX'44oSce$P?v-6w:[_&X&o'83)wKc*%TX(\"\n        \"ufce$vice$a>o'8,][_&Ic$@0Q`:;$4j8W%^)^e$'r8e?YRjl&x28W%e>^e$B:GR*.?Kq;\"\n        \"uGQlJ5l5W%<4KR*u>5@0RTbe$AOAwT/.ZxO#55W%=vbe$f8ce$h>ce$*cvKGA&DfUC0x/V\"\n        \"(kaS%uAEo[pe;,W=sOe$WG&&+uB=AY4'il^1)FM_7Bei_uU,/\"\n        \"`WYFJ`TscofIW(5gKj_lgLs$2hW+P`kdVo%liRDSow3&5pvG:Mqk&exu)lkA#5e%Y-aSU_&/\"\n        \"dU_&-P(_])`t,M3bTM'\"\n        \"GqlA#6Gg;-S_`=-gvX?-HGg;-jQx>-Ph]j-1e)_]J&K0M&&KJ1i2+j1uIr]5wTqA#=9kf-,\"\n        \"Sk'8*mAL,+pAL,J,s-6K/s-6?I3@0@L3@0Gb3@0Jk3@0Kn3@0Lq3@0Mt3@0B`BL,EiBL,\"\n        \"FlBL,k7t-6l:t-6VxJR*h>nEe,#c-Qxl-:2u>5@0ufnEeqLSX(6/\"\n        \"6@0%:LR*osH_ApvH_A.$EL,/\"\n        \"'EL,rWPe?sZPe?6nLR*M=s92vJQlJ0_Ds%r,d%.6E;UM(bVXMtmiXMusrXMv#&YM\"\n        \"umMxLklv##(nG<-*Ig;-,Ig;-5*`5/qM)W#l/\"\n        \"tZMRG1[Mxw=9#T4)=-LIg;-#Tx>-[Ig;-gIg;-hIg;-jIg;-lO,W-e;^e$0B^e$1E^e$6T^\"\n        \"e$7W^e$8Z^e$QP_e$RS_e$SV_e$Xf_e$\"\n        \"#ttEIY+)2MOaSV6xW_M:m<$j:oNZJ;qa;,<:pWG<Mq]PB<CxlB7Lt2CAbwo%o)Y_&R_;F.\"\n        \"Sb;F.BmQX(O1CL,?\\?Y_&QQbe$RTbe$SWbe$Xgbe$Zmbe$[pbe$1D.:2t1[_&oSce$&oSX(\"\n        \"w:[_&x8I_A#<I_A+(TX(vice$$pPe$aL<5&)v>5&G:`e$%x`e$+4ae$OKbe$e/\"\n        \"Pe$Ti:;$Q&0T&^)^e$0B^e$M_5_A0%$AO6dQ?ZM<XP&&cxOf8=[S%/\"\n        \"(lA#E@DX-(VV_&Z:W_&kkW_&\"\n        \"t0X_&)RX_&*1ae$s?[w9cY3wp'&s.L^EaEe>K9PSrFhiUQw`EeS_u:Z3$Ap&(v%'.kkBHM5'\"\n        \"MHMlE2/#Xo:$#EHg;-FHg;-5.E6.o/tZMak<^MJt<BM/DWY5qa;,<4C9/Dst%)X-kR5'\"\n        \"O8t]l>k/\"\n        \"2'`]g5'O;O-QrQAYG>e=X(E-be$F0be$)ch5'_+.:2.P1ciK@_f`mfdof&Palgk,Uiq[LZ5'\"\n        \"Um0L,Z122M[Nx(<GqlA#4Hg;-f;)=-#$)t-d.xfL'4N$#)Gg;-2<:H/:Yu##\"\n        \"H%UHM(;N$#]crmLNj6TM[p?TML,[TM-?f0#AY`=-/kq@-/\"\n        \"a`=-#Ig;-&Ig;-5tA,M>5N$#Mpx/\"\n        \".5x`^MY&-`Mm&h(M&5N$#onG<-qtcW-nxL_&07qHMVhZLM,qx/.>TMUM2-:)0:V1vu\"\n        \"q-V$#]kK4.oqKHM=wXrLR:W$#EHg;-HZGs-<Pc)Mgk<^Mbkw&M0'2hL7WZ)M0llgLl<a$#\"\n        \"Coh2(BFKR*RTbe$.ULR*tcce$ufce$vice$xoce$*2de$,8de$.>de$0Dde$H7ee$K@ee$\"\n        \"Znee$[qee$g<fe$h?fe$kE]e$+oBHMK$rd-n%V_&5Q^e$7W^e$8Z^e$:a^e$QP_e$RS_e$\"\n        \"SV_e$Xf_e$Zl_e$[o_e$`%`e$a(`e$lI`e$mL`e$oR`e$pU`e$/@ae$0Cae$2Iae$3Lae$\"\n        \"6Uae$9_ae$:bae$QQbe$RTbe$SWbe$Wdbe$Zmbe$[pbe$^vbe$nPce$oSce$qYce$r]ce$\"\n        \"ufce$vice$&&de$%sPe$$_AYGhYQJ(%-be$E*Xe$Z,]-#T@.@#7Hg;-]Hg;-xGwM0=V1vu\"\n        \"o?r$#5+W'M/'MHMh92/\"\n        \"#NfG<-EHg;-FHg;-nwX?-c.A>-HIg;-ICg;-h13Y.pE%%#n$KZ.Plk.#H:#-MRL.IM$P&%#\"\n        \"SZb/)BkCL,8fH'SiD>f_f3`f`mfdof^J`lgk,Uiq:ha/)TD8L,\"\n        \"Z122M[Nx(<:U9/\"\n        \"DY<(&P)C&)X<s#K))at?0aI78%*=vV%&>MK)i].;6:<u?KwTaxFLkE_&Z.KR*I9be$XYRX(+\"\n        \"2t.CeEKGNMZ')X56uxY&qpuZ'$6;[q8M'S-nZ1gV=v?0LCee$lcNR*\"\n        \"(Pj?KJ0Q.qq>Uiq)lkA#8rcW-jJ^e$V`_e$;o)K);;Z_&6fDg)0:H9ig$kl&/\"\n        \"kR5'?Qe]GO]?g)%-be$F0be$[urRnHA#J_B-Se$4Pde$m'8@0J:[e$Y+)2MPW=D<5LTJDt'\"\n        \"ADX8sY,*\"\n        \"[#^e$(*^e$(6+cix]*XCc*Se$;VNX(0B^e$2k1kX%nAYGNd6R*E-be$F0be$ckCL,2Jde$\"\n        \"H7ee$K@ee$Zd[Y,vxVY5)lkA#qGg;-4Hg;-sTGs--`ErL*RrhLql[C#U#5@Mnar/#GMYSJ\"\n        \"[7voJU''H*mvO_&4PERM]7BVMnBdwL:j[IMMoS%#%>.d*w-+d*x34d*_bN^-NGT-Q5cf+M;\"\n        \"oS%#ki?d*#Dn34iO;;$weS#-^)^e$QBZ9M,jOw94[)v,v>Ve$B:GR*Cv>ulS%voJh2D#-\"\n        \"27@R*u>5@0RTbe$Ddfw9Up/\"\n        \"ci$g@>QfSooShfOPTm7_MUx8;R*:;6@06:5]XteZJV[xPe$%9k7RqnVGWoVKe$&EI_A/\"\n        \"Ade$,L(GM`E23vLZ`=-VsUH-@<)=-HIg;-IIg;-KIg;-LIg;-\"\n        \"c7&F-^iDE-u[]F-r<)=-]QKC-(ucW-kb=R*w1=GM/XlGM/\"\n        \"UY,MYGXP&S?mA#T&uZ-mS^e$SnGR*gi1@0H5_e$jM9F.Ob)_]IvA0M%s//\"\n        \"1'hqA#h-A>-i-A>-ZC&j-sNV-Qb_.NMN$KNM\"\n        \"+*TNM,0^NMK6gNMQZGOM@aPOMG5;PMJGVPMKM`PMLSiPMMYrPMN`%QMEr@QMFxIQMLF+\"\n        \"RMlL4RM%RhSM^&RTMi.[TM-4eTM%E*UM'vsUM+k5WMt$QWM7+ZWM&1dWMp7mWMq=vWM/C)XM\"\n        \"5h`XMsniXM#=JYM50sxL?KG&#1Ig;-3Ig;-4Ig;-S>dD-)qDm.2HbA#*S%:.[fgl/\"\n        \"9_TJD=q5,Ecj$#QpX),Wt'ADXUb0Z-vCDk=>(g+MWRP&#_;)=-,0#dM7trXM&$&\"\n        \"YMumMxLqQP&#\"\n        \"(nG<-*Ig;-,Ig;-5*`5/\"\n        \"qM)W#.0tZMRG1[MM_e&MwQP&#LIg;-#Tx>-[Ig;-gIg;-hIg;-jIg;-lO,W-e;^e$0B^e$\"\n        \"1E^e$6T^e$7W^e$8Z^e$QP_e$RS_e$SV_e$Xf_e$#ttEIRxc9M\"\n        \"]@VMMx;pNMmA#OMoM5OMqYGOM9W54MCZBMB<CxlB7Lt2CY:YV-o)Y_&R_;F.Sb;F.BmQX(\"\n        \"O1CL,?\\?Y_&QQbe$RTbe$SWbe$Xgbe$Zmbe$[pbe$1D.:2t1[_&oSce$&oSX(w:[_&x8I_A\"\n        \"#<I_A+(TX(vice$$pPe$#@=5&ANwr-G:`e$%x`e$+4ae$OKbe$e/\"\n        \"Pe$vQkl&-Q.W.e>^e$)gd=cfABYG'?.W.%-be$F0be$I_x(3Z@;5KoE4/\"\n        \"M_wjfMY6YDO0_5R*>SP-Q[id.Um7_MU\"\n        \"0?CX(YMgw9t1[_&CHEo[%1EGW96>AYdqYM_:^CX(2Jde$4Pde$M,FL,MfJ_AI:ee$K@ee$\"\n        \"LCee$7&*44Vjbq;nsT_&tre+Mv@H>#/(lA#.lG<-/lG<-`8kf-xu^e$]3HR*_9HR*v]e9M\"\n        \"K/gKMh-w1MGNsu5_iQ>6qnJ88>K)m99BD2:?#=,<4^WG<#Tk]>@uH;@A(eV@B1*s@@h9/\"\n        \"DM?UJDJ`:5K80XPKkwrlKS772LxA)&Pk]SSSp412U-uLMUR?jiU)C&)XTvBDX$_9>ZStGs.\"\n        \"-WF3k%&?f_#YkA#]Gg;-qGg;-4Hg;-sTGs-';OGM(RcGM.wCHM/\"\n        \"'MHM0-VHM3:SqL2q('#P+d%.l+lWMOX<0v-d''#&<)=-4nG<-Za`=-KIg;-dO,W-Yp]e$)-^\"\n        \"e$Mp'@0+l0-M,92,)\"\n        \"Ac<#-YH.m0ZD:&5qnJ88q<C2:#Tk]>)5dV@*>)s@KiUPK:B92L^<GDO9uTSSq=LMUl4hiU$_\"\n        \"9>ZIb(T/oM*eZ4KBYGt5*T/%-be$F0be$EYhl/D[xCWtfKe$e)VX(ga^_&a']e$K/gKM\"\n        \"0+J6M@Lr(ELrqlKi&%#Qvk),WH_1p/OqwoohYj'8V$#SnnTarnc9&8oo,Ep/\"\n        \"%OAk=7;cw9b-*/:/GdV@1(=2Cfb5p/D]Op/&Cp+Mm/VB#i;)=--/\"\n        \"A>-v#)t-()ofL$,D'#(Gg;-0Gg;-\"\n        \"IOD&.[diUM(*jxL)?D'#'cET#.MX,Mn]ID*/\"\n        \"(lA#GlG<-ZlG<-klG<-tlG<-)mG<-8sA,MlkkjLW@VhL.3eTMV%43.j`4WMr<vWMVnK4.'\"\n        \"xhxLPotjL-l,Q#,AXGMUXvuu.FZ;%60oP'\"\n        \"=kgJD<V4L,Bs>ul=Qe]GE2^VIF;#sIIVuoJWrwW_HCg+M_wjfMTeJGNW*GDOI=P;RF)e-\"\n        \"6A3F3kln=`Wv?J_&tcce$ufce$vice$8=3LG(P[_&_jv-6)/de$6ITX(1l[_&2o[_&3r[_&\"\n        \"KE,FI`H(44,J1ci)tdi_9M'/\"\n        \"`5M^f`w3HDkEL9R*e)VX(;Vx-6[qee$=3n3Oms^_&h?fe$2U@F.EP*44w]MX(uxn+Mpw@8%*\"\n        \"=vV%9HrA#@_`=-LR:d-:cv92.+_HM?<`-M892,)H5hJ)\"\n        \"I>-g)JGH,*:#)d*etpA#(]3B-^:)=-RGg;-SGg;-88RA-_lG<-`lG<-G+kB-6jq@-OOKC-q_\"\n        \"`=-(Rx>-s_`=-WOKC-x:)=-mGg;-oGg;-2.A>-Kjq@-ARx>-*Hg;-G.A>-6)`5/Q7OA#\"\n        \"82nQM9@xQM_F+RMaR=RMCXFRMP_ORMPfXRMFkbRM<-J0#ecq@-@PKC-d``=-RHg;-SHg;-*\"\n        \"F:@-VHg;-hUYO-Qu,D-f;)=-#Sx>-[Hg;-bhDE-Vu,D-WPKC-lHg;-0/A>-oHg;-8Sx>-\"\n        \"wmG<-@xX?-fPKC-+<)=-vHg;-Tkq@-,nG<-E`Xv-*)ofL&8V'#>4u(..GbGM/\"\n        \"XlGM6-VHMhbG*v`+U'#awX?-1W)..KLpSMF^$TMIp?TM/\"\n        \"'A0#i^Xv-%T:xLQJ3UM'Q<UMYVEUMZ]NUM\"\n        \"N#(.vv(A>-<qk0M8@V'#+n4wLrn>WMt$QWMj0dWMZ>e3#FX`=-j&a..$chXMusrXMv#&YMw)\"\n        \"/YMk08YM.ToYM/ZxYM*a+ZM7g4ZM2m=ZM3sFZM4#PZM+4m2v[p,D-<<)=-B[]F-8nG<-\"\n        \"9nG<-4Ig;-Z/\"\n        \"7*.N'trL&qp_Mev#`MZ,6`MN3?`Mdc2aMPk;aMXpDaM)vMaM$&WaMj1jaM'8saMl=&bM-(#\"\n        \"GMi7-##xVnA#Y=n]-g4NX(4ANX(5DNX(9he7R]mtl&M-mA#-C&j-LZ`'8\"\n        \".+_HM?<`-MU:2,)H5hJ)I>-g)JGH,*:#)d*YU=#-)wY>-GJS8/J_1p/TFhM1WZE/\"\n        \"2wibJ2SZ&g2W)>)43oZD4`Mu`4aV:&5a^XA5ciq]5^`6#6?bS>6L9pY6/\"\n        \"=4v6w*K88#DJ59.'`M:\"\n        \")t$j:oNZJ;pWvf;9g<,<rjVG<AYl]>PCiY?0P)s@3l%pA5(]PBZH#mB7Lt2C6dki0o)Y_&\"\n        \"j4t-64Oae$YB4@0HMJR*Uh;F.JSJR*EvQX(@BY_&c9<F.BKDkX-UGG)8B92L]lQJMqWkfM\"\n        \"S[/\"\n        \",NUnfcN+K-)OW*GDOEX'aOmSu92:Ru-6#'=F.[pbe$nc9RE,g5@0Wl_q;sMAXCr+[_&s.[_&\"\n        \"<A6@0oSce$D(/:2w:[_&Le'44s`ce$BS6@01_LR*vice$a>o'8,][_&C,,F.&x;;$\"\n        \"og//\"\n        \"1^)^e$0B^e$^KcKcqKq=Y:Ta21gFde$2Jde$4Pde$biFL,K=[e$X%v1M<Msu5qa;,<rjVG<\"\n        \"1(=2CfvmA#:mG<-5Hg;-C;)=-]Hg;-pHg;-/a`=-tTGs-.;OGM(RcGM.wCHM/'MHM\"\n        \"0-VHM3:SqL3Ei'#tTR2.s+lWMOX<0v48h'#&<)=-4nG<-Za`=-KIg;-dO,W-Yp]e$)-^e$\"\n        \"Mp'@0+l0-M,92,)Ac<#-YH.m0ZD:&5qnJ88q<C2:#Tk]>)5dV@*>)s@KiUPK:B92L^<GDO\"\n        \"9uTSSq=LMUl4hiU$_9>ZN?*j1oLCj10Dm92,-LM'J`:5K_r0j1w@2poTmm:ZMW&k1&cxOf[\"\n        \"Q]S%;LlA#E@DX-(VV_&Z:W_&kkW_&t0X_&)RX_&*1ae$s?[w94PERMM2eTMi7BVMkh5WM\"\n        \"r<vWMpTDXM.*jxL*P%(#x$a..8lBHM%YU10p(1/\"\n        \"#&B$(#EHg;-HZGs-a15)M=5lZM_G1[MHk<^Mut<BM0Msu5/\"\n        \"(lA#rGg;-5Hg;-rBg;-QND&.o7TkL%Eg;-IeY-MQP6LM$W.(#EOD&.\"\n        \"SXgsLbV.(#J/\"\n        \"A>-HIg;-KO,W-=r_e$qX`e$4Oae$qSPe$%1<;$(+?v$.UDW%.uic2Yq2g2(v%'.<xTHMLe=\"\n        \"rL*F_kLs2nlL/[i/#E;#sIJ`:5K_Z,RENXg+M6Z7(#j^ag24d[Y,K'/LG\"\n        \"O_p+M=dRC#kQ3g2;7u(.H`ErLEJhkLql[C#kTS?Mnar/\"\n        \"#GMYSJ[7voJk_Z,3mvO_&4PERM]7BVMnBdwLVhZLM1iZLM/\"\n        \"V$lLin[C##qX?->Ag;-N5u(.m/AVMnBdwL.+](#e-QV#rArP-\"\n        \"OL,W-dj]e$<]<e?6?`-6TF=X(LgY_&n)5@0ckCL,H7ee$I4Re$*@<;$+esD4^)^e$*0^e$@\"\n        \"4GR*F[nD4rXNX(KE2;6*:Iq;E;#sIR(M5K0?CX(LBpEI8Y?f_CTK_&9.]_&4Pde$_NoD4\"\n        \"/q]_&X?(2h)lkA#+'uZ-DUW_&w9X_&x<X_&:0Y_&;3Y_&#A[_&x7I_&CT>wT?]a]4.\"\n        \"9Z9MI57qV.FZ;%76xP''lG<-b5kj.x(1/\"\n        \"#RKx>-EHg;-FHg;-*9RA-swX?-VmG<-RHg;-(<)=-\"\n        \"tHg;-uHg;-vHg;-xHg;-(Ig;-)Ig;-*Ig;-+Ig;-,Ig;--Ig;-4iDE-anG<-[Ig;-gIg;-\"\n        \"hIg;-,0A>-rtcW-e;^e$;SEX(.+_HM:E%IM6Q7IMCW@IM>^IIMQI-LMRO6LMSU?LMWndLM\"\n        \"XtmLMY$wLM`HWMMl;pNMmA#OMoM5OM/\"\n        \"(SQM0.]QM2:oQM6R=RM8_ORM9eXRMQJ3UMRP<UMSVEUMUcWUMViaUMXusUM:-\"\n        \"0VMb19VMnH2XMoN;XMqZMXMusrXMv#&YMTdp1vI%w(#?h](M\"\n        \"/'MHM:e=rLl7x(#J?gkLQWqSMF^$TMJvHTMm]NUM/E23vg)A>-YxX?-Za`=-Q*`5/\"\n        \"59OA#p%v1MIV8;6;LlA#wlG<-4Hg;-f;)=-wgG<-uFU`.J+*)#vHKC-ncxb-KW+REVqg+M?\"\n        \"8+)#\"\n        \"]``=-HIg;-KIg;-Q',s/\"\n        \"b>cuuo03)#(Gg;-0Gg;-R(A>-AV3B-FX`=-dRx>-FHg;-Z``=-IHg;-JHg;-PHg;-RHg;-T]\"\n        \"xf.WYi0v.fq@-tHg;-uHg;-vHg;-xHg;-*Ig;-,Ig;-_kq@-\"\n        \"anG<-[Ig;-gIg;-hIg;-lO,W-e;^e$*w4^5n%V_&eRo344O?IM>^IIMQI-LMRO6LMSU?\"\n        \"LMXtmLMZ**MM.IWMMr;pNMmA#OMoM5OM/(SQM0.]QM2:oQM6R=RM9eXRMQJ3UMRP<UMSVEUM\"\n        \"WojUMM,0VMb19VMnH2XMoN;XMqZMXMhvdT-e)*q-g0BX(5O<;$ptxu5^)^e$0B^e$;^>\"\n        \"ulIp@&GoVKe$w0GG)C2^VIF;#sIHMYSJi8L#6,Bbe$#K5@0r]ce$tcce$ufce$vice$xoce$\"\n        \"*2de$,8de$l`o'829h=cDhBMhZFL]ln0ixlg^=PphgXlpj#:Mq9DViql5q.r/\"\n        \"(lA#.Gg;-<@DX-n%V_&<5V_&=8V_&8Z^e$QP_e$RS_e$SV_e$Xf_e$`%`e$lI`e$mL`e$oR`\"\n        \"e$/@ae$\"\n        \"0Cae$2Iae$6Uae$9_ae$QQbe$RTbe$SWbe$p72LGaMZ_&[pbe$nPce$oSce$qYce$ufce$\"\n        \"vice$$pPe$O5CYG)M8;6%-be$F0be$4-Sw9]$h+MT%=R*RTbe$%5=R*1U<;$f:*Z6^)^e$\"\n        \"*0^e$.B+cioP12'0tnP'M,x]GH->X(E-be$F0be$<=uOf;MFM_si?Z6q,uRn7M'/\"\n        \"`:VBJ`HNcof*P*5gQ&`lgR/\"\n        \"%2h)lkA#+'uZ-DUW_&w9X_&x<X_&:0Y_&;3Y_&#A[_&x7I_&:Kll&\"\n        \",;7v6><toobQQ?gRMLe$@/\"\n        \"j+MF^$TMJvHTMak<^MK'X^Mi%<*Mlp'*#NFk3.EGbGMHiRQ-Q7K$.cNMmLiDg;-B_`=-Bt%'\"\n        \".eR^sLRm'*#,Wd88kaZ'8cHHcMW*GDOFY*&Povc88=vbe$\"\n        \"f8ce$h>ce$H>U+`]djiU=b@/VW:I_&ABEo[pe;,W=sOe$WG&&+uB=AY4'il^2/\"\n        \"OM_@_U).%`K%MEA([MWH1[MTk<^MIqE^MK'X^ML-b^MWpp_Mdw#`Mid2aMwpDaMv1jaMj(#\"\n        \"GMQ@H>#\"\n        \"5:lA#*lG<-/lG<-.h]j-=:o34,r9-M-BMG)Ac<#-TLX>-mdQ8/\"\n        \"KhL50kviP0'hqA#Qh]j-2h)_]L5pKMiC$LMt-w1M.Osu5oJZ?^If058alAL,+pAL,J,s-6K/\"\n        \"s-6?I3@0@L3@04b7RE\"\n        \"Jk3@0Kn3@0Lq3@0Mt3@0B`BL,EiBL,FlBL,k7t-6l:t-6VxJR*h>nEe,#c-Qxl-:2u>5@\"\n        \"0ufnEeqLSX(6/6@0%:LR*osH_ApvH_A.$EL,/'EL,rWPe?sZPe?6nLR*M=s92ZOSlJkkoS8\"\n        \"r,d%.qE;UM(bVXMtmiXMusrXMv#&YMumMxLOu0*#(nG<-*Ig;-,Ig;-5*`5/\"\n        \"qM)W#P0tZMRG1[Mxw=9#T4)=-LIg;-#Tx>-[Ig;-gIg;-hIg;-jIg;-lO,W-e;^e$0B^e$\"\n        \"1E^e$6T^e$\"\n        \"7W^e$8Z^e$QP_e$RS_e$SV_e$Xf_e$#ttEIY+)2MOaSV6xW_M:m<$j:oNZJ;qa;,<:pWG<\"\n        \"Mq]PB<CxlB7Lt2C&oKP8o)Y_&R_;F.Sb;F.BmQX(O1CL,?\\?Y_&QQbe$RTbe$SWbe$Xgbe$\"\n        \"Zmbe$[pbe$1D.:2t1[_&oSce$&oSX(w:[_&x8I_A#<I_A+(TX(vice$$pPe$9n<;$(+?v$.\"\n        \"b7p&/kR5'0tnP'5LTJDZ]<691j/ci]*+JU#c-/VZJ[59[nSX(4u[_&Z/NR*K@ee$c-]e$\"\n        \"v+4GM)XlGM)UY,M&X92'GqlA#5Gg;-M:)=-MGg;-g:)=-klG<-kGg;-#Hg;-)Hg;-*Hg;-8,\"\n        \"kB-SmG<-,F:@-kmG<-kHg;-lHg;-xBg;-1ZR2.G5+gLf0L*#0Gg;-XOYO-'0RI/W$K*#\"\n        \"qrZiL-*%7v:-gE#B3]Y-%oEX(>6EJMZ[HLMkg/\"\n        \"NMtG,OM)5;PM0`%QM0+J6MLLr(EM%72Li&%#Qk]SSSrFhiUpX),W0-:>Zn3(m9Y)%W[q&XV$\"\n        \"/CHv$'lG<-/lG<-8()t-2EQ&MRe=rL\"\n        \"X8U*#?Hg;-EHg;-FHg;-pXr.MF8U*#];)=-RHg;-THg;-@pj#.IxE$MO%6wLU=U*#e_K%M$\"\n        \"niXMusrXMv#&YMw)/YM918YM.ToYM`ZxYM*a+ZM7g4ZM2m=ZM3sFZM4#PZML*YZM39v2v\"\n        \"Gp,D-8nG<-3Ig;-UfXv-cJ)uLkv#`Mg,6`M<3?`Mdc2aM@wMaMn%WaMj1jaM38saME5aEMW@\"\n        \"H>#A_lA#)Gg;-@)Vl-w3GR*K@sEI,r9-M.kpi'@g0j(S?mA#G_`=-H_`=-I_`=-8Gg;-\"\n        \"&,-h-_>j'8^hOX(RS_e$SV_e$8Kr-6_FW_&`IW_&Glcw96w#44OR[q;qpHR*(5:F.svHR*\"\n        \"Wk[q;xaPX(mL`e$oR`e$2/BL,Ka$44A+;F.*1ae$GoBL,0Cae$waVY,'<@JC9LtiC_m:/D\"\n        \"a)rfDC-6,EPZQGEPbpcEFH2)Fl$jPKDOf34@&_q;dIKR*RTbe$SWbe$*/\"\n        \".:2Vabe$hK'RaQ(Wk=f+SX(#'=F.[pbe$bKH_AV7Wk=Wl_q;lJce$0*EL,oSce$8g=F.w:[_\"\n        \"&@M6@0f@`q;\"\n        \"+(TX(vice$T'(44,][_&C,,F.<w<;$qHU2:r@qKG>3tr$/FZ;%60oP'A-6KD'?//:@W4@0/\"\n        \"=9qV[:<SIF;#sIIVuoJ_LM5KF>'F.p:4]XOINJM''lfMYn/,NZwJGN(NZDOUK2L,.;.:2\"\n        \".2o2:Sf$JUpxooSt4PPTjx02U4;cMU&@5R*h`3kX40v@Xu0]`Xv9x%YwB=AYk%[]Y.?QV[/\"\n        \"Hmr[*?28]7mMS]2dio]3m.5^4vIP^ZJ&m^;DHk=<[TX(B7;RE8+]_&9.]_&4Pde$X;%@K\"\n        \"C4<VH$CQ`keXl%lZFL]lN)kxldBASoPgbooX.%5p)Q>Pp$HYlpj#:Mq'dUiql5q.r.rbxuA_\"\n        \"lA#wG)a-9Rg-6w1=GM4RcGM5XlGMct2vuU8#F#XJU[-c(0eZ,r9-M@kpi'@g0j(qBqA#\"\n        \"G_`=-H_`=-I_`=-8Gg;-R-A>-(]3B-ABdD-HGg;-OgDE-UlG<-vvX?-RGg;-SGg;-2jq@-_\"\n        \"lG<-`lG<-`gDE-blG<-]Gg;->8RA-K+kB-.wX?-s_`=-vY]F-*.A>-(``=-mGg;-oGg;-\"\n        \"8Rx>-qGg;-:Rx>-ME:@-,mG<-0mG<-3mG<-YE:@-6)`5/\"\n        \"pAaD#V2nQM9@xQMkF+RM5L4RMZR=RMIXFRMV_ORMKeXRMFkbRMJQhSMivHTMl.J0#w$kB-\"\n        \"X6&F-pRx>-RHg;-SHg;-*F:@-\"\n        \"VHg;-D4E6.tOc)M.&'VM;,0VM$29VM]7BVMo>KVM3i5WMZ%QWMw=vWMsB)XMtH2XM=O;\"\n        \"XMpTDXME[MXMxaVXMMh`XMtmiXMCtrXM2$&YM$<JYMcH]YM+</#M]Bh*#M'7*.RlBHM/'MHM\"\n        \"lE2/\"\n        \"#@4g*#EHg;-HZGs-a15)M;#5$M<Dh*#4Ig;-mxX?-KO,W-I0GX(oWOOM5L4RMrZ2xL?Hq*#&\"\n        \"Gg;-(Gg;-W^nI-$T=_.[<p*#;:)=-2YGs-)5UhLIwXrL%2BnLt1nlLL^$TMPvHTM\"\n        \"J;uZMHk<^MK'X^MAJk<#8C5F#crcW-QX`e$4Oae$qSPe$VPcxFeC#0;s?x(3?$Jq;E;#sIN`\"\n        \"YSJ[7voJ4SX/;mvO_&4PERM]7BVMnBdwLoM5OM>S-+#nVsJ;PYGq;k6)eZlfGG;1tYe$\"\n        \"lJaJ;[gT>6OV^VI`,jJ;s9Af.BoZ,vRH`t-Bj](MZD23v^Cg;-9nG<-5R,W-^Tp-6Oq]_&\"\n        \"Qw]_&QtS_&g>%@0Z4DMMvP,4MuW=D<:U9/D;_TJD#1&)X$:ADXtjvf;><too(IU3O>Qe]G\"\n        \":l'g;H:(g;&6F,3Z@;5Kn`8g;(7ee$K@ee$5sW?g;-R?gOs')<U5[e$+GxFM:e=rL[a?+#W.\"\n        \"A>-EHg;-FHg;-Z``=-IHg;-JHg;-PHg;-RHg;-*`Nb.lYi0vFKKC-tHg;-uHg;-vHg;-\"\n        \"xHg;-*Ig;-,Ig;-_kq@-anG<-[Ig;-gIg;-hIg;-lO,W-e;^e$R)(@0.+_HM;K.IM=W@IM>^\"\n        \"IIMQI-LMRO6LMSU?LMXtmLMZ**MM.IWMMr;pNMmA#OMoM5OM/(SQM0.]QM2:oQM6R=RM\"\n        \"9eXRMQJ3UMRP<UMSVEUMWojUMM,0VMb19VMnH2XMoN;XMqZMXMhvdT-e)*q-g0BX(I6=;$.)\"\n        \"CD<^)^e$0B^e$;^>ulIp@&GoVKe$w0GG)C2^VIF;#sIHMYSJ'CmG<,Bbe$#K5@0r]ce$\"\n        \"tcce$ufce$vice$xoce$*2de$,8de$l`o'829h=cDhBMhZFL]ln0ixlg^=PphgXlpj#:\"\n        \"Mq9DViql5q.r/(lA#.Gg;-<@DX-n%V_&<5V_&=8V_&8Z^e$QP_e$RS_e$SV_e$Xf_e$`%`e$\"\n        \"lI`e$mL`e$oR`e$/\"\n        \"@ae$0Cae$2Iae$6Uae$9_ae$QQbe$RTbe$SWbe$p72LGaMZ_&[pbe$nPce$oSce$qYce$\"\n        \"ufce$vice$$pPe$PK=;$C8QY>^)^e$:@K1p4*Jp&$(Re$/?^e$B:GR*\"\n        \"bZ4@0Sd2;6Z@;5Kcw3/MwjkfMW*GDOBp=^>3p/\"\n        \"citS@>QfSooShfOPTm7_MUx8;R*:;6@06:5]XteZJV[xPe$%9k7RqnVGWoVKe$&EI_A/\"\n        \"Ade$6vI_ApEg=cCr'/`WYFJ`TscofIW(5g\"\n        \"Kj_lgLs$2hW+P`kdVo%liRDSow3&5pvG:Mqk&exu)lkA#5e%Y-aSU_&/\"\n        \"dU_&-P(_])`t,M3bTM'GqlA#6Gg;-S_`=-gvX?-HGg;-jQx>-Ph]j-1e)_]J&K0M&&KJ1i2+\"\n        \"j1uIr]5wTqA#\"\n        \"=9kf-,Sk'8*mAL,+pAL,J,s-6K/\"\n        \"s-6?I3@0@L3@0Gb3@0Jk3@0Kn3@0Lq3@0Mt3@0B`BL,EiBL,FlBL,k7t-6l:t-6VxJR*h>\"\n        \"nEe,#c-Qxl-:2u>5@0ufnEeqLSX(6/6@0%:LR*osH_A\"\n        \"pvH_A.$EL,/'EL,rWPe?sZPe?6nLR*M=s92o6TlJ)v9#?r,d%./\"\n        \"F;UM(bVXMtmiXMusrXMv#&YMumMxLdB<,#(nG<-*Ig;-,Ig;-5*`5/\"\n        \"qM)W#e0tZMRG1[Mxw=9#T4)=-LIg;-#Tx>-\"\n        \"[Ig;-gIg;-hIg;-jIg;-lO,W-e;^e$0B^e$1E^e$6T^e$7W^e$8Z^e$QP_e$RS_e$SV_e$\"\n        \"Xf_e$#ttEIY+)2MOaSV6xW_M:m<$j:oNZJ;qa;,<:pWG<Mq]PB<CxlB7Lt2C:#mu>o)Y_&\"\n        \"R_;F.Sb;F.BmQX(O1CL,?\\?Y_&QQbe$RTbe$SWbe$Xgbe$Zmbe$[pbe$1D.:2t1[_&oSce$&\"\n        \"oSX(w:[_&x8I_A#<I_A+(TX(vice$$pPe$Y8?5&x64;?G:`e$%x`e$+4ae$OKbe$e/Pe$\"\n        \"MT=;$J=%Z?^)^e$.<^e$/?^e$0B^e$D6&RE/\"\n        \"g&AOV3TV?V_SX(,ivKGA?_(W:2JP^Nacof^J`lgdBASo#YkA##Gg;-)Gg;-N&uZ-3)(@02C-\"\n        \"IMA>FJMY1_KMZ**MMqg/NMq5gNM#5;PM\"\n        \")YrPM*`%QMK&RTM:3eTM^ojUM9i5WMq6mWMl<vWMx)jxL'M`PMCO`PMlUW,#auQx-(YgsL5=\"\n        \")pLL1OW#*2A5#Dx_5/'cET#`MX,M$^ID*/(lA#GlG<-ZlG<-klG<-tlG<-)mG<-*Hg;-\"\n        \"t10_-nWae$Y]RX(cSZ_&klZ_&lJce$&oSX((JI_&UtU3OTu38@c]U_&#/xFM/\"\n        \"XlGM6-VHMdUG*v_;xu-Mi](M?3:SMEWqSMF^$TMIp?TMJ?Y0.1:)UM_P<UMT]NUMWojUMpY$\"\n        \"/vg3RA-\"\n        \".HU`.#Zi0v@hG<-tHg;-uHg;-vHg;-8*>G-(nG<-_9RA-)Ig;-6<)=-1nG<-2nG<-3nG<-\"\n        \"KNuG-b'Rx-7-W'M+<uZM9A([M5M:[MGt$7vfY`=-e<)=-;:RA-[Ig;-=gnI-mnG<-hIg;-\"\n        \"2Tx>-Elq@-xBDX-w#/\"\n        \"F.&PkGM)UY,MmPtl&AKS5'@]oA#ZJU[-t[NX(dOo342C-IMHQ7IMIW@IMJ^IIM9a@.MUQ#v,\"\n        \"8SG/2_vaJ2SZ&g2W)>)49+[D4`Mu`4aV:&5HkWA57=s]5QBT>6\"\n        \"rRnY6)+4v6w*K88_caM:#b$j:oNZJ;pWvf;3T<,<S:m]>HC*s@/\"\n        \"l[PBHhxlB7Lt2CHf.8@o)Y_&^s,:2_v,:2BmQX(O1CL,O,1LGEvQX(Xq;F.-UGG)&b82LD#\"\n        \"QJMe3kfMS[/,NUnfcN\"\n        \"+K-)OW*GDOifi`OR(+&PgjCAP$T`]P]W$#QcqB>Q^6VSS_djiUnFHJV1CefVpX),W9hEGWx'\"\n        \"acWA6')Xh`_`X,_x%Y$_9>ZVVruZ-66;[GFwV@`MC_&QgXV$@2$W@d`U_&/dU_&6#V_&\"\n        \"mHS+`E%H]Fdif]Gqn%W@%-be$F0be$[1KR*@]hl/oQF/\"\n        \"MQYPe$&#.:2X5Z_&Y8Z_&#K5@0cEj7Rp/\"\n        \")&PvjiKc7lq7RSRAX(rOSX(h>ce$1L+FIL^HG),LacWw*&W@Scce$ufce$vice$\"\n        \"jL`q;(P[_&.c[_&)/de$6ITX(1l[_&2o[_&3r[_&W]s9M?QQ1p'b-2_=MFM_Cgei_9M'/\"\n        \"`:VBJ`5M^f`&DKDkC^x?0d&VX(Xhee$MNbq;[qee$O+WwTWmbq;(iGL,#5OR*h?fe$&>OR*\"\n        \"kHfe$xcVX(?$8F.tre+MJAH>#YQmA#0:)=-4:)=-;qw6/\"\n        \"jJ#N#ff'-M,X92'wTqA#mbN^-t[NX(+W7kX2C-IMHQ7IMIW@IMJ^IIM:j[IMY>FJM)\"\n        \"EOJMGj0KMJuBKMT>qKMWI-LMwO6LM\"\n        \"SU?LMWndLM3umLM`$wLMa**MMa13MMc6<MM^<EMM?CNMMLIWMM/OaMMwg/\"\n        \"NM#%KNM.<pNM)B#OMoM5OMpS>OM9ZGOMr`POMA5;PMPGVPM0`%QM3r@QM5(SQMZ.]QM^F^-#\"\n        \"iZ.H#8mG<-\"\n        \"j8RA-4Hg;-YwX?-H``=-URx>-J``=-E;)=-@mG<-cRx>-D,a..85UhL:3eTM]K3UMqP<\"\n        \"UMSVEUMUcWUM+jaUMWojUMl+1.v7A:@-:9RA-#Sx>-[Hg;-nZ]F-,xX?-WPKC-s6&F-rmG<-\"\n        \"smG<-<xX?-oHg;-DF:@-wmG<-Lkq@-sHg;-BxX?-1a`=-vHg;-a^3B-,nG<-K@Qp//\"\n        \"<G##JWr,#(Gg;-`>0,.glBHMA'MHM0-VHMMkFrLDhs,#TB]'.-lGTMcD*UMwP<UMU]\"\n        \"3uLCis,#\"\n        \"kg=(.@3JVMfn>WM,qx/.Kp4wL7is,#<4:w-AT:xLC@Y0.P8cwL.ns,#cS-iL-*/\"\n        \"YM4*YZM15lZM`E23vLZ`=-VsUH-@<)=-HIg;-IIg;-KIg;-LIg;-i[]F-^iDE-u[]F-r<)=-\"\n        \"]QKC-\"\n        \"(ucW-kb=R*w1=GM/XlGM/\"\n        \"UY,MYGXP&S?mA#T&uZ-mS^e$SnGR*gi1@0H5_e$jM9F.$3Ao[Xrli0'hqA#Rh]j-H#AL,i&\"\n        \"AL,Y[1eZY+)2Mm8158NpH59+bcP9,k(m9K#E2:QY=,<@,XG<\"\n        \"Gll]>J1iY?K:.v?LCI;@MLeV@NU*s@EL&pAFUA5BL6:/\"\n        \"DlDVJD%xC;I^IVPKisxlK-r;2L%k4/M'Kd`O+LZSSt4PPT71mlT&Y12Up;OMUqDkiU/1./\"\n        \"V5h&)Xs%DDX#]<>Z7HUYZ[K99A\"\n        \")kN`<sVjl8%gK>?+GD8AO7niLg]45T,]rSA#P$@0`*Af_#YkA#]Gg;-qGg;-4Hg;-sTGs-b;\"\n        \"OGM(RcGM.wCHM/'MHM0-VHM3:SqLm#9-#1rN+.P,lWMw0HwLf$9-#&<)=-4nG<-Za`=-\"\n        \"KIg;-dO,W-Yp]e$)-^e$Mp'@0+l0-M,92,)Ac<#-YH.m0ZD:&5qnJ88q<C2:#Tk]>)5dV@*>\"\n        \")s@KiUPK:B92L^<GDO9uTSSq=LMUl4hiU$_9>Z,c@5BLpY5B0Dm92`rMM'J`:5K<?G5B\"\n        \"#MDpo2]o:ZtfKe$LcxOf9A_S%;LlA#E@DX-(VV_&Z:W_&kkW_&t0X_&)RX_&*1ae$s?[\"\n        \"w94PERMM2eTMi7BVMkh5WMr<vWMpTDXM.*jxL^.K-#x$a..llBHM%YU10M)1/#YvI-#EHg;-\"\n        \"HZGs-a15)M=5lZM_G1[MHk<^Mut<BM0Msu5/\"\n        \"(lA#rGg;-5Hg;-rBg;-e.LS-e.LS-;B]'.8l$qLnWZ)M/\"\n        \"'MHM1T&7.#Z<rL^s%qLt1nlL)[i/#E;#sIJ`:5KP7ci_HNcofKj_lgk,Uiq\"\n        \"qC<mBBLN_&nQFOM4F+RMqT)xL`L^-#s'gE#iGFgLBA^-#9>gkL5'MHM='(SM=wXrLM6fQM+:\"\n        \"^-#ZYV2CpcKq;)hp1KuBw?0'J&44NHbe$/XLR*#sce$jA;qVXxOrZ-66;[/mel^Tscof\"\n        \"BC+5gKj_lgLs$2hY=1Al1vu1qV@<Mq9DViq@]oA#6rcW-*q6L,07qHM6Q7IMEdRIMT[\"\n        \"HLMVhZLM*H,OM*Lo+/\"\n        \"Ilt(E#wNe$gRKR*k_KR*i4SX(nJPe$Y#>;$#isMC^)^e$*0^e$.B+ci\"\n        \"Au22'0tnP'Kve]G`k]MCM>Z>6Vr#sIR(M5K*_J_&7RAul2>ti_=tRe$9.]_&4Pde$)vw-\"\n        \"6Oq]_&Qw]_&QtS_&X%v1MTMsu5ws;,<x&WG<:U9/D;_TJD#1&)X$:ADX9X0jC/nb?Ka?V3O\"\n        \"H8bfCs?x(3C2^VIF;#sIJ`:5KaAdofKj_lgk,Uiqa%A/\"\n        \"DTQ%j_5#S?g0$xP'wFg;-R(A>-oV3B-FX`=-dRx>-FHg;-Z``=-IHg;-JHg;-PHg;-RHg;-*\"\n        \"`Nb./Zi0v.fq@-tHg;-uHg;-\"\n        \"vHg;-xHg;-*Ig;-,Ig;-_kq@-anG<-[Ig;-gIg;-hIg;-lO,W-e;^e$R)(@0.+_HM;K.IM=\"\n        \"W@IM>^IIMQI-LMRO6LMSU?LMXtmLMZ**MM.IWMMr;pNMmA#OMoM5OM/(SQM0.]QM2:oQM\"\n        \"6R=RM9eXRMQJ3UMRP<UMSVEUMWojUMM,0VMb19VMnH2XMoN;XMqZMXMhvdT-e)*q-g0BX(c,\"\n        \">;$Ga@GD^)^e$0B^e$;^>ulIp@&GoVKe$w0GG)C2^VIF;#sIHMYSJ@%kJD,Bbe$#K5@0\"\n        \"r]ce$tcce$ufce$vice$xoce$*2de$,8de$l`o'829h=cDhBMhZFL]ln0ixlg^=PphgXlpj#\"\n        \":Mq9DViql5q.r/(lA#.Gg;-<@DX-n%V_&<5V_&=8V_&8Z^e$QP_e$RS_e$SV_e$Xf_e$\"\n        \"`%`e$lI`e$mL`e$oR`e$/\"\n        \"@ae$0Cae$2Iae$6Uae$9_ae$QQbe$RTbe$SWbe$p72LGaMZ_&[pbe$nPce$oSce$qYce$\"\n        \"ufce$vice$$pPe$bSUS%E`oQag+32'K2F^GaWVcD%-be$F0be$\"\n        \"UurRn=YXM_B-Se$4Pde$m'8@0J:[e$Y+)2MPW=D<5LTJDt'ADX?E;-Er_>M9B`qo%<lLe$.\"\n        \"bt(3-kR5'=?.&G@13XCBh?XC2Zi+Mmf>.#@rZiL;'A0#qAg;-'kq@-NHg;-/a`=-#Ig;-\"\n        \"X;-5.l=[YM-NfYM/\"\n        \")YZMTk<^MBrE^MK'X^ML-b^MY&-`M1,aaMV2jaM7&<*M-kYI#6rcW-*q6L,\"\n        \"07qHM6Q7IMEdRIMT[HLMVhZLM*H,OMb@^-#4?eqLdEg;-g``=-k``=-o``=-xGwM0\"\n        \"AW1vusXF.#5+W'M/\"\n        \"'MHMZ*LS-m.LS-m.LS-o@-5.;lGTMak<^MIkw&Mh'Q.#kjVW#pGbGM*_uGMj00_-ZhWw9k=\"\n        \"NM'Kve]GM`scEOBT>6iR$sIR(M5K*_J_&LBpEIoQAf_CTK_&9.]_&\"\n        \"4Pde$?7,dE/\"\n        \"q]_&X?(2h)lkA#+'uZ-DUW_&w9X_&x<X_&:0Y_&;3Y_&#A[_&x7I_&Z5S?gKxu%Fc[Xe$?@\"\n        \"x(3ID^VIF;#sIJ`:5K&KI)F(7ee$K@ee$P_KG)Uj&J_JGT;IiLde$4Pde$\"\n        \"G6Oq;NQvu5)lkA#x(`5/[tTB#3E3RM=XFRMc7BVMpTDXMrZ2xL*_i/#&Gg;-(Gg;-.Gg;-/\"\n        \"Gg;-0Gg;-LG6x/&i&.vvQh/#w;)=-xB]'.nJCXM:#PZMo+-h-f`EwTT1xOoA_lA##Gg;-\"\n        \")Gg;-N&uZ-3)(@02C-IMA>FJMY1_KMZ**MMqg/\"\n        \"NMq5gNM#5;PM)YrPM*`%QMK&RTM:3eTM^ojUM9i5WMq6mWMl<vWMx)jxLodr/#GJA/\"\n        \".M(HtLCer/#RHg;-*tA,MSgr/#%nG<-vHg;-\"\n        \"d8K$.w%7YM0a+ZM,m=ZM/)YZM^J)3v0Xq/\"\n        \"#PSx>-<*)t-JYajLX-b^MZ,6`M$3?`MguMaMh%WaMj1jaMk4aEMVNtl&0tnP'1'4m'6TgJ)\"\n        \"7^,g)8gG,*QHE/2RQaJ2SZ&g2X2YD4]Vq]5\"\n        \"@]oA#%S:d-L<PX(lI`e$mL`e$oR`e$9i:F.:l:F.;WQX(0Cae$m[FG)Y(BJC:U9/\"\n        \"DSQUJDTZqfDC-6,EQdmcEW[NJMRRjfMS[/\"\n        \",NX3c`OZECAP[N_]P]W$#QBqIJVubdfVpX),W'1EGW\"\n        \"#1&)X#8DDX$A``X,_x%Y&qpuZGPP8J$_-L,x_32'Cde&Gdw*5J1iJR*E-be$F0be$I_x(3Z@\"\n        \";5KoE4/M_wjfMY6YDO0_5R*pc'RE]mg.Um7_MU0?CX(YMgw9t1[_&CHEo[%1EGW96>AY\"\n        \"dqYM_:^CX(2Jde$4Pde$M,FL,MfJ_AI:ee$K@ee$LCee$7&*44Vjbq;nsT_&tre+Mv@H>#/\"\n        \"(lA#.lG<-/lG<-`8kf-xu^e$]3HR*_9HR*v]e9MK/gKMh-w1MGNsu5_iQ>6qnJ88>K)m9\"\n        \"9BD2:?#=,<4^WG<#Tk]>@uH;@A(eV@B1*s@@h9/\"\n        \"DM?UJDJ`:5K80XPKkwrlKS772LxA)&Pk]SSSp412U-uLMUR?jiU)C&)XTvBDX$_9>ZT(mSJ-\"\n        \"WF3k&*Bf_#YkA#]Gg;-qGg;-4Hg;-\"\n        \"sTGs-=aErLkw70#EmG<-EHg;-L)`5/\"\n        \"ttTB#q>lwL_w70#e<)=-gnG<-nh&gLcPPA#3$I6M@Lr(ELrqlKo8%#Qvk),WJlL5K(Rk341[\"\n        \"m'8V%&SnnTarnc9&8oo-N5KEm7L,93O5KQ'X_&\"\n        \").ae$LAmEIAGDk=Bcw1Ks,d%.a0AVMw6mWM0CdwL,EJ0#L`ET#QEluuXCg;-.lG<-/\"\n        \"lG<-+a%^.Xm:$#oX`=-/\"\n        \"Gg;-J?xu-<,W'MHwtRM['(SMN=M+v<6)=-EHg;-HZGs-U?gkLX&7tL\"\n        \"f.J0#%F:@-RHg;-THg;-vRx>--Mn*.b3JVM3Y$/\"\n        \"vP@:@-lmG<-hHg;-8sUH-mHg;-Z,kB-pHg;-'<)=-_,kB-tHg;-uHg;-vHg;-wHg;-xHg;-\"\n        \"f'hK-,dAN-)Ig;-<a`=-1nG<-2nG<-\"\n        \"3nG<-6*)t-#`K%MZ6lZM8;uZMbC([MLG1[MAM:[Mqq49#%Lx>-IIg;-KIg;-N[Gs-+U:\"\n        \"xLjpp_Mcx#`MZ,6`M03?`M]8H`Mo])aMte2aMvqDaMsuMaM*&WaMi+aaM.>&bM'(#GMd@H>#\"\n        \"GqlA#*lG<-5L@6/\"\n        \"<<:R#2g'-MvW92'1'4m'4B0j(qBqA#;lG<-7Gg;-:YGs-:WajLda@.M<R#v,#eY>-;&S8/\"\n        \"LqhP0:JoA#MGg;-SgDE-b_`=-28RA-RGg;-SGg;-A+kB-c5&F-e:)=-\"\n        \"`lG<-alG<-c#&'.nh](Mo=EMMkBNMM`HWMMaNaMM'h/\"\n        \"NM5%KNMo0^NM,7gNMl;pNM5B#OMnG,OM$O5OMvS>OMKZGOMkaPOM/\"\n        \"5;PMcGVPMeSiPM`YrPM*`%QM?r@QMA(SQMT.]QMY:^-#\"\n        \"<-OJ-8mG<-&PKC-'PKC-A;)=-6Hg;-URx>-THrP-E;)=-_wX?-9c,%.BrZiL3-[TMM2eTM]\"\n        \"K3UMqP<UMSVEUMUcWUM7jaUMWojUMf%'VMZ+0VM[19VM]7BVMi>KVMdi5WMT%QWMo1dWM\"\n        \"%9mWMl<vWM)C)XM*I2XMIO;XMpTDXMQ[MXMxaVXMYh`XM4piXM+trXMv#&YM$<JYMcH]YM+<\"\n        \"/#MC3S0#0<Y0.-HbGM0-VHMG^_sL04S0#,C]'.6+kZM2;uZM4G1[MIqE^Md$FBM/DWY5\"\n        \"/(lA#^Gg;-qGg;-rGg;-t10_-q/Y_&5Rae$CpQX(]sbe$pVce$/\"\n        \"XLR*rVPe$wdq9Mwdq9M#DVS%@Top&tv#/\"\n        \"Le>^e$4:N2L=>S?gB^UlJ28Q2L<4KR*u>5@0RTbe$58P-QRC_xOvVP2L\"\n        \"=vbe$f8ce$h>ce$Yb(qre;HfU=b@/\"\n        \"VPQ>e?ABEo[pe;,W=sOe$WG&&+uB=AY4'il^1)FM_9Nwi_,W;R*/\"\n        \"1-eZ-K#G`TscofIW(5gKj_lgLs$2hW+P`kdVo%liRDSow3&5pvG:Mqk&exu\"\n        \")lkA#5e%Y-aSU_&/\"\n        \"dU_&-P(_])`t,M3bTM'GqlA#6Gg;-S_`=-gvX?-HGg;-jQx>-Ph]j-1e)_]J&K0M&&KJ1i2+\"\n        \"j1uIr]5wTqA#=9kf-,Sk'8*mAL,+pAL,J,s-6K/s-6?I3@0@L3@0\"\n        \"Gb3@0Jk3@0Kn3@0Lq3@0Mt3@0B`BL,EiBL,FlBL,k7t-6l:t-6VxJR*h>nEe,#c-Qxl-:2u>\"\n        \"5@0ufnEeqLSX(6/6@0%:LR*osH_ApvH_A.$EL,/'EL,rWPe?sZPe?6nLR*M=s92CaUlJ\"\n        \"SF[MLr,d%.YF;UM(bVXMtmiXMusrXMv#&YMumMxL8@f0#(nG<-*Ig;-,Ig;-5*`5/\"\n        \"qM)W#91tZMRG1[Mxw=9#T4)=-LIg;-#Tx>-[Ig;-gIg;-hIg;-jIg;-lO,W-e;^e$0B^e$\"\n        \"1E^e$\"\n        \"6T^e$7W^e$8Z^e$QP_e$RS_e$SV_e$Xf_e$#ttEIY+)2MOaSV6xW_M:m<$j:oNZJ;qa;,<:\"\n        \"pWG<Mq]PB<CxlB7Lt2CeI8JLo)Y_&R_;F.Sb;F.BmQX(O1CL,?\\?Y_&QQbe$RTbe$SWbe$\"\n        \"Xgbe$Zmbe$[pbe$1D.:2t1[_&oSce$&oSX(w:[_&x8I_A#<I_A+(TX(vice$$pPe$.c@5&L^\"\n        \"UfLG:`e$%x`e$+4ae$OKbe$e/Pe$x(?;$udF/M^)^e$0B^e$M_5_AS:(AO6dQ?Zq$p+M\"\n        \"&cxOf[R`S%/\"\n        \"(lA#E@DX-(VV_&Z:W_&kkW_&t0X_&)RX_&*1ae$s?[w9cY3wpJ;w.L^EaEeba=PSrFhiUQw`\"\n        \"Eewt#;ZWhaJM`/^e$.<^e$/?^e$9;hl/wD>SIF;#sIJ`:5K<oiJM*#FL,\"\n        \"H7ee$J:[e$Y.;MMqYGOM4F+RMqT)xL1&51#ZYZC#XBP##xI31#.lG<-1()t-XhUxL7e(HMV'\"\n        \"<$#rgG<-/Gg;-PdXv-Bx>tLr-v.#,UGs-?tg%MG^_sLJ_41#je(*MR,[TM-?f0#P$)t-\"\n        \"21(pLraVXMvnW4#hgG<-$nG<-uHg;-vHg;-jPKC-(nG<-ASx>-$Ig;-&Ig;-)[Gs-QJxnL`\"\n        \"ZxYM*a+ZMOg4ZM2m=ZM3sFZM])H6#/Lx>-)-a..xT3rL75lZMN=uZM/Iv6#;5)=-@<)=-\"\n        \"A<)=-o4:w-68b$M^'X^MR-b^M`&-`Mr-6`MZ3?`M]8H`MXwMaMn%WaMi+aaM82jaME8saM?\"\n        \"5aEMiNtl&/kR5'R=pA#H3]Y-t[NX(OQ/F.07qHM3<`-M[:2,)TYhJ)Uc-g)VlH,*9pcG*\"\n        \"X(*d*36rA#^:)=-RGg;-SGg;-m-A>-VGg;-D+kB-k_`=-`lG<-St,D-<8RA-;4ho-(_5wp[:\"\n        \"MMMfHWMM/OaMM.<pNMsA#OMnG,OMbN5OMvS>OM3ZGOMFaPOMf(SQMB.]QM2:oQM3@xQM\"\n        \"RF+RMSL4RMTR=RMJ_ORMcfXRM@kbRMpJ3UMXP<UMSVEUMT]NUMgdWUM%jaUMWojUM34l1#\"\n        \"KSNL#Ru,D-)xX?-[Hg;-J,kB-tmG<-oHg;-qHg;-.a`=-GF:@-GA7I-1a`=-vHg;-&Ig;-\"\n        \"K@Qp/\"\n        \"X?cuuBQ<1#*YGs-1+W'M4wCHM5'MHM0-VHMe'm.#Hk@u-T&mlLEWqSMF^$TMJvHTMJ;\"\n        \"uZMHk<^MK'X^MAJk<#uWWL#crcW-QX`e$4Oae$qSPe$&5?;$x0bGN@1D'S(Gvr$/FZ;%\"\n        \"6tVW%VE6DNa2^e$GD%GV3'S5'0tnP']G&aF9V@X(k'Il]HYlSJthQe$?Yhl/\"\n        \"JlL5KF6Se$XYRX(Dk$MgXwJGNEFfGN,e'44,dp4S(LADX%C]`Xv9x%YwB=AYk%[]Y)\"\n        \"htxYBd:>Z&qpuZ\"\n        \"'$6;[*9dV[1;a-6)/\"\n        \"de$Nx6@01l[_&2o[_&3r[_&BnDM9Krfl^*lB2_u8G_&MFpEe[is9Mf6v.C>iBJ`Ar^f`\"\n        \"Tscofp%<5g&B;R*Qw]_&R$^_&q;5LGYfRe?[qee$NLOqVms^_&h?fe$\"\n        \"739@0DM*44?p1:2'>FR*+oBHM.t1-M2cTM';LlA#=:)=-PW=Z-jJ^e$180eZ2C-\"\n        \"IMTQ7IMUW@IMV^IIM9dRIMWa@.M48,,2_vaJ2SZ&g2TdA,3oi#d3W)>)4EO[D4lru`4aV:&\"\n        \"5T9XA5\"\n        \"=Os]5WMsA#H'*q-EXW_&.l2@0#WAL,r*X_&mL`e$a0]q;u3X_&2/\"\n        \"BL,E*,:2RDs-6A8JR*0Cae$2Iae$Q[;F.R_;F.Sb;F.HMJR*b$q9M?\\?Y_&Xq;F.W2Z_&\"\n        \"RTbe$SWbe$fJ9RE$N5@0\"\n        \"Vabe$[46_]vo)GV9_hxOS1FAP*g`]P^a?>QZdJJVubdfVqbDGWrk`cW/\"\n        \"U&)XHQBDXHXa`X2qx%Y&qpuZ'$6;[&ZmdNd:;uld*ol&.FRe$/?^e$5;hl/\"\n        \"ID^VIF;#sIKc1pJ*(=X(ckCL,\"\n        \"H7ee$I4Re$0;?;$nDn%Ok6K1p&+?v$/\"\n        \"FZ;%60oP'CQ.&G3vQ)OT0GG)[%_VIF;#sIIVuoJntA)OR85@0V/\"\n        \"Z_&RTbe$(uSX(tcce$ufce$vice$xoce$(,de$)/de$*2de$+5de$,8de$\"\n        \"-;de$4pI_AaN^_&[qee$g<fe$h?fe$,uGL,q&U_&+oBHM/\"\n        \"$;-Mfjpi':T0j(6TgJ)C,-g)>#H,*QHE/\"\n        \"2RQaJ2SZ&g2W)>)4X2YD4Y;u`4`rmY6l3_M:m<$j:oNZJ;/l[PB0uwlB21XMC\"\n        \"6UpfD8hPGE9qlcEQINJMRRjfMS[/\"\n        \",NUnfcNVw+)OX3c`O:<HAPba_]PnFHJVoOdfVqbDGWu0]`Xv9x%Y&qpuZ^Z:EOhF;ulg942'\"\n        \"<6i`F@MnDOs?x(3OV^VIF;#sIJ`:5KmWKGN_hui_\"\n        \"Fu4L,YC7@0Z/\"\n        \"NR*K@ee$Zd[Y,?,[Y5;LlA#w3]Y-W9X_&4Oae$f+SX(w4I_&+JZV$'Wv`O_,^e$&Pdl/\"\n        \"dnro%<lLe$)b5aO/DBM9L`YSJ[7voJLrqlK]_eMLw18R*/c&44/XLR*#sce$\"\n        \"&&de$')de$?H$aO+T&_]PE[.hY=1Alo9.>m&U#aON#__&p#U_&.(L-Mb&QJ(Vvxc39H(_]Y=\"\n        \"5DN9$0eZD'FYGk'>&P%-be$F0be$(lb-6[lj+MH=lxO2Tbe$RK?BPd:;uli9ol&/kR5'\"\n        \"?Qe]GEcEAP)W3p/\"\n        \"Vr#sIR(M5KCTK_&`Bl34:s#G`HNcofp+alg#YkA#j@DX-R[`e$5Rae$rVPe$-J?;$(+?v$.\"\n        \"UDW%6iKYPnn`oo-kR5'<BoP'<6i`F?Wd]PZeW>6VogVIwCg;-FHg;-\"\n        \"c.A>-2Ig;-HIg;-KIg;-1VR2.q&v1MINx(<4C9/\"\n        \"Dst%)Xc2n#Qr_>M9h$so%E%K#Qbat(33'S5'=?.&G^8Q9i/\"\n        \"4`9iWuj+MW@:2#>oc[.0m?0#?qX?-'kq@-NHg;-/a`=-#Ig;-lW)..\"\n        \";>[YM-NfYM/\"\n        \")YZMTk<^MBrE^MK'X^ML-b^MY&-`M1,aaMV2jaMfJk<#29TM#6rcW-*q6L,\"\n        \"07qHM6Q7IMEdRIMT[HLMVhZLM*H,OM`:^-#tdq@-7Hg;-g``=-x+vhLIZ`=-,m@u-BZbgL\"\n        \"wFC2#/\"\n        \"Gg;-alUH-6mK4.j#juLTFg;-]``=-HIg;-KIg;-Qkj#.A*ofL*p-3#(Gg;-:X`=-Q8K$.\"\n        \"cOSvLiDg;-Dq@u-Ni](MS^_sLNt-3#;dFR*u>5@0RTbe$Ddfw9Up/ci$g@>QfSooS\"\n        \"hfOPTm7_MUx8;R*:;6@06:5]XteZJV[xPe$%9k7RqnVGWoVKe$&EI_A/\"\n        \"Ade$6vI_ApEg=cCr'/\"\n        \"`WYFJ`TscofIW(5gKj_lgLs$2hW+P`kdVo%liRDSow3&5pvG:Mqk&exu)lkA#5e%Y-\"\n        \"aSU_&/\"\n        \"dU_&-P(_])`t,M3bTM'GqlA#6Gg;-S_`=-gvX?-HGg;-jQx>-Ph]j-1e)_]J&K0M&&KJ1i2+\"\n        \"j1uIr]5wTqA#=9kf-,Sk'8*mAL,+pAL,J,s-6K/s-6?I3@0@L3@0Gb3@0Jk3@0\"\n        \"Kn3@0Lq3@0Mt3@0B`BL,EiBL,FlBL,k7t-6l:t-6VxJR*h>nEe,#c-Qxl-:2u>5@\"\n        \"0ufnEeqLSX(6/6@0%:LR*osH_ApvH_A.$EL,/\"\n        \"'EL,rWPe?sZPe?6nLR*M=s92ZPVlJklxoSr,d%.\"\n        \"qF;UM(bVXMtmiXMusrXMv#&YMumMxLOv63#(nG<-*Ig;-,Ig;-5*`5/\"\n        \"qM)W#P1tZMRG1[Mxw=9#T4)=-LIg;-#Tx>-[Ig;-gIg;-hIg;-jIg;-lO,W-e;^e$0B^e$\"\n        \"1E^e$6T^e$7W^e$\"\n        \"8Z^e$QP_e$RS_e$SV_e$Xf_e$#ttEIY+)2MOaSV6xW_M:m<$j:oNZJ;qa;,<:pWG<Mq]PB<\"\n        \"CxlB7Lt2C&pTlSo)Y_&R_;F.Sb;F.BmQX(O1CL,?\\?Y_&QQbe$RTbe$SWbe$Xgbe$Zmbe$\"\n        \"[pbe$1D.:2t1[_&oSce$&oSX(w:[_&x8I_A#<I_A+(TX(vice$$pPe$ERA5&d-s1TG:`e$%\"\n        \"x`e$+4ae$OKbe$e/Pe$9o?;$64dPT^)^e$.<^e$/?^e$0B^e$D6&REq*)AOB*=MTV_SX(\"\n        \",ivKG-Ya(W:2JP^Nacof^J`lgdBASo#YkA##Gg;-)Gg;-N&uZ-3)(@02C-IMA>FJMY1_KMZ*\"\n        \"*MMqg/NMq5gNM#5;PM)YrPM*`%QMK&RTM:3eTM^ojUM9i5WMq6mWMl<vWMx)jxLi*ZWM\"\n        \"/-ZWMW3R3#auQx-jYgsLwp#wLL1OW#l2A5#Dx_5/'cET#KNX,M$^ID*/\"\n        \"(lA#GlG<-ZlG<-klG<-tlG<-)mG<-*Hg;-t10_-nWae$Y]RX(cSZ_&klZ_&lJce$&oSX((\"\n        \"JI_&A8X3O@lr.U\"\n        \"c]U_&#/xFM/\"\n        \"XlGM6-VHMdUG*v])A>-XTR2.b)9SMEWqSMF^$TMIp?TMJ?Y0.s:)UM_P<UMT]NUMWojUMpY$\"\n        \"/vmW3B-.HU`.eZi0v@hG<-tHg;-uHg;-vHg;-8*>G-(nG<-_9RA-)Ig;-\"\n        \"6<)=-1nG<-2nG<-3nG<-KNuG-b'Rx-7-W'M+<uZM9A([M5M:[MGt$7vfY`=-e<)=-;:RA-[\"\n        \"Ig;-=gnI-mnG<-hIg;-2Tx>-Elq@-xBDX-w#/F.&PkGM)UY,MmPtl&AKS5'@]oA#ZJU[-\"\n        \"t[NX(dOo342C-IMHQ7IMIW@IMJ^IIM9a@.MUQ#v,8SG/\"\n        \"2_vaJ2SZ&g2W)>)49+[D4`Mu`4aV:&5HkWA57=s]5QBT>6rRnY6)+4v6w*K88_caM:#b$j:\"\n        \"oNZJ;pWvf;3T<,<S:m]>HC*s@\"\n        \"/l[PBHhxlB7Lt2C4]m.Uo)Y_&^s,:2_v,:2BmQX(O1CL,O,1LGEvQX(Xq;F.-UGG)&b82LD#\"\n        \"QJMe3kfMS[/,NUnfcN+K-)OW*GDOifi`OR(+&PgjCAP$T`]P]W$#QcqB>Q^6VSS_djiU\"\n        \"nFHJV1CefVpX),W9hEGWx'acWA6')Xh`_`X,_x%Y$_9>ZVVruZ-66;[3=`MU`MC_&=+[V$,)\"\n        \"cMUd`U_&/dU_&6#V_&mHS+`1?J]Fdif]G]edMU%-be$F0be$[1KR*@]hl/oQF/MQYPe$\"\n        \"&#.:2X5Z_&Y8Z_&#K5@0cEj7Rp/\"\n        \")&PvjiKc#0t7RSRAX(rOSX(h>ce$1L+FIL^HG),LacWcwdMUScce$ufce$vice$jL`q;(P[_\"\n        \"&.c[_&)/de$6ITX(1l[_&2o[_&3r[_&W]s9M?QQ1p\"\n        \"'b-2_=MFM_Cgei_9M'/\"\n        \"`:VBJ`5M^f`&DKDkC^x?0d&VX(Xhee$MNbq;[qee$O+WwTWmbq;(iGL,#5OR*h?fe$&>OR*\"\n        \"kHfe$xcVX(?$8F.tre+MJAH>#YQmA#0:)=-4:)=-;qw6/jJ#N#\"\n        \"Qg'-M,X92'wTqA#mbN^-t[NX(+W7kX2C-IMHQ7IMIW@IMJ^IIM:j[IMY>FJM)\"\n        \"EOJMGj0KMJuBKMT>qKMWI-LMwO6LMSU?LMWndLM3umLM`$wLMa**MMa13MMc6<MM^<EMM?\"\n        \"CNMMLIWMM\"\n        \"/OaMMwg/\"\n        \"NM#%KNM.<pNM)B#OMoM5OMpS>OM9ZGOMr`POMA5;PMPGVPM0`%QM3r@QM5(SQMZ.]QM^F^-#\"\n        \"T8)O#8mG<-j8RA-4Hg;-YwX?-H``=-URx>-J``=-E;)=-@mG<-cRx>-D,a..\"\n        \"85UhL:3eTM]K3UMqP<UMSVEUMUcWUM+jaUMWojUMl+1.v7A:@-:9RA-#Sx>-[Hg;-nZ]F-,\"\n        \"xX?-WPKC-s6&F-rmG<-smG<-<xX?-oHg;-DF:@-wmG<-Lkq@-sHg;-BxX?-1a`=-vHg;-\"\n        \"a^3B-,nG<-K@Qp/\"\n        \"q<G##65m3#(Gg;-`>0,.RmBHMA'MHM0-VHMMkFrL0En3#TB]'.olGTMcD*UMwP<UMU]3uL/\"\n        \"Fn3#kg=(.,4JVMfn>WM,qx/.7q4wL#Fn3#<4:w-AT:xLC@Y0.<9cwL\"\n        \"pJn3#cS-iL-*/\"\n        \"YM4*YZM15lZM`E23vLZ`=-VsUH-@<)=-HIg;-IIg;-KIg;-LIg;-i[]F-^iDE-u[]F-r<)=-\"\n        \"]QKC-(ucW-kb=R*w1=GM/XlGM/UY,MYGXP&S?mA#T&uZ-mS^e$SnGR*\"\n        \"gi1@0H5_e$jM9F.$3Ao[D6oi0'hqA#Rh]j-H#AL,i&AL,Y[1eZY+)2Mm8158NpH59+bcP9,\"\n        \"k(m9K#E2:QY=,<@,XG<Gll]>J1iY?K:.v?LCI;@MLeV@NU*s@EL&pAFUA5BL6:/DlDVJD\"\n        \"%xC;I^IVPKisxlK-r;2L%k4/M'Kd`O+LZSSt4PPT71mlT&Y12Up;OMUqDkiU/1./\"\n        \"V5h&)Xs%DDX#]<>Z7HUYZAt@/\"\n        \"VLMce$pVce$2Jde$Vbee$TQ[Y,E9oi0)lkA#]Gg;-kGg;-qGg;-\"\n        \")Hg;-4Hg;-kHg;-sTGs-NB=gL&Q*4#.Gg;-5(`5/\"\n        \"jtTB#lMpSMF^$TMs)],vj6)=-[F:@-4Ig;-mxX?-KO,W-I0GX(oWOOM5L4RMrZ2xLAU34#&\"\n        \"Gg;-(Gg;-JfRQ-&U=_.^I24#;:)=-\"\n        \"0Gg;-gYKk.7*1/\"\n        \"#oX`=-EHg;-FHg;-c.A>-2Ig;-HIg;-KIg;-G*LS-Hj0o-w,Q9iO_$,D44cA#=5$-Wr_>M9%\"\n        \"_so%X&P,Wbat(33'S5'=?.&G^8Q9iBn`9ikXk+Mk^<4#>oc[.Cm?0#\"\n        \"?qX?-'kq@-NHg;-/a`=-#Ig;-lW)..N>[YM-NfYM/\"\n        \")YZMTk<^MBrE^MK'X^ML-b^MY&-`M1,aaMV2jaMfJk<#EVVO#6rcW-*q6L,\"\n        \"07qHM6Q7IMEdRIMT[HLMVhZLM*H,OM`:^-#tdq@-\"\n        \"7Hg;-g``=-k``=-v]>hL^W+DWPtqjtDFwr$*=vV%0nIp&@><R*/\"\n        \"?^e$<YNX(mdV3O`t(qrod6R*LgY_&<=uOf7GXM_8eRe$=-uRn1;'/\"\n        \"`:VBJ`HNcof*P*5gQ&`lgR/%2h)lkA#+'uZ-\"\n        \"DUW_&w9X_&x<X_&:0Y_&;3Y_&#A[_&x7I_&K*pl&?7wcWe>^e$A@x(3$p`9ME;#sIPr:5KZ/\"\n        \"dofKj_lgk,UiqIV-)XTQ%j_t3U?g0$xP'wFg;-T:xu-k)9SM^WqSMF^$TMHj6TM[p?TM\"\n        \"JvHTMPD*UMRP<UMRJntLquW4#(K/(MgniXMusrXMv#&YMx/\"\n        \"8YM*a+ZM,m=ZM.#PZM5-6`Mb2?`MguMaMh%WaMk4aEMVNtl&WWX?gPNui';^K/\"\n        \")S?mA#=lG<-8Gg;-QGg;-RGg;-SGg;-\"\n        \"XGg;-)wX?-flG<-lGg;-mGg;-oGg;-/\"\n        \"Hg;-0Hg;-2Hg;-6Hg;-9Hg;-QHg;-RHg;-SHg;-JPKC-amG<-[Hg;-nHg;-oHg;-*'^\"\n        \"GMd65GM9aVrZ0'5EX7pO+`&Owr$0tnP'7XgJDD&DX(\"\n        \"$X$&+?^w]GjGKe$E-be$F0be$Q9.XCl@SiKTeJGN@-bcWt'ADXu0]`Xv9x%YxKX]Y*?28],\"\n        \"Qio]/mel^3xWPgivFk=MFee$mfNR*[qee$g<fe$h?fe$869@0kHfe$w]MX(+oBHM/$;-M\"\n        \"fjpi'<ggJ)=p,g)>#H,*QHE/2RQaJ2SZ&g2X2YD4`rmY6l3_M:m<$j:oNZJ;/\"\n        \"l[PB0uwlB21XMC6UpfD9qlcEQINJMRRjfMS[/\"\n        \",NX3c`Or6GAPba_]PnFHJVoOdfVqbDGWu0]`Xv9x%Y\"\n        \"&qpuZu<o`X`/^e$0pu`XkuU_&9;hl/\"\n        \"DY?SIF;#sILlL5KI5DX(AWQ1p2DBJ`HNcofp+alg#YkA#j@DX-R[`e$5Rae$rVPe$sT>e?-)\"\n        \"fxXv>dD-#i%^.>*1/#_4)=-EHg;-FHg;-ExX?-\"\n        \"400_-GIPq;QO@;$DBv:Z^)^e$@R-L,_2]'8P<CX(*DxFM0-VHMMkFrL#IB5#_&mlL]\"\n        \"vHTMcD*UMwP<UM*vb1#Hk@u-a,W'M^=KVMfn>WMh$QWM=@*0vBZ`=-<4:w-AT:xLFRE0v(\"\n        \"UGs-\"\n        \"01/vLCbE4#9Ag;-&iDE-/\"\n        \"Ig;-8%&'.%`K%MEA([MWH1[MTk<^MIqE^MK'X^ML-b^MWpp_Mdw#`Mid2aMwpDaMv1jaMj(#\"\n        \"GMQ@H>#5:lA#*lG<-/lG<-.h]j-=:o34,r9-M-BMG)Ac<#-\"\n        \"TLX>-mdQ8/\"\n        \"KhL50kviP0'hqA#Qh]j-2h)_]L5pKMiC$LMt-w1M.Osu5XOpA#K]3B-*.A>-+.A>-J8RA-\"\n        \"K8RA-?wX?-@wX?-GwX?-JwX?-KwX?-LwX?-MwX?-B.A>-E.A>-F.A>-k8RA-\"\n        \"l8RA-V``=-hHrP-,4OJ-xE:@-uwX?-uHrP-q;)=-6xX?-%a`=-ohDE-phDE-./A>-//\"\n        \"A>-rCdD-sCdD-6a`=-ORqw-Y<OGM(RcGM0-VHMHdhsLfHK5#dM@6/D8OA#2N/\"\n        \"(M)RgP#6@DX-\"\n        \"%oEX(>6EJMZ[HLMkg/\"\n        \"NMtG,OM)5;PM0`%QM0+J6MLLr(EM%72Li&%#Qk]SSSrFhiUpX),W0-:>Z,9-vZ*YO?gUHpl&\"\n        \"/kR5'EvE^GPsVrZ%-be$F0be$UurRn;MFM__nCJ`HNcofp+alg\"\n        \"#YkA#j@DX-R[`e$5Rae$rVPe$t3Fk=:x#8[kuU_&?@x(3ID^VIF;#sIJ`:5KZ/\"\n        \"dofKj_lgk,Uiqv9RDb&MO?gdNxr$2$]p&av'Abp,Ve$<YNX(R/\"\n        \"uKG1%XlJNd6R*ckCL,H7ee$I:ee$\"\n        \"K@ee$K=[e$q`E5vlo$S#L[Gs-4#F$M@b`=-[N`t-/\"\n        \"<@#M`WK_Mijg_Mi56DM7cf7nk,UiqR@s.rnGQfr)D/\"\n        \"DtALi`bQ&.kXeQxr$0tnP'<6i`F>Bf`b4d:1.D6jBM2JuCjX4l%llvj`b\"\n        \"Q)__&safe$t^Se$#2p'8#2p'87kl+MJ&s7#xAt%c#Bt%c#Bt%c$H'&c_X3B-$hN^-X5`'\"\n        \"88qu+MxlL%MZ%>T#CFluuXCg;-dcg,.uNkGM.L>gL)6&8#C%mlLA'MHM0-VHMTwtRM(.v.#\"\n        \"ZX`=-*.E6.;NpSMF^$TMs&A0#eX`=-gBRi.m><-vThG<-RHg;-THg;-5-kB-IIg;-KIg;-\"\n        \"LO,W-PDn-6;&6]MCIRAM)XEigFooA#LIg;-e0Yc-:9^_&Ee`w9UhS(M^:AS#*#Y?-]Ig;-\"\n        \">:RA-w/A>-l<)=-gnG<-hnG<-i*`5/\"\n        \"*&@S#[c:aMloDaMguMaM0&WaMo+aaMj1jaMw7saMr=&bMsC/\"\n        \"bMnI8bM%PAbMpUJbM,^SbMxb]bMshfbMtnobMutxbMv$,cMQ+5cM&u]+M6N/8#\"\n        \"jZZC#gg60#HAg;-LHg;-NN,W-F3_Ee;x(GisIm]c:9^_&poNR*E=KG)-#.Po/\"\n        \"+n]c^@OR*$jVX(w5L_&*fUw9$oSuc6/\"\n        \"Z_&RTbe$x5m347P^.h&RGq;RP[ucK]ER*W$5`M_DZ`MguMaM\"\n        \"j1jaM#?D].5;89v,sX?-2quHMG)h=uF$F>d+K&RE+2HYGKD^VIF;#sIE`oQa6@t1K&qJ>\"\n        \"dLW<F.cFKR*QNXe$6ZK@Mf3?l.W;[S#FnG<-Sa`=-TnG<-nB#-MIK]S#C[3B-_]Kg-[-M'S\"\n        \"<'v+MQQ]S#&k](ML>&bMdfg,.jOIbMKr#:ve/;.MUHJ8#i6K$.xHbGM0-VHM&LA/\"\n        \".En;BM&&^7.NQJ_M]dB(MJ/\"\n        \"r%M2'OW#OdI3kpoY3kruY3kruY3k>*m+MCR]8#r_@;eO[kR-rakR-\"\n        \"tsK4.J)HtLXS]8#a#^GMrNe]MrNe]MD[xS#r_@;eRnK4.Vv+`Ms[kR-rakR-rakR-rakR-\"\n        \"sj0o-QdI3k?0v+MD[xS#qX7;erX7;e2dC8ffuUwTv+Z3kv+Z3kwVql&@=]Sfe>^e$3'CTf\"\n        \"JhFG)pCFlfW8`lg+4_B#kHKU#f+I4vi*#dMX-b^MYWK_Mv>Q`M`Jd`Ml7W*MG_e&MDGwN#'\"\n        \"1xfLf_e&M7jG<-0ucW-kJU_&sMd=cR)j`FU%voJVR3/MXejfMX'pGN3Il1g=tTV[>Ue]c\"\n        \"f@Qe$2vwOfB*KVes5?5g5*^_&Ykee$^wee$_$fe$@co-6aZ1aM/vMaMv1jaMmC/\"\n        \"bMv$,cM8u]+Mbkw&M[p=T#7(hK-CLB<MrF)e.TUl##Rk@u-C%mlL/\"\n        \"'MHM.qugLT.P9#t>L1Mt5P9#\"\n        \">XajLUwXrLY/\"\n        \"P9#EHg;-HZGs-U?gkLcD*UMwP<UMT]NUMHk<^M6rE^MK'X^MK$FBMW'-l._*kT#Co`a-%+\"\n        \"ee$nxr?KI%a^MSWK_MXsgCMk=NuluK.>m^bHYm48oA#qa`=-l<)=-gnG<-\"\n        \"hnG<-e[Gs-V`K%Mdj;aMloDaMguMaM0&WaMo+aaMj1jaMw7saMr=&bMsC/\"\n        \"bMnI8bM%PAbMpUJbM,^SbMxb]bMshfbMtnobMutxbMH.?:v7U]F-'oG<-?[U).)+ofL+4Y9#\"\n        \"pZ,%.-IbGM\"\n        \"/XlGM6-VHM9:SqLC4Y9#c3:w-7'$&M.ZqSMF^$TMIp?TM5'A0#eX`=-+kq@-X/%Q/\"\n        \"$Dl7RlS?Yc;LlA#RN`t-Xj](MVwN^Mt-P9#DfG<-SnG<-YnG<-]nG<-e=Ab-gA1:2]tee$(\"\n        \"KW?g\"\n        \"WvPrmrT*;nmKEVn_bpA#hnG<-cIg;-&0A>-knG<-fIg;-s<)=-nnG<-iIg;-v<)=-w7&F-\"\n        \"snG<-*b`=-oIg;-qIg;-rIg;-tIg;-uIg;-wIg;-(7@m/R@cuu<.b9#(Gg;-.Gg;-/Gg;-\"\n        \"2YGs-x4UhLak<^MPt<BMchw@bS?mA#U6u(.RwV^ML-b^MSWK_M^>Q`M(Kd`Ml7W*M'Rl9#\"\n        \"ttTB#qu:*MTO1U#iFQ&MwU/+M,Ru9#jZZC#jAxu-Rg#tL@Fu9#Zg&gL3*OW#W-N'MlO:U#\"\n        \":kj#.g8P`Mp8-)MTV:U#ZsZiL-8saM*J8bM'V/\"\n        \"+MXQB_MERB_MERB_MERB_MERB_MERB_MERB_MERB_Mn_UU#D-v+jF3),jf00_-ulWw9%3B;$\"\n        \"FBMGj^)^e$0B^e$Ek-XCMH6JL,eGq;\"\n        \",P$Dji>h=cNA<Jin^?Gj8%HX(<sK'S6Gx-66Gx-6Pam+M^hU:#5hq(k6hq(k6hq(k6hq(k&?\"\n        \"C8fmFx-6Qgv+M7kg_M7kg_M`q_:#?f=(.>tKHMi'kB-1*-l.mb^:#T<)=-SB[20kh1ci\"\n        \"vr?Yc8eRe$YA%2hYo8GjvTIYm`t);nnGQfrU+P`k];S`k];S`k];S`k];S`kOkU`k];S`k^\"\n        \"A]`kjKKC-]iDE-]iDE-b>Y0.tW8(McrT(M,m`a-<E:_ASmv+M/(.V#`YFAl+bb?KVV@e?\"\n        \"ast=l2vVe$5;hl/ID^VIF;#sILlL5KI5DX(m'8@0I4Re$@/_'S@/\"\n        \"_'S?+_'8lD:Yl5L>Ji-kR5'1)T]lJ$w]l'E2;6OV^VIF;#sILlL5K0?CX(gF?F.6:iw9Qw]_\"\n        \"&QtS_&Bhv]lPbBfq\"\n        \"K(h]lXcVX(nQfe$'mDX(sA5LGsA5LGq5#LGXgTul$=aoo-kR5'HgoP'>B%aFv_<X(#Yuxl?\"\n        \"R:d-WqrEIQ<u1KaAdofKj_lgZX->m9x@8f.HOq;/tnP'BnD>m$6&REGuhxF0_5R*OpY_&\"\n        \"q25@0NHbe$]fRX(QNXe$UkfCM:=Nul]X->m^bHYmsSF>mEZ^_&b-fe$c0fe$8Z1:2km^_&\"\n        \"f9fe$g<fe$h?fe$iBfe$jEfe$mNfe$oTfe$qZfe$r^fe$tdfe$ugfe$wmfe$vdSe$/QB;$\"\n        \"UWaYm^)^e$0B^e$9xw(319YlJ5.,F%NF/\"\n        \"Vmv<KG)bBASo=X(B#0Qc)Mr=&bM$J8bMpUJbM'V/\"\n        \"+M1KR;#&Gg;-(Gg;-FSR2.EnBHM5'MHM0-VHM:e=rLh^R;#(BaD#]NpSMF^$TMJvHTM\"\n        \"ak<^MIkw&M`Jd`MhN[;#A#>;n+2RX(E-be$F0be$?Yhl/:bJ/\"\n        \"M8eRe$RTbe$@s4;nY@n]-Wt6F.W$5`M[,q(MJ[wV#enG<-aIg;-gIg;-jIg;-mIg;-tCg;-\"\n        \"x6u(.s85)MKpA,MV@6)M\"\n        \"JKhkL#[i/\"\n        \"#E;#sIJ`:5KrhZVn_+.:2Nn]_&J:[e$-$3XCgV9_]gV9_]gV9_]^2n+M8cw;#hS>8o+S#_]_\"\n        \"2n+M7p98o2Tbe$fG,8ogG,8ogG,8ogG,8o.(4_]_8w+M9l<W#gM58o'h]j-\"\n        \"F=(_]_8w+M=(F<#UpN+.GIbGM*_uGMX0Wvuo]l)MOEg;->L`t-T&mlLQWqSMF^$TMs)],\"\n        \"vP6)=-mxX?-w`nI-Fgh9Mv$FBM8bf7nk,UiqYQmA#rnG<-nIg;-'7)=-*+>G-+4Yc-_:$LG\"\n        \"W]+qrf1b.q+h+2q9.Yc-_:$LGdDn+MQ0X<#*h+2q9.Yc-_:$LGdDn+M*-aaMQ0X<#)bx1q+\"\n        \"h+2q9%>G-+4Yc-`=$LGeJw+M)w2*M]HPA#iv&4Mnar/#I`:5KJ/miqp5_KcB<[3O^%Bfq\"\n        \"P%Xe$GJJR*Yn)F.`aYlJR.rlKoFniq.Hbe$]fRX(QNXe$UkfCM:=Nul]X->md0*\"\n        \"ZmEFBfqEZ^_&b-fe$c0fe$8Z1:2km^_&f9fe$g<fe$h?fe$iBfe$jEfe$mNfe$oTfe$qZfe$\"\n        \"r^fe$\"\n        \"tdfe$ugfe$wmfe$vdSe$$(oQaJG]+rCTZe$+GxFM:vqh.2ut.#KX`=-tl6a.?ra5v@hG<-\"\n        \"Yh&gL4DIV#,05DM==Z=MmuMaMp1jaMmC/bMthJ+Mk1N*M7$OW#xi>5M(L>gLPK'=#]kK4.\"\n        \"TtKHM0-VHMt92/\"\n        \"#%Lx>-EHg;-HZGs-GEQ&MTk<^MhqE^M8(X^MQ$FBM8bf7nk,UiqYQmA#rnG<-nIg;-/*Xn/\"\n        \"@h60#BB/=#]G`t-#H;UM>D3+0t=G##9H8=#(Gg;-2YGs-@?gkLTg$9M\"\n        \"0O/\"\n        \"Vm5LLB#vQI@#6^1aM1dDE-hSQEM$J8bM*Dj*MF^TX#&9m(teEl(tfKu(tkTg_-CJPq;=:k?\"\n        \"Ks7P%teEl(teEl(tfKu(tkKKC-eQKC-^.$L-avR/Mec]bM8p^=#%ND&.SIbGM*_uGM\"\n        \",ecgL0r^=#/\"\n        \"Gg;-<:)=-hGk3.qNpSMF^$TMw5],vCc]=#gSx>-.ad5.wtV^MQ$FBM)45GM,JuCj/\"\n        \"(lA#rsw6/1&@S#B8%bMtI8bMqU/+M+uxbMBvxbMj#q=#BMs%uDnUH-C'rd-wQk?K\"\n        \"pin+MBvxbMj#q=#BMs%uDnUH-C'rd-wQk?KBO'@Kpin+MBvxbMBvxbMBvxbMk,6Y#AGj%\"\n        \"uCMs%uEnUH-lx]7.lt*cMC&,cMC&,cMC&,cMC&,cMC&,cMAjJ+M*2nlL;[i/#E;#sIX`k?K\"\n        \"CR'@KCR'@KCR'@KCR'@K7;6LG5/$LG19o=u7,/Au7,/\"\n        \"Au&Isooo=6LG8>6LG8>6LG8>6LG8>6LG8>6LG8>6LG`P3wp1=o9275J]ub]N]u85J]u85J]\"\n        \"u85J]u85J]u85J]u85J]u85J]u\"\n        \"85J]u8;J&#<Pe&,&0A>#4ij-$@bn-$.(1B#P#u7I<UL^#TrIfL1:h#$o-c(N2C-?$)P%\"\n        \"GV3LHZ$6-.S[4Udv$ES:c`2OvV%H<KVe3X;s%9krS&>$;GjBB8p&bcF$KKQ.cr;R:B#0oL?#\"\n        \"9wWN02uU?#65>##_RFgL.1bW#J@`T.<ew[#8@`T./\"\n        \"fLZ#N@`T.]o2`#V@`T.IK'^#5A`T.D'F]#UA`T.I?k]#lsG<-.fG<-4tG<-7(H<-n$kB-_g`\"\n        \"=-FF^gLfhC#$H-vhL[+>x#8CfT%\"\n        \"lWZ`*kXSdF>M_oD2<XG-?Q<.31ULS:hT1B-dnr.G05DEHCVIr1e2e:CkQitB=VSj09Q.F-2>\"\n        \"r*H*M^kE+rCVC;]VeG762&G6]7fGAF_oD5spKF642eG6(x+H&*)?-.JZ1FND2:D#KJKF\"\n        \"`Z=lEo07L2X*#s8.Uf&6^`Yn*cNv3+l*Y>-.)(^>-a3I?=Aox0paijE@-xF-E676DiM<;C:\"\n        \"4//G3Ww<IO*l3CNa2)FkY'k1=Y[72P/8>BOGC=3`lxuB*i$4EUcXVC/)DEH'qU-O<2BEH\"\n        \"+/\"\n        \"G]FcgXoIT1PcDH]4GDcM8;-0m>G2P^A,3#<s,<nf=J:Brl-$jn&@'eof]GcYj`\"\n        \"FT0B5BXmqfDr1bDFBKm3+rne'&lXY'8/.n-$1&b%OV2oiLC.AqLY-lrL^Y5.#P/\"\n        \"g,M(lP.#4Z5<-\"\n        \"?TGs-7)trLe1g,MS'(SMff=rLkJG&#JmG<-LB]'.$T3rLdkFrL::2/\"\n        \"#a3(@-;c&kM4LG&#QX4?-KViX-=UMOYIDp`=3sRDFU#M881m@dEft<D3mRhJ2IE&K2*'D9r+\"\n        \"KTc;EIe'&ES'r2\"\n        \"Nq(DN^EqC-aES>-+%gN-FufN-i;w:OF-RD-PvgK-@WJ(NHB^nLc4B-#U:F&#'Srt-s/\"\n        \"RqLnQD/\"\n        \"##-3)#L2^V-uC<L#a;H3b-d*DNk'C4NOJr'#]YkR-XX4?-8:7I-NMM=-KLx>-wC^$.\"\n        \"`(trLor%qL13oiLIFfqLEI3#.(mWrL$hipLXNx>-EX).Md7$##T?*1#=G;4#.=3ZP>H1n#k,\"\n        \">>#)8>##74i$#O(V$#dEX&#uI;4#&b($#*4SP#.Ml>#2Y(?#6f:?#:rL?#>(`?#B4r?#\"\n        \"F@.@#JL@@#J@%%#LL7%#PXI%#Te[%#Xqn%#VXR@#Ree@#Vqw@#Z'4A#_3FA#c?XA#gKkA#\"\n        \"kW'B#od9B#spKB#w&_B#%3qB#)?-C#-K?C#1WQC#5ddC#8gZ(#=Ul##wnd(#dA/=#wOc##\"\n        \"-G31#H'3D#A2ED#H0X$M3CDW'Q'w7RQY^._dUPV-)j?`WPj$v5M-)5821G`N3;<PAi1r@b9_\"\n        \"/2'b?0/(Khpr6O*QS7n1g4]7NTP&A?cf(Khgr6m6#5Aa`*DNLwwCax#;YY`q3]b50NS.\"\n        \"E8FwV&_7Y0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4A1OY5EI0;6OB`l8Ym@M9^/\"\n        \"x.:bGXf:f`9G;jxp(<n:Q`<rR2A=vkix=$.JY>(F+;?,_br?0wBS@pJNP8J7ll8[sIM9`5+/\"\n        \":\"\n        \"dMbf:hfBG;l($)<gH+JUM?D]Oe2+M^J9Txb8Sg1^:P_`<M[U+iveV]=$(8>>L[$;Q+GVY>^\"\n        \"D-GV_gd%O*L4;?hl?on0q08@d5,YlJt]4o.vdo@gDGul%lJPAdsj@k>pDMBB2&/CFJ]fC\"\n        \"mDKxk.0*]tLoXcDP1:DETIq%FXbQ]F]$3>G[hH]F?3ouGeTJVHim+8Im/\"\n        \"coIqGCPJu`$2K##[iK2Yw.L051]k@td=uU4+Se+Ss+M$bClf1xo(N+p7DNCiS`N9R1AO6Z&\"\n        \"Yu^9&;H=khxO\"\n        \"+jYoIC9euP3h);QMhHVQKj&8RO,^oRj`3VZj.+Pf#1?fhSD>PSTb)>Gi)u4J<^XlSYi:\"\n        \"MTCDtFi`77JUpfQuceiIoevw#JhdOn+Vt34GV91l7[1'3DEbQ]rHurm.Ll*0DW6>I`WoMq1K\"\n        \"rN,AXXei=lxs(>Y&6`uYV%';Z'ICVZ.gw7[2)Xo[g$v4]j3;P]:Yp1^1O2##@(m._D@Mf_\"\n        \"qp+Vmt/c7nLqe(aP3F`aTK'Abwe@]bp0rCjPoIrdY5s+DRYv=cQ^>Yc37Q`Ed^x7Iw(3JL\"\n        \"QeDf_<LL%teVVrde>[iB_l0AFk%Soe%X7`jqIOlfub0MgHwh@t%1-Jh)Id+i-bDci1$&Dj5<\"\n        \"]%k9T=]k=mt=lA/UulEG6VmI`m7nMxMonQ:/PoURf1pYkFip^-(JqbE_+rf^?crjvvCs\"\n        \"n8W%trP8]tvio=u$,Puu(8P>#,P1v#0ihV$4+I8%8C*p%<[aP&@tA2'D6#j'HNYJ(Lg:,)P)\"\n        \"rc)TARD*XY3&+]rj]+a4K>,eL,v,iecV-m'D8.q?%p.uW[P/#q<20'3ti0+KTJ1/d5,2\"\n        \"3&mc27>MD3;V.&4?oe]4C1F>5GI'v5Kb^V6O$?87S<vo7WTVP8[m729`/\"\n        \"oi9dGOJ:h`0,;lxgc;p:HD<tR)&=xk`]=&.A>>*Fxu>._XV?2w98@69qo@:QQPAW<+\"\n        \"WHXqWkL&Y#<-k`>W-\"\n        \"u`6F%i^ZR*.9IL2`4m<-XY#<-YY#<-ZY#<-[Y#<-]Y#<-^Y#<-_Y#<-`Y#<-hY#<-ilu8.;\"\n        \"jiT9`M&FIq%jS8o1v-3R[6WCN)]'G'a6`%A07nM8x?DI=6&9&-KFVC>3as%6=ofGFKWs%\"\n        \"D2'pM*#QvGFNnt(+4LVCHK3oMvQ:nDApNSMrM'BF>O#lM&udKFUa`QM@PD6MCYD6MFi%\"\n        \"nMBY`QMaY`QMc``QME``QMc]`QMPPc%MV5:SMP0^,MCGY)NDJPdMC8g,Ma?x/McQXgMEMPdM\"\n        \"cNFKMT2cp7?PS88`Y#<-Zc>W-bh=_JA42eG$]eFHThmfG6GQa$L&CjMgBqh2bTcs8ot>LF#\"\n        \"qb'%$B#hFKo%(/,@n-NV_ZhM$CYs8eu*7D*h`9C<Zn3NNwp=%ESX$HUuBx$&)gGMK-P<8\"\n        \"pb#gLUR-1MW+pGM_N#<-gX.U.rD2[A5QP8.ZK2[A84^e$AW_w'&m4gC92+7Dv@\"\n        \"Rq16Y7fGAiN9`-2f:C@a_MCtswj1$E'kEl#;J+v/\"\n        \"Fj119e7MlE*.3W^W0C.KfUC(UQ[AI6%7MN3NJ:\"\n        \"Cc#4EH]w5/\"\n        \"ng,89u0DeGti5lEl,4nD=[HHFbrlKF8PjpB0,DEH3eF<Brwb4M3Co6M>-lrL%KDA.vjDV&(^\"\n        \".F%lTsG3%FC:)lW_:)F9nQs1WE.3s'/>-pt.>-n$gV/lJTiF'g.U(+A?.6\"\n        \"1[3iM8t2.3Z#gG3&x$*.OTKjM`JJ+4XvS,3p4RF%,_Mp7-2vLF@Wg29p9_M:&QOA>H[\"\n        \"JqD5v&J3)51g2Yc>W-xtxw'@ZTjMYqvhMeqvh2)X&T.%0<?%kVwA-6Z#<-:Z#<-O]\"\n        \"3N0gkqaH\"\n        \"%B#hF3jgaNIM.2.NKKjM^U2:8%ehM:6[=Z-Vb.+%sh.+%wt.+%xw.+%&./\"\n        \"+%(1&+%m)VOD%Lv<B:>-lE&cS>'3J;t--5IRM/\"\n        \"'+aE=0MDFATe]GB^*#HF,B;IIG>8J1t[5'7iKC-U]rT.\"\n        \"hx.>Bp5[uN/\"\n        \"h]j-ipT[')WqlMFZ-pMG^$TMx:)WH_PGI3E>`-m(%x92.[moDlLnw9O&>R*l&mrB-\"\n        \"JraHtveUC463/OI?sJ:J?Iv$$W5W-HRY4+RKV2Oja.dDB'Iv$T>G)N/+p/N?fov7\"\n        \"Cm/\"\n        \"g2TUda$CU&V8h#C@0']D@0)iV@0RR@E52_O<B$B_(%jPp;-qPp;-sPp;-wPp;-&s1T.:<U=\"\n        \"BT*([-R[RF%phRF%%1JF%4D*nMxC4nM;ntnM=$1oMA<UoMB?LSM^t^2BWje;%6Z#<-\"\n        \":Z#<-K,;T._PGI3#L1x$EeAV83^aJ2DB4W-v3(:)p)28CuAFVC$N6)%I8FVC*lt8I=.\"\n        \"A5B8J`3tZ2b3t=3q34m:GcGhaXb$R5>)NWBV2%/L[1M5'30C$e`TC/\"\n        \"m=vBsMV9([f6L,jnVmL\"\n        \"vbssB$]eFH[3kC&t9kC&0nbC&D<+W-lxHF%e^v:&J1AY-&?lC&>ElC&3_-/\"\n        \"6:W..6-Jh;-)P%f$1T3i2^0#O-TXm&%BFWU8G',d3r%sqL+AClL<-)m$1X<IN*lxZ$\"\n        \"A4wt7wgRQ:Y&3i2\"\n        \"2gKs-Sd#0N&6,0Nl2pjMXgZL2YA8FIuup;-8Zn#%6>V-m1^N.3tgE,3tgE,3tgE,3tgE,\"\n        \"34b*W-:aiwK&aiwK&aiwK&aiwK&aiwK&aiwK'diwK'diwKxLhQ([8jI3JHfG3JHfG3BcL90\"\n        \"m$8r8X*,d3'.iH-DLd>9k(,d3uRuG-uRuG-uRuG-v[:d-Z<wQsvs9FIvs9FIvs9FIvs9FIn]\"\n        \"p;-1-%#%3b<.NK'<.Nv%<.NK'<.N(&<.NK'<.N(&<.N(&<.NNloR9*(:oDBjje3;2JuB\"\n        \"l(dH=qhJ88h,B,3R;1j(+GhG3sg0s.S:dd=p'1737<>CP23aI3]wE]B<^(`P(*1`Po/\"\n        \":`Pxf6aP/xmh-Icu.-@'f.QvZSlFvZSlFvZSlF@sC`PLO.e-H`(/-uT5x'Tqi;-MPs/%XQa/\"\n        \"W\"\n        \"fFmt7%6^K2HBSMFwKnL2jech%UH<p^fFmt71UaJ2B=r;-MK^s$2NMW/\"\n        \"_;EM2S((w[a]+c4v4v`4laA)4W&<IHtZ=IHk+n>[Z*E.Ng+E.Ng+E.Ng+E.NG4aINciv-\"\n        \"NRvL*%_7[F%R8T-F\"\n        \"OQXH8NTaJ2oJbJ2IT4W-Hg<F7%/\"\n        \"JdFZ*E.NN+E.N.+E.N.+E.N.+E.NC1Ar8a4(p$g[MnB6D]:(bgEj1EB&A8>/\"\n        \"G)4v2L=''13W-JuwPhY'NINI/gjM<Y2u7-Qt<8k@Yg3d`Ph26%@>#\"\n        \",i68%-GP:vkoC_&Q:jl&,7/\"\n        \"T.T>$(#UNYS.[ps1#pxRu-t-&vL4]h2#cTGs-+R]vL;1R3#lTGs-<-PwLB[<4#rTGs-J^\"\n        \"CxLJ605#$UGs-Y2.#MQap5#*UGs-ji*$MZAm6#2UGs-v1X$M\"\n        \"#L(:#U$`5//OB:#/@s'MG:-##Y=#s-Bd(*MUe-##dA/=#tJA=#%AY>#/\"\n        \"[6iLh;>A#?SGs-&A;mLFvmF#tSGs-NeFoLe*)J#<TGs-7&krL2ShM#`TGs-&CAvLJ9BP#\"\n        \"xTGs-_A@#MTvGQ#\"\n        \"26@m/\"\n        \"Zx$8#C5RS#?UGs-=4-&Mv<uT#LUGs-qvf(M2TnV#_UGs-&EG)ME`9X#p=cY#]4xfLU'WZ#+\"\n        \"SGs-)FhhLnc1^#CSGs-w1vlLbsJfLj@U'#6>w4AKY#v#Ner*>jP0$JpC_`*)>MMF\"\n        \"2A7A4U<t(3Z<^p9sHu(3-9cmLEe$J=oXW/1CF;;$rY3D-1iFL<nJ,W-[dNk+Y1uL<$G@6/\"\n        \"#)P:vSue+Mn'ADEjjf.#shcdG'G6IM=]sfL7xSfLOR?##'%HxLm[5<-.(.m/>1`$#X]Q(#\"\n        \"Q1F,3EsI-#aG8=#7'###+Sv;#j.QtL2]h2#WHg;-$gr=-[pchLCZqV%`9_c)b8m.L_8v:\"\n        \"drk,Jh7s5Vmd^Z(svu=##FHYJ(dI,v,jn(s-XJxfLvIvxu6:P>#Dlap#=b;<#Qxj5v8U^6v\"\n        \"QmHiLf;Gxu#g'W.Z`($#>KXV.7J?$vX*m<-rdF?-UVvp/\"\n        \"s*t5v+CB6vqRqw-iu:*MMx#:v=0ChL:pIiL0wRiL@>+jLgH<mLVL>gL*W2R#NmVW#a[lS.^\"\n        \"vrs##5T;-%5T;-&5T;-;U^C-\"\n        \"&aDE-*]]F-hv<J-q/[a.eF.%#pEKW2:C8lW'/\"\n        \"###>c3a%k<.AF9#RfL--^F-vg:bW3:J]-P=@s;OX4[N`jIJM-<I7vt47'N;IH.RN;-5MkUg_\"\n        \"-orl'&D]'aS_9)#v-0Y%NP;]Cs]/+:2\"\n        \"_x_e$9,i3tW-Y%N57L1pni(xB&&c=(oF$##+46qQj%?M(n)n'&X-mxu4/\"\n        \"WwYxHnMM>x3CM3aoRnAOgI$vM+EN<BP_MSPE,)7#p,FYdP_M[29,Mqb9=#fa;<#uD3QCe;k?\"\n        \"PYI2q7Abw],\"\n        \"9%o---.$QhxlKHNNoiBTH`blU;@Oo&'b`o,n5r>RqWg_-Ap-?R64v<-7(`5/aBd;#A;/\"\n        \"SQ5+c1)vnWq2ulBU)Ak^A9+8?R**'9I$k:Zp0m?nR8(;*9B%4&GOTwDINX'0@'Y?PC8cx)Cp\"\n        \"..n2i/OIR(7Z7)/\"\n        \"f)Tm8^92^QxRe]-(*QvRxx6=.kT(fq9xX&#8kTq)anr-$3Cdl+<DThL#iwv*VwM>(U(ip^\"\n        \"W0.7UB9L1pk$H]%;C%90hxfS'T6^jM_7v`NFK:ER#F_T&bOk-9NHeKY\"\n        \"_Dr9;@XlxuVMbr.gpDo[R@Uw9_OXk4=&d'&J+k^,GnpOf7F35&x@s,4B=JV%h1?'O>\"\n        \"DKgL652X-qF^>n#rJfL4fM=M15T;-:fG<-^^0q/\"\n        \"=$@gL^DK;-&L*nL`?M;-7o3B-be639I8Tb.\"\n        \".;gSjIYOv[%gb6]VO3;O4nR'OwwX&Xk4$##6WU@-JSGs-K>gkL.`@(#Ufc;-gZYO-Her=-@\"\n        \"F@6/$ZE5vC#F$MF&]iLP#S/MfZ/%#pAC_&F&>uubG@FN)2ZX$B4U^-t(V#vJJ=KM4c3B-\"\n        \"eSl.;/\"\n        \"q9LPiX7XC4PGk4*_-@B:+'.$5vagL0]Ld-E$5^Z,k1HM@A^;-0clE8muRvn@Q7IMaP26MIi+\"\n        \"p78]T9rj1a=cb;JgLVK$29<9sQN>$N'o>]ZLG@,oxulJToLJW9=#kCme'-uGi$\"\n        \"bMY$0#LCq^KZ(:9$9Uw0+%WedgiTdFDvqi'>oc4b<rOpBT]+4%7hRb.C16:9=I5-M>W-R:\"\n        \"4D,r'vJN%YPZ3B-o;p4&qo^SW%7aW-4@;L#9iH39,sw],;0'poPi[+Mj9ZX$SO42vlX*9#\"\n        \"KIIwuGpNYUEi099-)&sS`#hH%35^l8`<Ld4.bImLp's7#hjmxuarF?-.jGE(cDA-%IU)n+;\"\n        \"J2h-)=+uUNGPq7@U+42p*f?Qm7?uuQ2;.Ms7<j$35^l8N'*f6]#AgL1cqm8vP1O+jTXvI\"\n        \"pY^98?U&f-M#Be?kwNTR.$2>(s:x8B_S559J5,F%Q%PRabC>p/\"\n        \";h:ENs#;'#_&^Q_jts#.R)eQMVq)$#^XO63o,iI$np<u(rMZpKtxo<=%_'#v%FPu<S`F,k':\"\n        \"Z]%k%<U8Nqmd+Z#k13\"\n        \"Dm]AO]IT8%(a:5&RO23;/Oe(V7Dd2*xq?bNFc5^.<H`hU;p;/\"\n        \"'`8,cN.ZPgL_S9=#RZI?[d'+<+(BDK*_9X@:8sJe$=0a8&-LQXM2_Q58?5o_8AAL<-Pe#Y9^\"\n        \"*YC/F&DjVYT;iL/2<?+\"\n        \"Ks:Y&F1ilL]5$##7j,t$;j,F%oSSY,*&L,39UtO'x9@#MGf[$vDM@,:1d14`kBY.V=8=\"\n        \"JMLA_l8*DKpTDLp0%#Q?(#VZjha/54_$[jF_d=PPa8630E>B=uc-muD9^*-];'EIbh#i@`e$\"\n        \"lDbk4OV[F.T<BpTXJU[-9[adX<W4L-m$04V^vj$P%J/\"\n        \"C'c&1.=uGL[-9nn*%0djgL=igV-ngYU)u@K6vBH?a$xRkxu(/\"\n        \"DIM.b(:#GYE5vv5u6#*:&K)9iRe?FLN0:Datxu22.`$mS#69\"\n        \"Y(cxu(ndi_b>/RC::)=-1/\"\n        \"(mSHi@Q-BT:i'0.-_J+[$S*;%trLa3x+%dm:*Mj%qCMqu]+M;emaM3_9=#=Nc)M@LI&Mkq)$\"\n        \"#?7V9.fb($#-.7q7t?wxIkN.S;<)cxu;?Qq;&nNltdOGW8\"\n        \"b<usL.J'%NsLnBOd'b=cSJ$XUo.4F%q?)P+*E/FIXp(,ss4^V-cZ.kt[JBENrxf,&Nk1p/\"\n        \"sO:J%Iu(bNCpA1,NJtgLR>g08@qB#v=(>uu=PrQNS=@@%XpT(M7N(:#0)B;-V-fu-f'mlL\"\n        \"+[U*'=[S>6->n+sV^+68d+-.Q1]@vn*Q#1';^.^ZNdjn8#p9L5&MOgLSpls-jaxs7=*FW8`%\"\n        \"Q+`]^H`Nk%/;%.(62$L%##vx'Ep^vqJfL.oGa;jjp=:mKW68'UJf-WQKpT&d8n.7&>uu\"\n        \"`p^l'(QZ8p8<ggNSdx_%D7R<-k]2MN0Jl58[x]u(n5aOu>?CE<S]XKNSX'hOJ7a+&e>O:9(\"\n        \"8dU8R/=Q1/2)q%w3wY(+GKH%T3bO9<[g.?J$Xd=V4sMN9lRO:O1vVf0m*JPgw+hP_W'-%\"\n        \"9&h8.4@0:#gtpxuV$9gL/\"\n        \"E7#%6C:?%`KjiM5Tu(3?S[o@RNV8&f#8R3)vP9rl#i*%t6qOfbZdKN8>$u'+R<<-)^\"\n        \"X0MZWJwucY2a<Dtx6*X.`<-i#_d%WsEo[)_?o[<CFk4<_C_&*u>gL\"\n        \"%+RJ:(fBpTK>QIRTwv?YGKMnTp<RN)<.&l+mx(r7p2T,3Q]-u1Tihv%(ANj$iJfD&9;f&#\"\n        \"DTHcMRxfK%k3,x'AZm44[6HcMNjQ+#xOJc.JCS_m/i4X,a?Z&#vjc9M3tj;-*GCY:kq0@0\"\n        \"Mw6hL^wL2'9JX#1aAM?@x+,ci+okxuUwZMM8lY**c&[/\"\n        \"$'Zg<-][%'&P5$X&L8HY>DENe-S).0;ebQ/\"\n        \"='fKQ.c#4K3wIQ#Tj-wI'?u>q7ML?-diTawnw^m#9?Few'@[)RUxP'HNZed2&\"\n        \"Ye`O46UV8&v?U&#U<[#P2$XK:rYbxu=GgGMnC#VM*dC)<OO#+@;n)'QkAD^'0pao7?agJ)\"\n        \"UhWY-c^+O=@(9mV-]g);ThHh,j?u3)?]mp%5v,wP[>Vb8N$N$B%HQ9K[8fn$8*[rB(I,2U\"\n        \"eO-a(O3d4kGDoAeG+CA(>FcW?&0+w7vX:1M_j+p7_Vln*96BpTe:cP(rilJa8)WVS3T*)\"\n        \"XYc=k%*OHRaHs,fX;_ga=Es//-Eg#QU`vn+NTYC&&ZU]4Vj6Jc;_d5QUtF&x>9a'#vrR-Z;\"\n        \"/RkxuTiME-1RNN&l<*##+JSG#^2FGNc4+s'[QH&#Lm'1Mco5s-k9@#MkT/\"\n        \"%#@S0gL.AK;-fuR>%A1.$Ps-7#%YShJ)ic[<Rx2%I-*1?;Zv8a$9PCBe=GPFE>fLHQ:x&#1'\"\n        \"vJ@f$Li_8.\"\n        \"p0KG))s:RsmYX8&0%G&#@tdGMwmk%Qfs,hLw-3,;hs2E,@aEb$`<,_JX41dM4#^8'@)\"\n        \"UR84b'X8:E5LN1_oRn`/\"\n        \"KgCc>[.Qs]'ENt]#<-GI>dMm63J'mnjk9Ow%73D_QHMVg]SR5[ViL\"\n        \"P.+%U?ogY2w+Ok#71[<-c1bZ%Pt<EEgTbxu^pJQ_l+84vM:G=**N?_J4tqOfZ)Uq2iF.F%\"\n        \"3LZY,1>-t$rZs;-<I*^-N4&[8x-,GMT@$##('>uu_9_M9akC(AQZ7p/:/EI$nR:xL:Xc-9\"\n        \"<)(kbP9cA=-.n8B-aDE-OaFkL0^f7RevD_&)FV8&^fXM$tfu;-p1/\"\n        \"$(gH<ENaxTb@jtQdF9`@2N^I,dM@#;'#jI,5='Bb;9omkc%@hTQSndO<'35kZA?^8$&7c(.\"\n        \"M?re789.W8&Tcee4\"\n        \"OGxn$dm:*Mb%[^.qu,<-OkBA8Pf>Ga*iO&#AbOO'xUdl/:#l;-m3S>-Jw.?&n*wp7/\"\n        \"Gf_QTtDvur3o<<W6uxu-ts89oH=(&2KeZ>k:$XL&MOgL$9?uu]X=kLh60-<06QV[Zop&v6%\"\n        \"mW-\"\n        \"6G@jiM=$##4Si=c02bi_P9)#vf,c6>8ma-?4g:#&LjSWQI[87&;(aS_ogUMRl?8s8j$qJ)k^\"\n        \"&f$ZdEe-JVd--fh?p7tv&fbU_wb%In<T.lC8]XPN5[$qip;-wg.q$aPHk=KX<r7(T]oo\"\n        \"$a'68:uO&#3'Di%pJAh>,Xm,M;lpV-Gv0jr<?+<'1J<?@grP+`l*VQA)AaZ8D*[#PeSMB'k]\"\n        \"X&'>k:a/ZP>HPXbZun1Ar2'eI7,oipU.$r/ps-#JxnLGi$3#;TGs->V+WMJ4$##Hju8.\"\n        \"t>)4#/hi,MSmj$PdqWB-Z;;eM*I`*N:FF3N:I$29whk]Y0Zmq)V`9,a9Prw'xoce$DeE>d/\"\n        \"kAC&@UMl][sR['^&DRsKDNK*'R95T>%O0(4l7db2V-0G/k4>'>p3<#xdva$BXlxu<nVE%\"\n        \"8(`9M[T*nA#0O#/Lk,b-:jiuTI_BP'SbJ'Jr8AhL7/\"\n        \"RYPqfV6M9O5s-$u*H9`.]R3S'LtO?T16*LkG&#5mKcNEWgW$*oRE>qxtKMGb4s%$I:F%Q:\"\n        \"9T([<Kv$k@OJi24Kg?i%&^,TeXV[\"\n        \"qoT,3,(36)fVZ)M+4[mLeVZ)M_XO`<MV6B7sHel/\"\n        \"7I,hLGVRkAsaJqDPM8QC;CvX&%2C,3'B5XCe9JW-Hv;@B8Clxunss8:BjOvIeT/\"\n        \"%#Mn.Z]6NYO-1@,`'2jS$9if-Lalop%Pu'[b+\"\n        \"l`+dt9L:&Oo&tV$'Iwp7s4k8T`nmjV,pG9%+ueTR=>D#QOd^lR_,11:M6Wr9R;]Cs-k/$/\"\n        \"hCX%8C#L#vQ2OxucOmAM'Y5<-n?W.(sW$38.dgJ)g1@0:7ixhtR>mX$[F35&qOd-HYlwE@\"\n        \"@X&A=YNF&#,PA'=g'j*.0[IC8:4chSxv69BKWsN9j_d9K2x]D*U/\"\n        \"-QUWo$1'de(58N]bxuhp8#Pg<N8.9Z(m9E8DS*SVc>P>@d9Nv?5&#9pTjBO[aX(6NS/\"\n        \"=FR4s%]]@C/'WP`<x2/d<\"\n        \"_9AF%EXa7O`$3E9fZ0m='DW&=g<2^QM#]%?lau3DhK:@HLU*.-Gn3WS4mJ8'6M('O%of0'\"\n        \"iMRfLu0-M(n>W(&ruGCOr;^sQ34FOPo#%MM7k1HMM,uZ-._$w%(RC`Nq9B?.WMwOf4nr)N\"\n        \"7a4/\"\n        \"D0&$K)_VLk=7gg@M4%co7Cnc'vaUakT4X'kNu'JjLWf$R8tqTd4w&rOfWLZ'A2fah#u?h2`\"\n        \"dV/hq3RH=M17,LP%L.V7..(-=S8,O/iF72''RIc;ZL]oo(cF&#5a,K)QECPA+g?L,\"\n        \"Hpkl/gGO8pKcbjL>JhkLm3O'M</,d$OckgL?fG8.*?Bul/\"\n        \"hvgLE(',MDA6M&$l@#GJeG^ZL7@B&bs?4vkjX4iAYT9v)`_@-AFD^7m2orTlF3@D[X/\"\n        \"06M8nWHS$tP_WcbjL@tQa$YH,e;\"\n        \"b3-EPZf^&,;e.B$[f)r*D@nV7@Z;uM36ha<'Sc=;<<]jBE95F%t3N0C^6SDX]&[/\"\n        \"Fo;rtLik[]Fbvh4+Xar(?4N?GP60x.MRKHQ'03F5$SdRw%E/.D-aeH)%B3^<Q$;xj'/\"\n        \"vEE,==Nd-\"\n        \"ThP&m-=-p'@#@<H`aUO+mAU,bPcun%sYBU)YfhJ)/Wl6;*2PYZ)[2V*T+5p/\"\n        \",Z,NjOLv)O(KTY,=tZi9Yk)I-_-?X1JY(<-kUX8T&RSg'qXiJ)LtG&#%2,-Mj[I$*giu_/\"\n        \"c+h&$jkuI+\"\n        \"-[iGrN=]CsU>n/\"\n        \"UHC(a)It<eM6aBL&0a5=-^.B<&$89h$IBfaN4fHJ*F=H6Af#8_80gfgLY@:29-JlJaT`hW$\"\n        \"QY$N9vRkxuk9aM-7:*FOxt*0:%+63)hJD#8vCP_HmRi:RWQ8XZMJZu7\"\n        \"3`<^QD/6M*.w2FW;@CD(<1)HMgU'sQH@A^$/6F&Of/\"\n        \"hT8[LsU2Q+Q&Qth?BYgjfgV5blM:nj4j`O?_T&0JK,tMl7[_a>7ZU[xVBP)XJcOsN;5R._W&\"\n        \"dw-:a7af>K*'R[*8<G.$;/n/`A\"\n        \"go,F,qE9S)2>m39b0_-6g-cG.P9@x7=5W6MPx4CMf<]Css.86S-GCUTKhAW(e@pS<\"\n        \"PwSgMPLY]1rL5QAs]txudb5E5$a;BNauLweLR9-PgDQe%B*?&&5eNnLUYi?%RC$##\"\n        \"R0dCsGb:;r\"\n        \"vq.N%ia-9'T;rkQ3<]CsPo^8&/\"\n        \"j_9R;?^h:'iQd+X0ZI)ii2L<KS`d%'QXHl^w9eSQxVIM)p@S@U_F&#sVBeMscZJUkqs`tq?&\"\n        \"_6YTII-bRY)NKCSe%$@e0%q'o,;O^bxuPt-gLQo)$#\"\n        \"2+Gb%;Pw],)Ai>n7p3<#cH2*'44P&#qI1&Ov'dk%*=*(O^4g,MQ1wa(R96J$Js/\"\n        \"aNUMVU%ar--N9rc58N)Lve:1d^?\\?9;m$d9x58qiA,3b)W_&.x$HMKw(.M*%G[(VuhVQ%xI$\"\n        \"QoJbs7\"\n        \"*nG(J;8=p/P@dI'I%HdM[GF)#_^J/\"\n        \"C(`q]l$OKG)ip<dMd)V#v<-tB-2fp0)mHxnLb9:5'A8EG)2,vlMHug;-GRD=-R_q5N/\"\n        \"3^H+SCx],$bFH(Bu0c<eD&G5^<+g$plH)+J*'l`G5/6K\"\n        \"-aX&#.fD_&`Zv%+xcq^-j*e^?L[/\"\n        \"W(TEw,M#[(IOo8*0,3xZ6NDZt;-W10sMfaU%OXAG2r-k1HM]Rb+&ha0QCusEr$_Nc)M2D?\"\n        \"uub)Q7vj0cp7;Yw],:ggJ)*M_p7q#Y$0O@N9'U1$##\"\n        \"_(Ih,O^&ON<RL@%iX],X_&=J-d-2H-;lQx)&Qe'&[1>gLWRqi9Hrr224Cq5+DCXh#b@>,\"\n        \"M9WO4)wTBi:I*b9'67tRh+`/)*#pBpg@Z84VR.PW%3=Z]%X)X8&nTFW-ft&NET645&bde8^\"\n        \"Sn)$#LR1gL1_%&Fmn_v@c4dxP)uie;L@Z8&hs6:&k(S1p9mE,MEh0]XRF>#vB(>uuV7T;-\"\n        \"veuV$L.x],ck_]lM&,Sj6PYgLQcAGMN:f_)i5i*MKs]32D]5E8%voO9oUF&#S;CX-m<;m`\"\n        \"eH%DEnn`oo;Bb)E=Wv+sAvi`Ard-[B9@6L,>t.F.81r%c8X<m821Ne-nsw[>TVWt(hDFP*c+\"\n        \"^8's.VO9;N+L>ckT&#fVeuLb1bw8.,A=Bu#g+%BvlD.`w=T)2(dD.0(ftH(IGk4(9W^-\"\n        \"6RLw%UHJ68OckxuLCrP-:;Xv-?TGW-v9u'f5Y%-O.LXdt]#qCMZp@o[*]AcM^T_TMEf0c*\"\n        \"DMHN9OHF&#v=bxumD(N9X<)F7a&AK)'Nt%c$sh&?rOCWGt9IeO]_&K84L.&c-*Y^-cH%DE\"\n        \"$Sh4$[)G_OLB?;Bs($aObiSS'XHV_&E/\"\n        \"lQ8rJ5R*2#=v0E<2ekr#Mq-VE6K*k2Rq$F)A(&VSQ#G4=tc<TddW.('>uuB7T;-)oMY$\"\n        \"3uMjL&5W+9kV58fq1F/)N`,_]9?k+-Ns]WQrrvE&\"\n        \"AA'dMI1$##Xbi;-Lw=w$Q[vv.wMfFNx:o7&aGoH%8VUE#B#u<'3lHBO,[A3/\"\n        \">X#Pf-_9&v?ev.:OgRk=P7OfQVL[[B@Rma*WDg)N`aj#M6ZuL'P[`a*Y'[hLPar`*+Ul#>\"\n        \"KDLLlNZ@F=\"\n        \"LhrB8-DbxuYnxR89TiOB37^q)S>$6/4v?4v>*7G;gSkxuEV`.MM@;=-@'[6%`#<qMi/\"\n        \"uHHZM(X-A^rRAoi$L*OBl#+Ni,iLnD:@-)VR3']TH&#Qs+s'=?3-MCAT;-Qpom%jb_8&vA9&\"\n        \"c\"\n        \"(2mx=F[kxu%*P*sYk&f(-Q#X-w&se-qZu&?j=-0b*sRj8Ip(4F=K]9vp7,d$NWEq*12-<-/\"\n        \"k[f$-%6;#aE)8v#A@^$x9@#M53.;#*Ag;-L7T;-DB+g$D[;eM=e/v&>?<dM`1$##@)d5&\"\n        \"v@txu:3P@R@qMf:+_DU2POdIMeXtfL1n)3(j*rp9]'778;_at(O=Z#U_U'L5)ofJj;Nj#&L'\"\n        \"hi#qFqu(Pku&+CA$T.3&IG)]T=g$<o2F%[)A+7Y:gb?K9L1p?l1e$u=7]]hg0h$P]-#'\"\n        \"wuV#G01cxuZ_=O';v;?>0xk&#uX;5&4-jmAN$,42Gaij83RX,O@JK*Xp:=B.S2#?e[_If$:\"\n        \"TbY#K_;;$m1@['HtEM0Z'&/100+q&.su(3-00A=6g#k('Obr?I_1GD2MOg1Wlk.#Yrn%#\"\n        \"a;L/#cF_/#n@O&#%/\"\n        \"e0#%:w0#+(U'#lI:7#jTL7#U7o-#Ln2'M^a5Li:?BSI<Zm`*-V'v,gYNrZUNMP88^\"\n        \"329DlAlf>]p%FxH/8ISmQ+rf6cxX4eu:Z(PC_/ug:Vdjjl7eVDL&5xX:L#\"\n        \"L9GD#$C9U#:STU#Be8E#8t,V#K1Zr#,4,b#Z6FA#mLt]#2INh#BAWD#S^8a#XGMk#UxSE#o*\"\n        \",##BIDmLCVjMMMAw+#-ol+#+FEjLL=UkL>qL;-?^h@-s(.m/6b.-#n[P+#'6m,`Y0nlL\"\n        \".$7+#?rH0#xbY+#a/RqLAoK;-5:Ju-,N*rLb0N$#u=F&#/\"\n        \"5_^>r:q(<NuOcDNadGEkKNe$g7OY5u7T;-wKPH2of1$#tLb&#*GY##w'U'#%MYS.D]&*#:\"\n        \"rls-r'trLH=pg2fU5;-JHAYG\"\n        \"^&'>GkKEM0/Yuu#(Sfr6Ap;A+T<SS%?-,/\"\n        \"(_#12',IvV%7LC#$a8b31O&`c)9kfi'+I;J:FF,W.6#^3FJ?&F.:5iD++o5<-0.[mL>c(p.+\"\n        \"vv(#<tfe$:9Jp&bm3L#RAl-$..X9VsAQlJ\"\n        \"+;0?$/#[o@8]'Z$48fA#3fC8Jt]5j(/\"\n        \"r[PB^3%W%=aF#?9OKh5Q+T;.fo4m04LAwgZ%rr$@b35&Mi6eZbVMkb0X;s%7.;p&7&7]9t+\"\n        \"18./cHd3:1VV$iK*g2%S:9&_.9>,2(&@'X=no%\"\n        \"g5a>$]hi2(N*=wTwe%/1SpV'8/\"\n        \"neKup]G:;:AJ&#>x1L5[#:L,tri4F_6(,)SjsS&RmZ-H59*XCq:HP/^4u;%i?9e?/\"\n        \"^C_&2#7@'%^a_&&q?e6$^W-ZL_.W-r%kwp8#$kk5$TS%-GDg)\"\n        \"#eFL>^U'15bBQX1v.FcM4$3$#%4DH-,W40MPmE#.$5+gL*fQ##RAP##)eHiL]jkjLbrm##\"\n        \"uh8*#A`:5/9jd(#a>gkL<I$29PYX&#YuGq;bVFP8.#fo7L`YSJY)x<(AusY-BxsY-Y1n--\"\n        \"h/^Y#e-el/\"\n        \"q]Ke$7kx+2L`)d*.VWh#$$,F%USSiBr@Da4,j?S@Y)x<(6gG,*7jG,*2CL^#O].JLTNoM(\"\n        \"qN$j:mYr/D,:?M9&)Le$,>DJ172%a+aEQ8/2a@X1lt&@'Agm.Lf]SV-1*G']\"\n        \"%Xp%47Q'N(C=N'JO[FV?_g)^#>f#N150x?0E=g+MS;)pL^mFrLsIhkLxqJfL]6U*#KT2E-b^\"\n        \"./MCVOjLaNEmL,1o(#uI*s8J=pV.Niw-$1pP`<YGNk+(jW]+&5-F%QqHR9Ibgr6l.6D<\"\n        \"O0do7hG]i90V644s]HY>]P:&5h-_M:cO]88UbMe$o`Q>6G.<s@g&_w'.YFcMxQC`N>0C*#@\"\n        \"61pLcG<,#W$loLw.D'#'Us-$U%E_&/tkl&*VUV$vU'^#:I9/DS>PR*c0>++F$OP&GqBD*\"\n        \"Ee')*A.?>#]jj-$uQj-$.aU_&(%ai07wMs%XQ3L##k8.$wmCG)>0GJ(qiMY5<ArD+$B]3F'\"\n        \"P'DNxQC`N*@,gLMHrIMf5p%#qd0'#A0*=(+cU5'eUx-$.RP3XUi;FIwFKwBvTj-$X5;D3\"\n        \"T=@wTDSOk+K?`J;,xF2:K]l-63x,Z$9,MP-f9FCM2ecgL83DhLu9-##vNhA#SMf_&SF/\"\n        \"2'6sq@97?*XCDZAp791D)+r*4H*Vl8>,?\\?sS&FGl;-`o-A-#BpS%%U3-vqA<A+P8:c-I1$#\"\n        \"#\"\n        \"Ao'Z$YEdA#AQS5'PPh/\"\n        \")k?\\?]OOA(w-o'ChLa<L1MoPBcNx:-##bROONnr3B-96ii%ohL3tcQP_JE0i>[s;-\"\n        \"EMsehEPDU%I-Ne=8Mk7-##6Ad3ODL;p&bedq)>:Te$%m8eZe/8>,-:,@0\"\n        \"cg;A+ZW3L#'1wu#4[PGER^sJ2::W_&SV_e$4Tpe$7xho.dZ=G2?3mcEL(x],Oq]pA0j_r?\"\n        \"UH3L##.f'&/M5;-R-iP0*=vV%I=g;.9B`k+x$]f1QUT;.v8o`=^f$B5m9X`3)G5W%p>)L5\"\n        \"qSou,gALM'.^8)=LsoP'XGpP'MOT;.V8lD4)uQ-HtFwWU0c[c2Cl*^,LB7I-<Y`=-Jgi&.;%\"\n        \"AnL,]U).AEaJM%?+jLQ8xiLOf.QMV2q4.@=7fN^sJfL)qugLYPFjL$WP&#5p/kLL4$##\"\n        \"Qi?x-^XbgL#$*$#14RqLTH_w.dW6crj&3`sn>j@trVJxt;^2&+[u&#,,f487aiY`<ubV]==`\"\n        \"5AF[w2>Ga<juG+RDVQLsASRe9(>Y+d<S[5>9P]mnF;62OIq;(mq(#:rs1#?(02#E:K2#\"\n        \"JLg2#P_,3#WwP3#^-d3#c?)4#jQD4#od`4#tpr4#$-85#)?S5#/\"\n        \"Qo5#6j=6#<vO6#A2l6#HD17#+C0:#;6H;#1,@S#cInS#q0tT#FHmV#Oa;W#v[1Z#Fkw[#\"\n        \"AEEjLBRKvL/2DpNY6J8%\"\n        \"T5>##Lx26.A0xfL(@,gL5_7:.frQ(#OCvV%hV=s%*rh=2)_HwB6aS:DN&###=nX]4vQ<)<`\"\n        \"e^&#Zk`e$&xVe$JG:;$>,T,M_9vfC<L#/$:osnMuPgYGb1_VI'(-Z$[ac8.PYVMK8(Te$\"\n        \"PBm9MK7$##>&LF%.ug>$f)vW-F,Ye$=v^@MK7$##P]LF%.ug>$*NV9.E&*#Y.7h>$5-#dM4#\"\n        \",sQ@XGf$TqtsOIqUaNswXvMt*kB-4bc8.=YZ`a8(Te$=&q@MK7$##+CNF%LG:;$Zd+[N\"\n        \"5l>W-Ea5`&.+h>$xv_&O:9(5o2UH?$nI:;$RkP=PEK6##0cl##;o,Z$Pp9W.<PUV$ClXZ-v`\"\n        \"Te$)x,Z$YOGdMHdF)<#n$%'/x#Z$3MmW-K3r1,X4Ne-Im/KM/fuS.TR$##q^#`-l'X['\"\n        \"/%-Z$%uYHM>+>s6::gI$fe`mLUg:hN4cGW-Xh%+%5UvZ$0_i,#Q:Tm$S=;IMIQfG;nN-Z$\"\n        \"aCK%Pg2XAFN-r$'Su;E,DU5hLa<iq79'*I-U-:K*.6]0#=o,Z$HFO88:ch4+AbE=-CCxfM\"\n        \"H:sfU[d.Z$7v_&O#BBW$Hel'&<UE=-YC^@Pa@Pp'fp_7#):=SPM(vW-*+[e$Wx]&\"\n        \"Zn5bS8m0UB#d8>ula>`Y5)(LB#qqD<#d+eQM74fQM-M`PMbTjMMi8H`M5OFnM9_`=-MscW-\"\n        \"IDce$\"\n        \")FLR*K?be$Wc_e$Hb@5/HjBSI`Mu`4h/\"\n        \"x34RxV_&RxV_&6$Y_&r^fe$<ZQX(jEfe$e4`e$U]_e$Jw?L,-AZq;0i[_&0B^e$GxEX(\"\n        \"oTFOMO&xIM2H]YMESX/M8_(DN#&G>#k9HL,fNHR*\"\n        \"Z-HR*e)VX(3>NX(-^U_&3pU_&V.W_&MiV_&Axde$C%/\"\n        \":2qZfe$arRX(V`_e$^u_e$O)QwTb+`e$E-be$+4ae$4LWe$lJIbM0'xIMSU?\"\n        \"LMBDOJM6Q7IM$:>GMa:>GMMc'KMHdhsL)PQ##\"\n        \">p$01h/5##*=(7#=e[%#a-ef*N^Tk9+wK^#/trCjXNZ&#bi$L>t?Rw9:l>O%H&###/\"\n        \"ji?B4n'kbO`qr$($O9`=,Y3F.*k-$#A'DN=w$sR%oB^#S+FcM^:-##911[-1SVq249=R*\"\n        \"NSu>#\"\n        \"W`[wBm_>.#)S?i2,6B5822I&#l7at8vK$c=?)Me$H&>uu_nBHMYM6##.Sl##,T2E-;X@U.>\"\n        \"xL$#QEA>-C_hc*wCSm/DC_O;[K85<mKMA.i[$l;of9L5,+'.?8aa>$&7,fQYApN,LKWM,\"\n        \">=dKFc9<MBlEvsBbI'oDac%;H$E'kE'3be$MEbe$SWbe$Yjbe$rA,cHJUg.U-\"\n        \"ggKFdHKrLT3UxXl]gh2YoPe$0;_._]MYH3$F[cH)a+SeX+A>-mnRQ-F4@m/\"\n        \"1`($#<rC$#H:)=-EGg;-\"\n        \"7i&gLK@?>#`/\"\n        \"g*%Mf68%)4$$$*P:;$g+nw'?p^e$C&_e$mbrr$TQj-$7Iu'&k:$##.=Mt-wDEjLO=6##vLO&\"\n        \"#&o.T%41.5/Kd-GM]ex@Xu>&s-UP/AO.HcM'CbO`W*P+Gr8$FN#S/Y`j\"\n        \"v;Rk+so.5oI2jA+=`YAFW;[SeGg&F@PD>>#H.m-$OMYY#B5H,*)06;[vRkA#/\"\n        \"oA,Mf:-##%ZG-MvqJfLA45GM2hq@-&,g4OXXZ##>kF?-F.Q]N?\\?+jL(o)$#vfs>$uJ4-\"\n        \"vNs.&+(J:;$\"\n        \":1E_&+Y[_&:B?-d1bBr2]:E_&q&[`*rm&/\"\n        \"1E#uZ-nk^'8eM$(&Vtj'&^sVe6VhK:)f)3^#3b7:.cEq:Z9'$^,WdkJ)34-(J(Bu'&w_8e?)\"\n        \"A?LYK=jEI,>5;-ITKFIFTA8.g96vQ-/5X:\"\n        \"/5g9D5NR_&QSGLM$Hwx-_FbGMXJ?>#3(`?#DLYdMiX6##new[#(AcY#Im>gHRh9a#N=151W/\"\n        \"IS#s6'U#C<ZV#He&gm@5kW#E)tYhQ:6Y#bLs`#wYJF-H-%I-K.;t-Iqk18'5_^#oA``*\"\n        \"d)k-$r=dl/Bd.L,Eqn-$;QvddXQpdmUCow'Ov(@'LPUV$,Q]'/\"\n        \"Bi'eZ^0h+MVB6##EEreM#xSfL3D,s-XF')NaRC`NB$/\"\n        \">-EEreMJ4$##quKg-=Sfk+6Y+dkQ%1kLUr)$#Df(T.7xL$#\"\n        \"fbP[.=Rk&#vfYs-tlRfLBmcOML]tJMARP&#*E595M-4&#Bi8*#E)1/\"\n        \"#Xpr4#'EmV#Y8$C#SK-X.%2G>#Z7T;-vX1H-Cn[q.P:P>##rh3=TUfi'LU)da=P?`a*hnP'\"\n        \"VGP2(8?(^Y(7>YY\"\n        \"tCO&#-b`=-K3xU.gKl>#^<^V-Lr/\"\n        \"4+Je:4+qour$%qeL#VU5B#6$(,)E:3W@2)IS@$DO&#V-@A-TWXV.k]1?#qfWfL'CeA-WdkV.\"\n        \"Kc:?#FS^V-U'm'8U^ae-Cjo=#(5T;-']T;-5Rx>-\"\n        \"^&:W.]h1$#Z[B;-i^4?-@4@m/\"\n        \"s<#+#n[P+#P&eX.+n:$#*bB;-#^_@-9Ag;-OV_@-c^d5.Y@=gL&;-##]@FR-KS.U.>qn%#\"\n        \"2Rrt-m+vlLJN$+#`PZL-='i>8Yg'^#0P9$$aDXf:n^@>#\"\n        \"jNCZ-iq18.fers-Cd4DOgW/\"\n        \"%#b+6.'-FP'AiG4;-,NQ'Aw^&.$RD>>#v>[Csa^%Q0A$u9)#_j-$?oiEI,kOwT6#:kF)_\"\n        \"HwBL8YY#R:A`W,5,pSk4O@B[RJM'Ie'Se#r-.$'EC_&i#PlS\"\n        \"Vu38]-uiT.,Sl##x5v%.+eHiLp&;'#:>D].$3A5#JHRm/\"\n        \"G7I8#$u%5#=UGs-ohT%Ma`e^$2J#YCMQ<,a'o<X(2[n7RUS_Ll*Z$@'$A'DNr%9;-)lG<-\"\n        \"xRGs->#&iL?\\?VhL^UiS%vc%I2\"\n        \"5ios$%V:=:Zgh?#O1PwLP&^kLGvX>#;VMH2c-8j0P#NA+e]'7Be%Nw$VxRfLbSC;$Ctw*=q-\"\n        \"1Y(&Ymk,QB/;6$QoH<N[`uGTCL`ERn%;H^%<v6s1^V-KxvU;)MjG)brlHfq.Hx`h<T.U\"\n        \"HkTW1hivw*.kvDF9l%9%u6_ipuBP>#8:i$#sQ-4#*,Pj[VTLk5=v5SE?5'.Z)]\"\n        \"bgL0xHTMLhH>>Be[[#N]mb+J+,gL&>s,)T;EVC&A2eGa8JKFs%M*HY&Bg2JT;-M1,j,)\"\n        \"K8jjE%W+;C\"\n        \"^T/\"\n        \".*LGl:&S,)SCwJ+7DiZVkD)ZXVCdI:K2Z$Su-2v&GNvg7f=B5JlDjZr`=,&D.G-U7F-Ko`_,\"\n        \"T*ZL2?ZWI3PD58.*0[E3hoP-3E#B+4%Jhx?0VCthAG$L2=GQh-6r&<Vt;*7D4'FG-\"\n        \"+uUkLlbh)5[w`V$UNNh#wX4R*ogaZ#S0,##_e*71+*Bt,=;iN%^&`V=0/\"\n        \"T&JK2h'%2]P7Kn`<l/\"\n        \"Y<ab?EMc<opdvdT]11>E3sUg@ImnKL@3nD4pkeU-J8EMH.lFQ`P=*i#dwsCLhQ/cj\"\n        \"@LeSNL7x_;vg:be7u^QJ<AX6m4;b;+]EkgE?EocE@^6IuuhP00G$VjY,4cbTcZRo^5mVE_\"\n        \"8AqR5+uxK5O%,n5:cnC$E.YpWfVJF,O?Grg<mJ9MgN@-X&ndI?EAFdq3?(xB`d1De/PKjD\"\n        \"guR<sAUm8iM72mZFG*J-;NEtRv&GxrS9>PT8SpsD12@IXAiOCnpHD*3UoZfeiXtI-T*@%\"\n        \"rfi?:t=E5=tGo#=MiL7kdf9XJMT/bBhu=l7S25=^0HlI]qs^hBH'Hc+[pf&SrB=AYYq3P+I\"\n        \"&,kZoc5*lR1IhRoa;UT;<#bR.f=/DdqOC/\"\n        \"u&os,>F@dQBNwbrk5BK(Zp(,6U]XCGc12OMs$h0hC<*Ks8eEdKFe[tnAcD5)p_9sod7[3]\"\n        \"MK56KUP&pdEe(7^ak2LK@%c+%,>?NX1o9VX5\"\n        \"X*c4<mB:P6Js.CccDUYnhh:cZX^u11wYQgY@xtV:O;+tOf'D@aujnB4#OWAR&JuZp?=\"\n        \"9wZqW]a`%DToZH].$D4-f83b6l9ZrBEOfAn*KLPrUdX'xAw2)2,##K)9L#O$5L#BQ7v$ge;;\"\n        \".\"\n        \"0T/.*tX-fVbXq=-Y-v>IFXi@g%`Fg(uKEC,eb[S'U2iG)`*/\"\n        \"VJI?e:8]<Zrg:.CO#St*.gXtQh,``G>#.fCv#rng[27+m<-BO.U.0g>G2?uY<-]H,h1p&#t$\"\n        \"^'w>#7x_v#u,X2M1,`>$\"\n        \"I^3M2BlbgLebd>#`&eY#8']u-#MhGN%iQ>#-K+FHa9if58qZ]83w&k1=Y[72:h3P2Xq$61_\"\n        \"QiM2i_-MVbXI.N0?ojLD?sdGv.o=1p6f5/_Id)>M-3[8`fxS8=)Z$0q5IPNbSH>#&p^kC\"\n        \")C5x'VK7AOZ'ZiX<n)2O2bIC#m_J$MUVGuB)`V(8VYe6&bNNh#rQl'$L3BK3)-V.M.\"\n        \"YsqShO7e7`TH=9xMA^'=O9S@oA*Dl7,,p_k>vl4I*rtt2Ax(M?uS5?-Rr0<&vt3kt]N1?5$&\"\n        \"T/\"\n        \"Im1h2kp>W-]wKqXJ'$d)s=A:T@L%m_rb5xLQ_G%2@tmk-?eVT_fXXQf`8ZSP)HH0@3N0FJ,\"\n        \"c[5+Lws;QOnG#OPPS&EjZ=g`3Qs>LBN4p/mk;^]OaK9##igOCU]<rbCAAwM8x*OjJ&:vI\"\n        \"=%0cOvJH4hAfqS'dBIHV2)=FC2Tf:bE=-WR$7RGFRmZ'gQ:'E_bs_tmLThTD;Hs<08+\"\n        \"3eNCrZMH%?sxa@j:]<_H?&eCB`Jb<)A(,i4@AZTnoA='F4N2*KJ-*>ST%_+]Bnn<,27^u+G>\"\n        \"V\"\n        \"n*<fkD<fbp92c`nGq4m@>ib/\"\n        \"9spXD)RB1Xo%YH<N':1oOE'=B1?+U:.QAQM8lQCBfgKvf0,cLIAe<8gV,RUZj:[F?(id&?+\"\n        \"dwbItvC;/T/YflaJYI^DtZ8XVj>k&_rwpxi:=Wh;KTZ/3\"\n        \"3;V.p2B&EPUp=7TK8o5h.sKW7i4(O_.evNqIQ]%=F%$`)n@QP'I>vgMjtdRD,@IG(1(\"\n        \"8AuHA@n4eXpfhog4_D,0BpIjS%[u-rB#otJp.g%hn,&f*EQB`)5cSMO#,99alJ[0,]kLtP]\"\n        \"kL\"\n        \"MUl<&Cr%I2,Pn7kicwxJF%'cXp^hH8axsJ23%?0EP-Rf=SnQO(RU4g1XOdBIfl0eGQ@\"\n        \"Tp2lMTk9UVrS&VhK?-id#&JjG((,YW`>5Z&@UC14p:9rgoF>9O>,<9=/jFa/\"\n        \"tk1x,o--+Cjq.\"\n        \"b1(_GK^XG-*t1B-^t3GHc+/\"\n        \"s%tnMGHEg[rL?&`_,]vEC5@T*.3W4IT.CBnh2jcM:%:j0w9Xe;vucVr7[0rjmN)YXlS&BZr`\"\n        \"agA&>623^VXjWj]6lqrhaZ<Ff:B=do2sIh386]QC89i@C\"\n        \"eU$^tLGAxIA_NKbeS5<NPn*sQqE+#ZIx?qIf(vT0/\"\n        \"Zf`d(v;T:Ha#s'V#fu=>6)_,;<dS:9%3X5aobI.M&v#*RnS9W,/\"\n        \"rOq0V=ugu`uh]6,)#2YF*M'jTr5up6i`.Fh5K`B%OX]^d6PF\"\n        \"^^VJ>&Pof3X/LN#heH<L5_*7&HB#)L_bTb9@dOhRUqLN-lk0<_^;T97@'K&NWv9GGX/\"\n        \"'W;3Vcog4iws`qd*t=*whmDvco#>XB?VuoqFUd@FrC_3kTC0Zva=k:cp5V:g+T',-\"\n        \"uQ9V2PGS\"\n        \"j<itAI(b?V18;w*%f6KHN5AdBot9#HQw5uY+lHMN@68Roc+T1gY3u9/\"\n        \"VJ<]5hK-_MZmI00vfK6M.6ttl^)hn#3K84jXT[Mg_+bkn>.YQ@kc1pD=wUi:xY^2+fmG&\"\n        \"T7JmaoBJ=g>3_*ak\"\n        \"11A-%QiA5AEshn=W3_St+,kadd#<AeGDWkd:4/\"\n        \"MK4RYtHd?KZ.-p8uGT[p?h*e=cWsS1E5,T:50V9J..3jGx;4KZ7jp15[sFWw(H%2Lntg&\"\n        \"YH4BPVuLY2]G'po,NtHSA23@/-c<rmTRU\"\n        \"9bS4^,6:1S`%Z=MMf6T.1$5L#/\"\n        \"a7n&8dlPaHr8k:TqHKlKcW_U2BrfCMOV6&GNgk9a#(B#3OTk9k+&X%[DG>#&2,##Ru&:)`-*\"\n        \"j9rOkM(KG(W-]%?_/TNP>>;6>G2KsXo$dNgn9'nJb8\"\n        \"vKFF7-JNK19>2eGHO4,HCFwgFW?(EYHh6f=[J](':Cap.>%ZlES9+9p?gXR2-hv#+(\"\n        \"ZMs7oCf4M>iov77RZ>HC59n1*Ll(N/];e./P%/Gde]s8YMpm&VlT>#gK/\"\n        \"SLpbu2*:x(SLc8h7$\"\n        \"^?QK1;dX6]W#H6KZH+b`*3d6@v18_?UDK=B&>ILjsr>FVm[,dbmZqRmct5#8cN1^mb-Do[\"\n        \"c5Fica@k_3)U+4,GD@5r#:PKh]l/``4ocPa^=c.98ZGc[iTS?PG4?SBhZs=c_'#Fc&<7P2\"\n        \")k`^r&/S/\"\n        \"_#CqK#=NDM=BVVd4h>xJ5%.;#U'vk8F]oE$f*Wmf6rwb1bPt`WbH)ZEm_ln'%0blB;S62%\"\n        \"1N`.vQ?CW(@GTYTLN.+QVm%.Ci.AE>[L+al(oUeX13gRfhDHGG`P4#Kn1-Ew1\"\n        \"PSSL_CxpE,?P5$W[KbGDgw's4O;qHo)7q2MGUsnOOPmLgN:Z:<`g,dru%Qag:sKe0Fj/\"\n        \"RUNd;Q;#?voT<1B*&]-j_s2%4DfT67=,DgFiH$1IL@gq9b6eL>+Za&v8u`lFP^oorJrln5c@\"\n        \"gvlcplWO`V?j5PNPqu$@F*OGqEJjbF;6G+<Y-+WC$'Aq7#]5&W^<W4]b;7X0kIA:YWo,>\"\n        \"bq1U[Vb/8o@^s8(gdnHd(VMgYcr:&u@D4_j0r+jOuc_.bq,bJ)_;o:ZWG5YjUSpjBI%(M?l\"\n        \"'A[1[@Hk#`]o4N3A7>0KF$H<I,,#l#'c@Qp9wKo[9P_=tSr_x@;CCFHw88$[r6l-$>.Ph#\"\n        \"Ws-q$<V,m8V/LT&QPP##O::ElD%gO;rx?@-c:(W6jr8oD<SiiFaWCtB+5?lEtgIUC8pPo1\"\n        \"@7^U/u;0F.2rU[,^t3GHUhUW]0e#%..ZE+N8C?cG;#YVC/\"\n        \"R^8%C@>a3BQRL2@Q3.3JD#c4)'@*3H^WI3)PuT7<]%jLjgxc(G'pi',#5L#^B3L#Y[\"\n        \"9utrSQr_`mr])cBkI#WQ7`7$H/]/\"\n        \"SXGR@&i.)d$mTrW:(KNN33C*NknCPS`<&+?tdcu@2,+gcg]OM6SbQrJnv>B;_RhSA*@@ee_?\"\n        \"qBr?$_o><s^FSG3S9Z`4R,N'xv/&xLNSFfa@J2NsZ@W6^*SHDaSb0Qe)%>^9>h82Dpu/\"\n        \"L_M.`mEp4[OjOXeM$4O5)hvwU[0f4=U>bc1OsccAwiF*]T3xgefjuS;08=25,\"\n        \"Cdmed3FSSFIB-B0dCm/\"\n        \"<3#9)MqUKj=9RugEF);-Cq=oro-c#3_.(Zk+*.r@s?9:gVk=uDe-4r/#h?5t\"\n        \"?9L8>>Z;EY/E:Nij%BN2=*X39/\"\n        \"IQZ$X;G>#7E.J&MBv%fp-_o.FYPGpeC3nlE>h^HGuTH'8J^584Ggm&UfK>#v+1iZRkYV6%\"\n        \"6PiBfF,nOs5+gIDTM:,Rt0S.N^[b0P$GD$p(BOPbGagL\"\n        \"%d:ZkwD[&8YJ.g[WGfJXpx&-g^',mgr^&f>UfH='uu.VaE7gkixd=1vl_GpYY]>vr_\"\n        \"LKNM4d#w%K98,2mCpHTqrk2hkP'F'xAa]f]E'M_Z0^CULkt`3@Yh7/\"\n        \"4MK<Hxo:jc@p*9SB-CUl\"\n        \"bk'?@aC$5Ms=,AN-h&u7NpY_1w<%jF*GRRL$x^)$b*T*a=2Ih:l)>+^;TZ9SSnu(>s#\"\n        \"23IZ6lZ11-f+pbd1Vj:D]Q/\"\n        \"5Qks[0%->t1xMiNg$6*_EH7Bf_c):5XcT_<]GLe$^@1I$@Bpk$\"\n        \"Jf`:m0e@8s>@6-g#j`0<Zghr77jrS&>bTU2KG>C5D2>C5LG,c4)'@*3fC)X-+[cgE]QRf=\"\n        \"I1uq&0Q@D$;^>#>+>)*HGNYm/C,*eFE(jiF8/_[%a;)_=;U=4^Yw-[B13i-6'4&n/kVtj1\"\n        \".'oF-t*oFH,7]:.*]Wx7OMAs%`@2'k;q*Ee`MC1pIuGInIbhKI9u&R[OqB+;.7a4?f,R:IK.\"\n        \"RWu=#Dp'P/Cb&8_I]R74EqM+Y`)rDAAMBCfd#>,NB+<Nf1]<r7io+v`(YBQ>6dAT$FMK\"\n        \"Kq*ArQK`/\"\n        \"gT_d8Bc1k2$sY2O'$SQC`JRVXCV+sNA5N7KU_`(Hun^=su,>Bi^<elUB?\\?KMVI5UvQ80q).\"\n        \"N#ZfV)@pnQH2qB,mQ=x:#P<&N$_;Bug.ci3R@,x:YJZ5YqmvxVBr]>bSwF4t\"\n        \"l*iJM2WRP$H(0D0(CkG_j$MlA*&^@?%8V&>0PZZ(:El^KNo31Xvom$K#BRd#k612[(m0[5/\"\n        \"VQf(f/A;uA9fa6h_PLht?pmH3g#).a0a4-S[-)2=ax?1p[2EuB)JKo]@G&NaTlnMJZTe]\"\n        \"rk)dj;ON'8,lIAd;1T'85M#Na_]EmSLwc98+b&xFruo0<WZcPLP`/\"\n        \"%KYf`G^un@o1Y9mN+Pe[;#;XVfc-_6hZsntp=mPIx4v3x3A79:#HB45asNu<R`J9/\"\n        \"(7>luH>KUCL')Bhxt.UO+/\"\n        \"3LQ:FQkQo?^HAcorb=]`VYYD6'#o?+hsq[/\"\n        \"m8J8uL?p6eC)MERB0,RMIeM;u6>:E3[FpKXlNZxG1#eC'=v7=-g&w)<g,iw[AAV*&LM*\"\n        \"7pj72m$t9x%=$l`c;HW0=-E8OK&A)Dp%=#-OJ\"\n        \"X_Xo(s(><5'l?BXT&_-'IO/?-$mcd$/kLO1H31C0Ah,(/4=q>5/6/\"\n        \"uB<XOnB>UOnB:[bcHF$FlECCMMF)b):)2^or:pj^/Dh>770c7;<-4LTZ./P%/\"\n        \"GG:[mLTNx'5O-;^#8#J]0?(Q-G\"\n        \"9i7FHfw3dQ=L/eGiRlHZKj_D39-.12%rm;-8K?*&`K-)*43M=//\"\n        \"C%12j:dMOpG]g<q`=`'iFffLD>ou,vlT&>N68@rac*-o'L%qK:Z[eam<WJ%F0pUYUs@`*\"\n        \"35NS765/KZKtqXZQHLW2\"\n        \"n18Q@OKX8<b[-h+*_TVA5?2Q?flS9Us`&1Ih?M>UK$tt[BJU+5YL_>UQBEj,9iVYK>n[\"\n        \"8IXLpHN-X8UUJBHeZT?8>Cw)G,Md+MpW0D(fZq67&^HEi->Rn/fi,l`xWc-w$#]PFgM9urDr\"\n        \"gq#bLD*AVs_-8'ZEGiuIc<2kq]t]c56J'vW88R&YjC[]1vPf@%;PWdq<vm1M78*AuCm*W,:'\"\n        \"#nLACgZUQ$p$8PU]@J6DkVr2=m'1ms63ic.9x:NKFT34U.I2NCKS2IkV;94oiW%5rtqg\"\n        \"v+Qe%7hN39%9(`,YZIF4Evse3Cp/\"\n        \"f3MNuM*up8FOP;EXI<'270$EPt$4AZ5&M5_sf^5je3?vK^ddLV69vdwV8'+%F-a],LF-U7F-\"\n        \"`iXVCxW`9CsAXVC=B1B-NJm-$18HZeA5rFV-lLfY\"\n        \")87c-&RjCJbi:TGCjOjG<cFtNkTKFLbZ+79A:DTa6#1=oF.@8r5Ym=/\"\n        \"$IGBgUx,&7RV8l+&vErpHY.j&0Eb(PK#`<PM0@sn*aS78OKgU7M0U-0)2h>CS/\"\n        \"-5CNf]6H6l?E.QI;qHg-tcF\"\n        \"p%cL[a0(,A@Cj5u#R8&osUDB^$UijpTuR2Q<IGSXb&L%>>GTR'bq=gu7@$B5Mj]1_;DDb5,$\"\n        \"^b4rUb1[#R0U>;Trc'kuhe@BkB^XPg5c'X[ad@9wsJ`1V1.,F97pGxuR?L/lX0DC%F/&\"\n        \"FW<]o=#HhmQE[225Th+BS>cC]N[@cGb<<HNYc65WTck9^C#d.1k)t@[US]v]9C]:ZMxm(\"\n        \"gbRJ(-1bYOX,@[*%AAP[#W,`N*u>X]P3tfBnM)6g>8uj0$hvf9/^2dPV'Yu90/avtr6Nck>\"\n        \"XDo'5&#]<sM:Ug313_j`(=Ao*^T13v&Kg_7H[h*qe_Nf_mA1-gKOM(5RbODdOXg7hWH>CpB?\"\n        \";9b3t:i..sOgX&i9n6c#Wd8A+2<Kt)ND2w0lHh:cfp3_sTDE5j_)hscIQ//WG.48_JHH\"\n        \";.avoI>6Q.8^&Jgp;n:)%UrYCSZUugNm*9JA>rts#Cv8tV%3$pf[,VF/\"\n        \"3i;<>XsNs4v#nQfs,D9EGwMLu/\"\n        \"nmsplWw>vUJ^dxk.79+b>W.qx?@-s'as-YK349P14bF34]+HO[dlE<rY1F\"\n        \"e&:p()Jit-,3>89$_(e3;9IL2jX-@1=jXIFxZKC-]Ygo1BJ8>B,P:iFJmXm1V,pX94o6(>\"\n        \"5u>qRDtb>EDU/@i_/d6_CI/D;)w/,Nu]`6&bNNh#)rr-(F9iC[v$dxrB)[WP-xRlL&6]Ji\"\n        \"Ih-2>YYa&h$6?'VP<BOMM1QQ-<D%Kjl^o%dukpBag=th6#7iYcX+kwKiG@udPbN&l>I9>s<\"\n        \"eQ.feRjV7a4UMg/*jDao$*r^X#w8MtW23jFVR%E2K$,<eRBfr3iIr#H7cHp2+[o_^:d3L\"\n        \"APwKnDGA+:amh?(&_&>]op#+iMv6eSGTH8M3LZ7%xMgALQax_j5vS<pP%b$j$UBs_?se#;\"\n        \"nGsFn1hl.kQ:/IA]?b:VxL*^T.$bv0*lL(7[1Ci+,rV=>IpeL(,(th[v2c.,abDj,4KpR9\"\n        \",^G1<ufixRxO_4nk2EM?(Edvi`ui6@^tKq/\"\n        \"*b$D8lVH$oi5A8+9+BA)t2vXGePW^U3K,v@=A:oL0C)Kn1TvOEN%s$X9T+/\"\n        \";H7(_%%1oQZB;-I^+Wjh1@JFZS[LJju7f(HH1Vl%oPn#id\"\n        \"(hPVJmgi]fFeKrA$QJGOI+R:'[e6Z(iTe6o3<bfo*3`J>^^celeG#c4T2#c?WTi&n1Lf'>#\"\n        \"t**$hp6r,JnXsfk(NRB9HBEX<0sLJ#j+oKf^Y#^mO],4v7hXaewk3>h%H^m(nWOOC^j-e\"\n        \".X?9#tvhW%rGNi@Y1Hef0HJ9_Vq(;6]aF`TZ_%jRvDFm<;wEt9mdYkL:NWkL)kh^Hq^Cq@\"\n        \"pr-;<4ONgAgr[&19EW:83%LdkW7RG*pD[L2EaEi2G&t.3E$U['<G>C5_sjJ2O#W2'9@nW_\"\n        \"YQ@D$3qnG;*d,hFei=J2FBab$Se39BxmDJ&4sPs-@6d,M&?$n&Jk&MN([0m&4)(K2QBt*A>,\"\n        \"uA#LZ4R*>^Fi#(2e0#$:Q>K5RlYXLm:Oe74k;-LfmV*ngm3Oao$MaMH/wsAHs>0sEx,'\"\n        \"371FJHZXe5?H#?.gxKO34f.x(EgY>(;V*gVubkD&;sm&[Ee_k*a0r24N%.'jm#**SS+pW>[\"\n        \"k8DEMU&m'jicCExm,*@-)GJ9pZQZH38/?@Z0Nx%fOOF80:*=,WfCMYFV@V$&_xOI-E0s)\"\n        \"*?H;%W][<*b3`rYRuE9ZC``:F]uN>4%xaZr<#==)+QYb[cte@)8lA1Fj3:BiF'b?DQ*\"\n        \"Fsuw4GXR8`Fsm;Lw9@%GN=d8VTrEOl/\"\n        \"nAXuU5@$+1m&8SvV2Ko8JJxf_Y,AMF-NV.+Gu@%Gh&\"\n        \"vDC<hd)2[He<bqf,YH)Snv*L]_]5H(UJ#4Dg,]^#S`Y&JK/-Frs'6Ba44KMpS&Pi6bAfa/\"\n        \"1`jOOelSLM>ukgb=pt7?n2M^OWi[4E@bi=/Tt1#-2khobn9_1:fWww_fo6mN%_#c0e[>gO\"\n        \"vm&x-tqV6^'Wc5/J:TS2tdM%Avm%?nd%YL)tL(?n#cGO2+-`997s9^#o.msS$H/\"\n        \"eGvJhP%*@@f=ZrY1F&WeYBnU3U8O?f9:.KTO17>>Q1LJ>G4W8sk19cNS2GCG[$S'sK2IRL,\"\n        \"DasC;$\"\n        \"Ch)v@ZwsjLV,*v#.+041vq.1G`K-)*,;9W&;mk;-;*_)&+But-?SRU9;HUQ9`h3:882n-\"\n        \"7Lot]G.&?x%'`K9BRqd>@a/<=uGudfTOU<M`0b,j`I^H9B.7T/LX^_6&hNNh#?`d`a-WuNe\"\n        \"&%bi<o2h.SY2Sct8K*OdF0N#-O6YH0/K/\"\n        \"81[X$uE43U9U`^*x;L*Er$l%=k>_>U^BUe]tWjuj6X^W%jo>sht-^b2+JQSb,96*mwp,$\"\n        \"s0WK,]BdGCf3u$12mcmptV$Kn?TMXSQECo9O(7\"\n        \"ZlY>Y>4.*kU62.Scp$/\"\n        \"L3KeLKs_9bA&'&Mg;d)w'7fM6VV'oQl_+#H8Zd5u6Ph#-_Icm,]BWYo/\"\n        \":`<O7-iNuu`G_HTd[hgZR0Id,)6DI:I4Kp1CsE^c0j>6AC9:c$ob&Y#McV3a%aO;6\"\n        \"R2,;NkX@;qgSCrq%+E0O<Q,ZiG3HZe_b*p,oOF]fKi;dDGPi2n>r7v*u@GB@=xhZA_kkjC=;\"\n        \"TePE`YWJJVW.mhk(2]$q'Nho1dJ5D=n8lwi^O1ZuMplcx&KV+%Nd$10J>_c?V+I*uKeH\"\n        \"Q,7[=6SGR.F.lXG4.^0G'Y#n)[f25p3iA2gN-AHg/Fdxj/\"\n        \",b7nV;'1K@a]D-?Xv5d>E<5reU4IKBrp;'cQYUD@KMonUM/\"\n        \")o`cqks2%`;H8+jTOo_S]@g+.@2#2[1.n[OgU'R_LNI8Qt;\"\n        \"_g?,L1NXeL`jB#Z&1EMtvcXJ`9PW%e)Ng[IA;5+suEP*D(@ADleBF4.XhfQF=WDHg0KHMd[?\"\n        \"fkL>YTkL5SP>#c[G0PD9/H-Ykrb(+<B$Jr96[LORsKCxP4q&1K-(#w*Op%G0gf$T^cc2\"\n        \"2JP##&####D,&I,Z3sv#u_=o3@N'(,>-8T&tXIbORfRI2@do(5s`Y=$W``%A[VOUYX_\"\n        \"H87oQAbP=h:Q8o?+W'>xsfLGmWCO<IibF(<j6RKWfM0G1vHrR[EZf5$K.q=*115@flu*[w'\"\n        \"IP\"\n        \"sCgbAAHxZd%96[LIq]$MnLQ(^p:AHo<<F18D4WSdg@(r1&h-^FG+]>aOmxE<Qi$_/\"\n        \"g5R<9*l[[AC1JFbc0O&Zra:WGQSD<[4rT#NfsQtV]`>t*Q7k1(I-a<TI4SroYJN,+Z3U#A&\"\n        \"grkI\"\n        \"0]T5=Y;6vu,<I[6$gK;_s:kZ%7cTsOTIPN+8k,i:EV#foMA3.>uKAEgX?Kjc19H*h(#\"\n        \"8rZ4t#$GTqR?)_u*?GHjkDu?xT1c@f2]Ibsq+:(Xwa<U;tUNo9Q+kdp>[(+ddN#1Z6A(o$\"\n        \"EK/\"\n        \"*t8>%P%bCU'h/\"\n        \"A(GXL6bCJTus^k?JKv6=dp5<5xfgs_`QYPBWAV%ti2rhjem%ZX`bTPDKG@8]P@uJf._jEY]\"\n        \"ng(AAc@?1J=k*-lOGG*<U68hA;7U&6*./rs&iwI/4HwW^Jxt4)#7v:v3\"\n        \"UmCUV5N5q3gU[wL%nVkLn27j`2p:d2esPlL>@=`#,,9OL[f:NE5ZZ=*+O<=:;5Y;$,GP##\"\n        \"08je*:H[#$I;@%')FFG2^2ra3r6$o'V<;.3BdNI3CQRL2nEsP2gd_V%jq1.3dCAhE&+Hm6\"\n        \">>ls16S[gLRckBg#hV4H2;s&v:$^_^K&I0?-6C3$vqx<./\"\n        \"be$OA%*WfCl(Z#nf8EXI1LE1YOORsnHnsUg9;q#j(.j(sL'O#C2>dEsFg7FR<tX_npGSu_\"\n        \"H6sQi[x)#eMXiSW<gE-jw=bS\"\n        \"9Q^):c/\"\n        \"qUWk>vc:qw1R]$3OYJ=kDi$9T8W*4f#t:Q)kLr>9'T;%4=+5J-LYN*^41nRNd+LZbv((]\"\n        \"g6p&vi=?lB;Gld3J/=RSv7K6(+)`>2_*(I8mld54Ql1W3*6'c]H:2TbObL/o%Tor\"\n        \"chZmO_H]]qb1(M@;qxiAfY3l9Duk^Wev0Vu3anmoY]-C>LRWY2c%b,`&eqoRA<Wbti>QSt)\"\n        \"C,dU`h`_h4uHxf[jhD<B;)^9&iE_g/O^MOu4ew*WZL#gFq/i2V0o=`$h,7s5Ya+_W1j/Y\"\n        \"4a4g93./\"\n        \"hU;%.H%asbXFC4P5_=UYL'(L%Mag[IQ;q#s$_EZ,dGG:Y]D$hLCK#nG+jL3:<\"\n        \"NOS9xcKFMKSTEpnt,roIlVIN^)P>Zu'Gr/u;:I%SMUmP6BdUI%(HIX*7gf5LuQ:XY.<@1X%\"\n        \"](30E0xf`5fki]t%.a5dPVKs>7`Mb3A,OZWScfOFXt&Fr+M>%+i3fF'wk6U=?j_PuVriB8^\"\n        \"S=$HoU=Z_&=?&,0DtPBV4Vt-_]5Z-R<7F.D%`U+;PipI,OmPSqtuN%kSwh(TP0^ZPK,VG\"\n        \"^%###.#SooPx*##\";\n    return _nvidia_sans_bold_compressed_data_base85;\n}\n\nconst char* UserInterface::GetOpenIconicFontCompressedBase85TTF()\n{\n    // TTF font data for OpenIconic font TTF\n\n    // (c) Open Iconic ? www.useiconic.com/open\n\n    //    The MIT License(MIT)\n    //\n    //    Copyright(c) 2014 Waybury\n    //\n    //    Permission is hereby granted, free of charge, to any person obtaining a\n    //    copy of this softwareand associated documentation files(the \"Software\"),\n    //    to deal in the Software without restriction, including without\n    //    limitation the rights to use, copy, modify, merge, publish, distribute,\n    //    sublicense, and /or sell copies of the Software, and to permit persons\n    //    to whom the Software is furnished to do so, subject to the following\n    //    conditions :\n    //\n    //    The above copyright noticeand this permission notice shall be included\n    //    in all copies or substantial portions of the Software.\n    //\n    //    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n    //    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n    //    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN\n    //    NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n    //    DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n    //    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR\n    //    THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n    // File: 'open-iconic.ttf' (28028 bytes)\n    // Exported using binary_to_compressed_c.cpp\n    static const char openIconic_compressed_data_base85[25385 + 1] =\n        \"7])#######cl1xJ'/###W),##2(V$#Q6>##u@;*>#ovW#&6)=-'OE/\"\n        \"1gZn426mL&=1->>#JqEn/\"\n        \"aNV=B28=<m_)m<-EZlS._0XGH`$vhL0IbjN=pb:&$jg0Fr9eV5oQQ^RE6WaE%J%/G\"\n        \"BgO2(UC72LFAuY%,5LsCDEw5[7)1G`8@Ei*b';9Co/(F9LFS7hr1dD4Eo3R/f@h>#/\"\n        \";pu&V3dW.;h^`IwBAA+k%HL2WF')No6Q<BUs$&94b(8_mQf^c0iR/\"\n        \"GMxKghV8C5M9ANDF;),eP\"\n        \"s=L%Mv6QwLYcv/GiF#>pfe->#G:%&#P$;-GItJ1kmO31#r$pBABLTsL:+:%/\"\n        \"-p5m6#aJ_&FZ;#jL-gF%N55YueXZV$l`+W%G'7*.p+AGM%rs.Lx0KfLwF6##_OB`N1^*\"\n        \"bNYU$##.,<6C\"\n        \"pYw:#[MW;-$_C2:01XMCH]/\"\n        \"F%@oj>-Wmf*%%q8.$F`7@'.B.;6_Du'&hF;;$63Mk+MS:;$l9r&lW+>>#YvEuu=m*.-H####\"\n        \"Y4+GMQrus-Su[fLK=6##'DbA#bFw8'tB6##Lhdk%Nb#d)\"\n        \"-l68%<a60%*8YY#ksnO(meGH3D@dOgq#Sk#<@KZ+^2bY#t=58.Z42G`R5pP'R#k8&1p^w0%\"\n        \"2tM(TDl-$nq@8%uP&<6FMsFrkg%;Qk:MEd]'CJ#q4cQumLuFroPu>C&8FB-#@'$.8mwiL\"\n        \"KO2EOR.tB-1N5</<PQ?SQlRfL^5fQMK4X$#8e`##9Q@k(xsFA#cFGH3*/\"\n        \"rv-@0steAL@&Mt98'RE:6G`(q1@doX]'R>$hiPchu,MLY5;-]Xo34camA#@\"\n        \"YqlLLxQ2MIMh1MB2tuLjV;;$\"\n        \"PE)B4-Mc##=(V$#3JRN'GRes$-M:;$eN;hLGSrhL)kL)Nv$Q;-h8Ee#OYLxL/\"\n        \"qPW-+l*hcw3Tt:K=/\"\n        \"g)&QD.$s@o6<40)V;=6axT1F<U;2OWq;.]BxTR2###;:U;-<3]V$]V@^FJR@%#\"\n        \"8l#U7uUO.)Y9kT%1YUV$Yp()3GU1E4m3[5/\"\n        \"sgw,*wqq8.5&7C#)#xP'hT%;Q<a&buYwaVaJjAZ#/7:*j>[x@A1P&,;nl8IW*Yn36=b/\"\n        \"(G*8FW.KkqrUgt]_uR0.g>Z>,%&$),##50ED#\"\n        \";EF&#8EaP]UkIJ1P*nO(@nBD3gp/Ys%*c,/Wp`IhM]^'/\"\n        \"B67gL0mM<%9@RS%EE.<$(iNn0&DJ1(*O13(0&Ws%C8I6oF'XMon(BW#Ej5/\"\n        \"1#iK#$V@pgL5W=huZXAwR'qAZPH(<?#%G3]-\"\n        \"R4B>QW<Q]uIvg_-5Or`S*J);;RmwiL*BauPcOB_-66lKYg,3^#r+TV-jO@mk5ImO($FKx9R:\"\n        \"xIQTCfd4XcE,#8EaP]VkIJ1W9m9MqiRdF#jaIhUm9DC)Pb3Fq';dMJ:>GMU'MHME]sFr\"\n        \"$#bIh$ErFrtE)Xu*S.GMsFZY#xx'&(-hBD3O4g]uc_.08/\"\n        \"tf2`_^7I-PK7I-dn6]-TYwTM9en9M3*-&4%lQDO]H1[MO@YoPM8,,MU#<?#0q3L#r&_B#\"\n        \"Gk5`-+XV$^wAI&#MgwGMe/=fM\"\n        \"%2rAMwM,i87d0^#(tAh8IG1B#8E_#$uLbA#=2vg)<j8>CoeY9C3[-CuBu[*$#xYr#2w@P#3(\"\n        \"CJ#G*2GMlH^v-KV;;?BT_c)lH*20%'%<&DI$,*DiY##=Z[0)jiWI)qV6C#2-#Q/8bCE4\"\n        \"d.fZ$aSIWABTgk0;-LD?7L1a#d:+d4.:L^u@Ft%,O27>>$/\"\n        \"Ib%@s@8:JrksLPQuP=Wj&2XY'f*.W>%H?Jo*50D@N'6@:&&6Mj;5:[G>',%/\"\n        \"5##sIkA#?V)2/9(V$#rA+KWNVB.*0Zc8/\"\n        \"_YWF3p;:R/\"\n        \"g;2.I07b2CAZAb4hLY['IR(<)[8UCCx5B58Xatm'Dn35&+^L-M&J_P8H79v-P>/\"\n        \"B#9]SfLYWY95F5kWo[iJdo7PFa[Nk9:<bmp2<`1g2$^kYhLMbQb[P)^F#&fAguE8*C&\"\n        \".'wwB_B(7#1Yu##;PF$/\"\n        \"GRes$c^HwBH$nO(gv8s7EPdO(jW4G`Lq#Gr3RgIht28G`]n&=(Vu_?###b9#`KGf%;T<A+&\"\n        \"Zu(3%'%<&_T/a+bhV?#+crB#;0l]#P#G:.rkh8.mNv)4kDsI3\"\n        \"5;`:%q2Qv$lf/+*u?lD#g48`&/\"\n        \"0*>P^-R`E7+R<C0F91p*AM9LpLL=Q3H?.q]PMP0*+_HM3bQ;-`78JL$R]duA'OI)ZFX:CxH#\"\n        \"OKvCnf)Gd(APJCZY#)J/-2IdS2^M4v##;Z[0)?sGA#\"\n        \"vx:g)Z4jd%lUFb3(ctM(7'$,&H$^>u=NxH#6J8I&tS?.&(w?w%M&CJ#nxkT9HoMs%Ri,#:\"\n        \"fs0c7fGA_#kSuIUZ42G`#?#,2'ti21;ix+MG3gF4`cJ,3)_Aj0cI9F*jpB:%cn4[$UJ,G4\"\n        \"[uUv-?]d8/hJL:%+SN/\"\n        \"2X,n.)1Yl>#4a&m%;Bjo*G6'j0DTnr6>vl[P^Su>#D5UlSjXT)5er^)%7R2=-kL)APv<qa4<\"\n        \"IK60tY#c4<q@?PW;e+WLuuEm05Vm&fK(,)nM5N(f%JfL`Lu(3\"\n        \"9UsNOj/@tLh]8f3.m(u$Uxor6]]DC8ukm/\"\n        \"N3vOG=Mo&vHc2Ii4iJ<cFuP@+`E'flLbcK`$v)$$$PrC$#e28]%/\"\n        \"5:$)V'+X$)lNn0vpf8%B:Y)4ZE>V/isSi)XSf_4Kj+5A3/mCW-nm<S\"\n        \"`Tr6C?9%0$lKTATZ(N=(=BPEd++wH?Ukx/1U'+&#`,Gj^8%L+**<m`-0]kHZ(LOA#vkh8.7/\"\n        \"TF4o=]:/Z(OF3kl-F%3F6eMJGsFrY3Kt7lAkHuNBAJ.3dT3C7Ot2CSeKD?]a;*eB[f%-\"\n        \"6K^'RB,aB#FP*2MSiaIhwq$vJZTEV&biqr$07>>#8EQJ(6uv9.nXAouu[XO%s[?>#p?_&#\"\n        \"aJxjt^;>_/^1[d+=7B_4s7En3+I@X-vu/+*X0fX-]p*P(&,]]4qio05A@N@,.>T@,(r?5/\"\n        \"=KeA?u>kB,pSaIhUT%/2.dOCjA/#2C7CA5//`Nw$*jnA#abDE-)aDE-/MXZ/8N9R//G/\"\n        \"JdTa+W-@Isp^Z_?##EdS2^4;5##af[^$/6fX-m'4]-7^9Z-HLQ;-.`6/1xq7t%uR$w#tPR6D\"\n        \"CIm,&=G4?Is8K%#fd0'#$sb&,`X2$#p%c00gEW@,QFVC,732m$ic``3t<7f3I.=h>K7)\"\n        \"qiFe8V8CwBB#'8:$6-^J6atvN@,P;I80eF&b*l-AU+Q1(`.JLR$&cIqH1Vec<066V9CIBkp%\"\n        \",<$'85fK>?uxwV.fE4g)Een2B,R(f)o77<.hp?d)BiaF3iEo8%Z_G)4@O'>.g:S_#OJ,\"\n        \"G4oHl2Eq<@12u)Yx#IM6u?b%]=YnAv6Jex/U%ZRQ;-P2>.3hC7),'BW<BvIo#';9l<'$<=8A\"\n        \"QI%##SJ%?R#H0%#hj9'#G`gT7LoqZ-$QdR&*oNn0@hmV-+NTj`qc=00vc%H)+,Bf3rQim$\"\n        \"VKi9/(o?X-^hDv#@81G`Ig2Au0aYK)uXAA,/Pg;M+q//1gFB,j<34G`R=wP/ji1G`]d`oA\"\n        \"Q$1i#`M66CL=6##)CQ$$^0+&#Kqn%#7mJR8@R6C#a?T:%cKff16t'E#E%D'S1lh8.VntD#$\"\n        \"be#5@=e/1#6RCsVJ$F<tI`?#Of6'5r0Er:3MBk;spA/1cbg@OmWuI<^'s?#FWS5<3QP-.\"\n        \"DM_*597on2$&###ZYw4fITr+De1'<3S=>6sG)'J3W#[]4<PsD#%f%s$PL?lL14(f)MCr?#\"\n        \"sX.r8aa#K)Nl/BHERX/2ucJ:/mu[:/vBo8%r:#?,h6n5/s39tU3*Y<UZ?85/Q4vr-VSi/:\"\n        \"#b<c4-[xo%ls>n&;[qu$0FLS:/\"\n        \".r%,&@ed)$QlvG@5uj)Oi3+3G0Z**uJMk'^@3B6.xl$,eAru51Qkp'&.-,*dLW)3.,RW%\"\n        \"EUu^$sZG0(pS4=-JvW@,8PQc4RT8,*IPgf)3;N#5G&MP0\"\n        \"U`2p/Qkf34,N_2:o76HN7.7W$]YO@,_NG/(,a'W$%'N50ohYgL_IGG,<;:v-2@`[,=bT]$?/\"\n        \"5##'AlY#ID(7#Fww%#QYmq%WqRJ1`tC.33Tlk0[IBN(3pm;%`ni?#A=M8.cc``3eN:;?\"\n        \"AWw8@k%#s-00J,;4YIQ0&Z?X('P7G`C)j-&JPciuqvC)*#BU3(UH5/\"\n        \"(omLgL0G5gLMQ(]$Y$3$#E@%%#Uqn%#fKb&#Rp8X-@[v(+vuRL(QknW$-xNn0WTP$KC[/Q/\"\n        \":%xC#)vsI33E0N(\"\n        \"H1:)N]*WZ#1H(0aVHt1':Z/\"\n        \"GM%7Q;-%#h3&F'Bq%n1Goub&]:.CCK?d$SF&#T=P#$b,`tL)V[*._1x+MPr@X-Uokl8JJK/\"\n        \")$8h9MO4G^#P^.3M>7(#&S:A2CmEx>dOI5r%:^iqM&_V;:\"\n        \"i51g*lBel/\"\n        \"a)12'oon60Jn))+`'fJ1`tC.3?l0x5oSEU-a1M9.F)E`#dDp;-JY#<-GOH&%H0B^^<5=E$`\"\n        \"Ge,Mu%2&7&bsZ5oYAA,?J<MKYsslWoV[(6DE;&$b/<-&hpE$M$oO07*pcW-\"\n        \"j:?NMCQFv-(oHhLL2JG=$,>>#B$k>dKV;;?/\"\n        \"addFXh)v#`._f#q%KoMhC?>#[CNJMf:-##-Q-E0rB%;QaVS;-#'e32tYg59ZF;s%BOP]uA_$\"\n        \"'Q)uCt/OKkA#Bua1uiRJF-[d'-%q%5kO\"\n        \"vfP&#(K6(#=3CR0*_S[#:H%O(xNUA$6*YA#Fr/l'V::8._%NT/\"\n        \"-K?C#ued;-1rru$JMQF%'1u.)M[4;dv<[5/H5_T%:='d)/OTF&*j`B4')?N#]_h[,>eq/\"\n        \"3l;pNM1'A5/rM?R&*dgAt\"\n        \"UZg6&7NF*+?jTx=EIIW-@NSKug0pTV.52G`4CNP&?&3-&?A/'H2Bx9.^1tM(b,sFrjv'Q/\"\n        \"'ZedubwtBC/:CB#jI:0C6CBh5E8I/(dcT5',m?T%eee@#.N?C#qw6u68AB-QI`_M:lpZEP\"\n        \"sTK^P9?%RPLu6j:kmQEPKF^w'?7%-3KV;;?J(WqgNt#`40<t4SYG,juMZfF-)dGW-^\"\n        \"Wk2DNLZY#Kp+)O8pID*IF)?#h:VX1@93/\"\n        \"M]VXKM_oX02SkIJ1lF;?#?W$9S$tn=GE$M#$Q#HpL\"\n        \"<D%/O(n)U/[[2Q/\"\n        \"8a&;Q%a3^#RWU@-nR.K9u?:@IY3E$#;P6*<TH+O4hKr0aWl;;?xpSBZ,K<@I_3EiM(IkB-/\"\n        \"RkB-,J'O.kE$`4(9a;%a0x8.>+nDS2BI4=E&=h>]^'Q/Z9V;-YOho.\"\n        \"<TqA,B/^q'%vGA#gEW@,YG8f3vf%&4O$/\"\n        \"i)cn=X(eOnH+s1gF4`XdB,1>MZu]8+-;]kFF#'''k0[gX7IMhIw#Ri3+3Og<X(+gpk9QrD7*\"\n        \"416AO7CsEI@cR30R,Gj^]9kT%d`>qpfnQQ%\"\n        \"e/0i)(@n5/6a=a;KfZ_#4p#g[oEkCWnP%3:)puH;f3Yn*q=jg.:KVK2HR+8&5R&/1t.b8/\"\n        \"mRXR-Oh#L/lYWI)*^iF=t%'Z--nii/S;3,)s<RF4#pMB,1H4Q/m$UfL_qtFb-:jB5l.$vJ\"\n        \"2uY<,7;IwPvMc>#^XL@,709JSBlvuQ#sj?>drN+3Pr3&7b&WF=b'4fur>gf)?N=v-=\"\n        \"Kjr7wv#K)LVsQNXSlwPPaE<%%H>[01lXI)YAOZ6#FPcMnGkIdt'e'Jh)MUdPco=d`X@>d_d?\"\n        \"]/\"\n        \"NX.i)Oi3+3AS.n/DEaP]ZkIJ1Bd]N/:2Cv-6I7lLg%J#/\"\n        \"Lcp77DsX<h3KKwMOw^A?,C1na;s4T&x6v^]$@O9%(iNn0L#>u-=Fn8%$t)Z$<QJ&\"\n        \"OvDrkJbb>a03c7uQOP]'RsxS@B+w<2C\"\n        \"$eXw,3d]'R]'^F-LvKl3WU'^#87>##;(V$#QJZ6&0;5##@%Yw2(k^#$BUW:.eAgI*074D#;\"\n        \"9ovektS9Ce*loL*uLp.2w@P#gO:1#/s#t-AQWjLA1(?-G-ml$c#[]4bQXA#`&ID*bQs?#\"\n        \"(LA8%]CAC#kHSP/\"\n        \"GI0P:?fHC#4(=(,wB3r&PC:*31Nw+33Ij,WxMvuJ3)WVRh5l?-mO4Z-Nv4wgwMPT.,GY##\"\n        \"TFk3.je'dMT,AX-<<xX-c#g*%k*f%u_I,;Q'rw^$`Pvf;E9Z.6aq#.6\"\n        \"d*6.6Xax9.wF5s-6/XjLdX2DdG*B_8%oK^#_.kFdx-Ns->0niLBpID>e9+@nf*,s/\"\n        \"aNP;dextBCY?:@-[x%d=<x&#_4:ww^.oQ<_G.4[^n,?2CDOUEn's[j0Kb7G`%K>\"\n        \"G2DLKV6dl5D<\"\n        \"-W<T'5ek#7T#=i26k/%#>Q@k(4tFA#K;gF4C'+W-`+[^Z2UkD#A.xJ1u3YD#o]d8/@jQv$0/\"\n        \"D_&(C=m/H,Bf3Pv@+4*ZKW$RbZV-:'ihLO`6<.`6?8C&)$?\\?Yo-x6KZn^43YAA,4&huG\"\n        \"]g&i)#VWECnp@A,Be//\"\n        \"1?`wA,4lW0R8MnA-kvjS8ln3B,mbX9CkF:@-bI`t-[I?M93+s20jbP;-3xqr$4FG>#\"\n        \"l7Fb3js&i)I>Cv-s$gM'OcO4]O^>r%3^lSdq_adth<e##E@%%#6`gT7\"\n        \"l1oh(KF.<$^&ACmmE<R%v-sV$95<+MKDlh':E1qLZMCsLb7cB,H]0hL6w//1g0Vk+]gK6/\"\n        \"7rC$#0KAN93TkM(GPS8/nr@B=5bOAu&J[n[d2&Z$tCF&#b(p#$5FNP&IeJH*)?[s&N:+A[\"\n        \"HdF)</.'Z-^I-/\"\n        \":#@-$$xn>V#fnG688MFAu.Zo?NosS9C+Wq-$,kq0Y5rJ&vJao,;#(C7'I]>c*ed^6&Y'fJ1`\"\n        \"=K,3cX.f$^#lP9TAu`4/I#H28)qj&s8Kf'clnP'IEPR&-<m`-LH.wI\"\n        \"mIAou)ouN'uK,:)0h#h1JL7%#R,Gj^x44.)_xefLa#1i)D>`:%J#G:.]L3]-kDsI3d,ID*\"\n        \"dkS9C(8_33L#v20_gB_u(%x)$>m;U)Y12mLS2sFr,bdt(LF.[B_Tr22kHe##IXI%#.^Q(#\"\n        \"Z=sd2PC:J)KF.<$U^nVoVB:=%iZeF4uP2)3i%7V87:Y)4/\"\n        \"D-HN<&iM'Fvnb0O)cB,4&Jw#R>iO'Q'4$6Ga7#%-DDJ:j'VhLirMP0v$C>$lD.gL*\"\n        \"kC80AYlN9qjTn/3`($#Fg99%OT(]$\"\n        \"K1>u%$CZV-1q//13RgIh(17I$E/\"\n        \"<v>8QX]u.d*X8AI]v$d>f;%XtRb%]:75)Vi-]-(^&@93_V;-YG^e6J7ws-f$L-MDMYGM_\"\n        \"0SX-(=.p.n5UQ/D/)GuI/15AV?96/3BZ=Yxo[F:HQXLs\"\n        \"Yw2$#/\"\n        \"h>%.HvC;?'*Zg)n.rv-dh.T%ve#`4lYWI)QNv)4%ZFW#o(p^[2I(I#-l*#7Y<2.Is;#\"\n        \"eoQOuf[c%q=?;5T[8`6gM=>-Q2iiO2G`K[VV$7Rjl&FnfL))?[s&bvY3'CI[s$Y-oJ1\"\n        \"E=R<_2pDs%*$[g)mBmGMN?H)47p&;Q;GB0uwRE2CsIkA#NKX_$>aKs-8(P6MjL1_%D&###\"\n        \"aMwFrsqou5P']m,'fNn0]_OF3JIV@,M$g://]v.4i'mG*e4B>,j^Aj0<kY)4ax;9/Tn.i)\"\n        \"RJ))3wc``3onXj1:s?^4_Bq:d$PJ@-xq=K(NljE*b<bc2(3D1;8uO]uFf<C4](e3*4`[?\"\n        \"AuS-*3;1(p78]F.3C;TBR6%>H&Ul68%=jQK%r$'et1-;hLRZ/@#$#bIhP$+W-U9:wpx215M\"\n        \"t'918/Tbk4tJ*B.^,QJ(29'.$Xd`#$XoI08&s9B#Lu@P#h(>(.p9_hLxT?R&=.:%/\"\n        \"bfQv$&tc05&1=]-6C_B#6G<GdT4Nm#Dvu8.cYxi=+'a216a.c%f#ak=dbI<$RnsK)_;W.3^\"\n        \"J)B#\"\n        \"=;`C&I0@)KG(@$g]s9wgaGA*n2Lh'#%&>uulY@)$U:r$#7Yvh-t'YKj*$&],+3j'%j7o]\"\n        \"4apCg%q]WF3;X^:/oNv)4QdPw-L%O;9`H[n[ww:v/,FeB,tKO@,ep3$6vqf5'nBI(R>Nuf[\"\n        \"4:#mFxCEZuG5EC=S*cB,vj%.MMJ$291i9^#q1:kFNMY>#=5^s%2H`hLf%f^$2om8.]^C8#\"\n        \"Qnh9VLVUV$i8SC#(<0/1$J@;?p/W;-jWGouR7Fv/Pb./1<)sFr^n-q$NsLB:K(Hg)$%nhM\"\n        \"0U.Pd/A:.$b)g;-w#Sv&R+no%:x,W%6oiu$/%@p./\"\n        \"6tM(.@wq$tTQ^#+r0Pds@O]u@#pG&i435&<U35&Q/\"\n        \"bT%-Duu#,.aV$6<Xq.aXa>.>c5g)NEFs-_gPS@:;xp&FI2Pd=1.AMUV7>S\"\n        \"un$W`w5V9CQ7T&#h-7m%qnrr$>i7@%PBBN*#DRv$t0;W-wod;%m^D.3Coh8._V_Z->-w)4D.\"\n        \"7x67gurBK[3Ib(/&,;MV0?)<-OBkvmYf>e_VX^m'1,)0D>;+1H:q#`63.5($(U1roT3<\"\n        \"IA]C@PGd2CKoxEI.*nMjpg&%#6x5U7n+oh(qe)<-]VsR'e)ahLKZP8.$ed19p_v;-Bviu$\"\n        \"vj4N9G'Z'?>IKC-#_@S/j;1g:?@(XS<U?a$K5>_?tor#($o@g:1gF>d_[i5r_A-$$LYu##\"\n        \"FdS2^Crl##9H%O(J%1N(m`0i)x1tM(Zk_a4U&>;?`04]<<Lt2CP#=2C*S+RNPjo@S(9a=#\"\n        \"95>%&g&p%#&dZ(#:efX.mqD$#*crB#AY>v,YLU8.Od5,2]AtJ:cT3T@D&Du$@]WF3cb(f)\"\n        \"0Zc8/\"\n        \"xS#8RJ40:95nkp%;Omb>igZKN>P9L(44'k1K_K6MYsslW)aog(:_i[,p)IG=X13G`O39Z70$\"\n        \"Qn&D.4x71Y=)-G@-u6KU4x-aSDe$)D([,.rbi(jEelL+iZY#kSuIU^CMc`j*io.\"\n        \".Y=W.9offL&(tu5kiE.3[Z*G4AMbI)#R6.Mp?Uv-&cUNtQ/TP/\"\n        \">;gF4See@#v2=v#RD.l'ncgQK=SHt7prgf)3lW`uE=AX#TVGe)B[0DLu33G`))[#\"\n        \"8xf8qJSKOW',rKJ)=CEL#FRGE7\"\n        \"tql?,Du5]MwY1f+TRQ##XmWF.4MG##Z.n#nW(hh&fK8<-]Cex-Ap/\"\n        \"kLfQAa+pNq12nS.d)a5eD*jE7f3EsmC#2RQv$KVM_&:OQ^+IL>n0/[?U&FF76/\"\n        \"5vai26bZIMaWPv.5Kuw'#h^>$\"\n        \"jP$ND.cTs9FnNp1ZN%q/\"\n        \"7kgv7[RiXu>['jC<;v<-OH(5'YsrQN,GkWSF&oO(:76g).CwX-DrS9CYI[i.51Gou46E/\"\n        \"%<gOSc'cWj&%ZI*-PX=F3>GVD3<Qwb4-Pu>Cnn(ju%'DC8oA5>d\"\n        \"Kn6mA^I6Z$(rQ-HmA4K3,0fX-H_$gL*]d8/\"\n        \"sRh`.N-v7$hXWY$+w<2C)+r772=hf)qx8;-UsgY>FPaJ2tKZt.'fNn0=R(f)YsHd)7>1p$V>\"\n        \"%&4&=(u(7_Aj0Y$n;'&QVA.OGg+4GF[?\\?\"\n        \"]g8P133'#*)LwLM@/2&74jj6/\"\n        \"id1*3URQ;-8khh;$p8?,%>2e;pFNPM&+*#7;Lo0:X(Z<-8,pL:8PQv$2+:wg%&>s6t++E*$\"\n        \"C;W-NZAX-ia&;QK=S?SF5nNSkko@SbCNJMYI(q7.uR_8\"\n        \"`4`v#(iNn0)+niM^uQ2MgN&;Qi2_$9,lO]uKmWnLQE%q&=M.>>6oA9=AZ$B=`82t.$5K+*X:\"\n        \"Ls-4h&K1`1[s$q[2Q/$8^F*rHNh#S5WF3NP,G4hO=j1HZU&$ZktD#5>PjL=`n]4pqi?#\"\n        \"H&,J*/]&E#AIwZ&#s<h<PaM4'*4$O(FEPN'm4#(+:LL;&E9gq%/\"\n        \"i1E4>E(1;>'SD*kZdh2`Dxu,mpP3',XTE#8O*t$5`tFb2_Nv#nI3p%YPw)*k9ob%$XA=\"\n        \"1ruVs%a7kI)@=.<$kGiE#\"\n        \"N4GD4RSE**[haR8;0h:%71#m'N1gT&f;m7&Ze7W$TmLg(ZHj@&W+2?#Mp:4'M9=x#%Sdi&\"\n        \"ZFNP&DTo&Cft9B#jPD<%TbsJ1'<1^#_<m=Ga:)I#h_/rB.xCT.dC)I#Ulu8.3.`$#v_B^#\"\n        \";r_':cKQYS,v:T/(N#,28YR]4He,87UH^#.f1))5EK[L2%f8q/\"\n        \"Z)l>-:CHc*qsJ<-hHFg$?:fpg2-RAOi-5.Vx^WR%.eY9C%mD('cFM<-;^TV-<KtglJFlW?\"\n        \"62vG*&)t.dDjZ-d<pW,M\"\n        \"jcNK:SK4Au9^)t-=1.AM2k^KOVj*J-hVOJ-S@jM'''lA#&K-Z$3.5F%F9OW-n=.`&i<F]\"\n        \"uSwY<-Ot/gL3S$m8N(:7'^CPV-XK+n,gBFt$)lNn0k?8m85dTN(]SWF3]+:]?q0PCHuG^[#\"\n        \"Tvgf)QQR@,ZD;;?g$a?u3*&v5P]sD3U%-w)tWS@#@nh(*iip[krQSQ,VHf;-Dwd]/\"\n        \"k(@D3LXDT+['uJ1.;Rv$BxF)4TMrB#`i.@'3)4I)5Djc)4gE.3jLBk$dGG)4[V)60B>'i)\"\n        \"56tB,\"\n        \"M(w'&xOO]uW?DX-2'B(&-C$_#wkjf)-pQ@,2W>W-u/\"\n        \"KF%3.(B#j_n.M8=kf-XLL-Q%]L-QbiT-Q8n@.*u^Op.jAqB#aU%LGDF,gL>fxd;]Fl)\"\n        \"4fc5bsDdQ]/$'Cn)'<)iHRp)IHIgdsL\"\n        \"T;I]MOjNg=aupbu,6^gL5D6wJ>%r;-Jr+G-Lq;Y0`qIJ1tveF4Y0%.MiMC:%HeolWDgC5/\"\n        \"^%AluJ<cwLI[qI=j@[#6$,vx=Fh6gLY&_mA2VLv%*<x9._HF:.>T&;Q7CNEdqDDe#b?^6$\"\n        \"w71Pd(l*3ia0E$#JepeF9HAX-*V3D#?Kaa4o4P;-uYZ9Cj)?2COqf5/\"\n        \"ZQdduCLk%Mew%bO>x2$#u'B?.CfP##$#d;-=5ZX$[aoI*]kbF3]%p-)F53.)62m'=WlK#$U=\"\n        \"#-ML(i%MOA#I#\"\n        \"nPj#$A4f:d&?-9.;M7%#o&U'#,]ZZ-^L)$#<Z[0)lw6u6J]B.*-<Tv-*Lj?#f1ie3CmQv$^^\"\n        \"D.3k=8C#tBo8%gYiM1'cl>du@cI3,o(NE#Po'&^)T1Mf;*i29Q4;7c'(E#ANp;-PwNn/\"\n        \"MgAa5balSVO>.Q/P0_C6AWae36C(C&G/\"\n        \"5###oKB#,DF&#'EEjL?YqU%Y]J3%;cLs-aSV)3ig'u$?S<+3FLRLMIU8f3w<7f3owsM0`0S(\"\n        \"6]u&)GiLY<Bm@j;7c4.1MqeP;-*[9%6wP%a6\"\n        \"3::e)[*%u67N[v.(q3HMikT]82^03(9W?K%Q93-v7*7g).$$E3m>sI3NY&E#Z%#Gr1qD)Wv^\"\n        \"m).Hx&;%Nx<2CFm70C?\\?1(q2:d*7#Xc2:Ti>g)6sLlMl0.q$e6AwY2^P[PAa;;?enPE>\"\n        \"(cS,&01em8Q``'8(8F`S<3KV;0M<dteWj$#OdS2^B=qp7p-m<.3,Jd)]_G)4_V8f37'\"\n        \"Hd2Frj2RMR7m'nOgIhWwZ>#S)oShe#s%gvs4/\"\n        \"C:12,2>Bfq1%%).M%DYcMWg;;?[75;-CdU&,\"\n        \",h:_A2bAW-HXKZf?dnO(/,-J*AeL`$/\"\n        \"DZ;$x,rFrXkuT:X^d4C`,LEdTCYG,qA6FS2=hf)qq[d#Gi<?8jPC*6Z[?##;(V$#PDQ6&5a_\"\n        \"m/X36H3VF3]-`(`5/$tn=GUM6qL=LL?d,:-$$\"\n        \"&.kFd>VAt9<NN)#'/###h=PxtIMMJ(.`JV6+522'PUX[7T=(/)F?r`$l]B.*1Wj;%I/\"\n        \"X'%SSK5Bm;pY(uf]Y,LVZ9/\"\n        \"eIfm0+LTfLvO5J*2u%T.jAqB#1Il805cVQ&q>6R&e^B<B0N&;Q\"\n        \":s,W-/\"\n        \"Snx'@^s'+^I%s$sMA@,g7,_+Br$W$+'&gL6hM@,b.$$J@%uAB_m$%'N3c%'Z:U>,jvOA#b`\"\n        \"vn&JCf*)R)cB,]7s^oQ>uu#eBF&#Od0'#oLXs50>cQ1=G5##=Z[0)okY)4:Z]F4\"\n        \"(cK+*x_OcM&t'E#x.<9/\"\n        \"8C)X-fpPA[Rp7jL&c6lL#0E.35MI[')J+gL2a+c43Fa$72r.DC*Pr+M>Up_4X(';S+RM@,\"\n        \"FQTA#&$_f+Q6@iL^^Q-3cF@q%J(oQ-5W:V%sa;e33a_`5a<=DC\"\n        \"tT3>dE-lV$8wMfW*+n@,OuH6S'8_],G(hKMb#<e3Z],@#.TW'8kT6%-3gc40udp(PAR&##%)\"\n        \"###QwqjtEUai0*jTS%.E-x%)iNn0mf4K0K_`8.>;gF4`m]Ku[+'b?fo+G4X_giBJ[lS/\"\n        \"ctMm'P[cQsYx6W-VU>qrSK;^#X]J$pm2>w&]3FX$jt$A,bs(k'.e3.NO6Q$,UG(.3AV)d*\"\n        \"m0V@,kY[-).UwHN<CUf)-_`3t0<R($@uvE2,W(v#8=nS%-bi[,;uL;$``F'oS#Gd3=F=gL\"\n        \"2%52(8UYgLL8,LN5`SP':,:W-6wpgucHh@.Jn@X-sBj;-9a^s$J)Z20$#bIhFH*;Qu5p'&\"\n        \"FKEx'00O$MGoU3C37PF%MSl##./Sj0Z42G`5FNP&M,@D*fh18.(N#,252Tm(u4eW.D[Zc*\"\n        \"j,Qn&9SG##@Q@k(#dCp.pN7g)Mkgb&T)cB,@qa>d$jV=YDmZW-?kWX1RLg+uKI%>Qx$EM-+\"\n        \"61d/IIvu#3wFQ#K%9g1oqP;-K#adun,f%ut1c98B[X&#-,K9iW*+T.ElY##>]&(10ImO(\"\n        \"ij@+4auRv$#@F,4Mk5.I=Jr@Cn=#AdU_iM=M&CJ#':Po(P1T9ChDUO&9&:Xo)sLh$+6)Z-\"\n        \"hfDu$kDjB#'1Lc5wo&vHYA,C8v4(_5-+7f=-YV(ZW4a?>^qD<%=5gF4c<lA><J<?Q<.R?S\"\n        \"%-/Q#@<^;-WiE30g.(bH/Dv+Kj/\"\n        \"w-$br^+>?1DR<RA?fX39MhLO:pk2]BF:.&cGg)m`0i)2^X2COZo6<rOt:C(23qL;A]/\"\n        \"0pSaIhFY?DCP5T;->$u$%&WO;-BjTP&B[RS%RmrZ#(iNn0\"\n        \"q3P,MRT8f3Y8mA#r8oQWXg^R/\"\n        \"eJ0Pd#gL*6p)M*MM4N*MZNt1'-2O$Mq&T9C[PbIh3^Ps-L5k+MHH6uulhP;-`U18.>gQ#-%\"\n        \"ua-?=7H,*jPe)*ow3c%=CAC#DOi8.'U&@#J29f30d%H)\"\n        \"*t%s--sxs$7)(=6@rFq&6l<d;2d8T#eGv+RrkU/\"\n        \"$NYwi>pUHh,^26O07+20C1#kjLC^GrHoV_'R;FG&#,0,t-#L$iL<N]L(LeCH-#7w&/\"\n        \"C7r?#41X<8W<orsneKD?A<B30+-2QC)Aa%$\"\n        \"St005lZxF-MaI6K6?TrJV<2.I#S.3VB8xiL7Tn##a^''#I`gT7IS($-cEk9%*oNn0>0;hLs,\"\n        \"Jd))afF4EZ$@'vKCo0o+`^#D.Cv$X>a(66hQ;-XV>=lFcof)f$Y=Yo8Yg$LiY,Kh=+9/\"\n        \"DX=r/e0P[$snP;-f.vS%mH-Zt<E&;QK.xH,.n4&7tS2q/c=-a5)[Is&A/\"\n        \"#2C'7O2CI+M3M_uT2^SJx/&>ImO(.@cS#GJ(p#Enk$/\"\n        \"uw(##TQj-$cehZfWq;?#UZZ;?n_oF.Roqr$W`qP8\"\n        \"Q@dV[Q]Th#2B0[M^eDJ'$Gb;?pFqB#W@.@#$'CJ#HSGJ?e]O&#b=Pk%Wg@?$*;YY#S1U;0e%\"\n        \"l0M+b;;?Vs9:2rvKLNOUZ##EtTs'1-;hL6aA@#`r(kkv9L(ARc`:2mLm]ODknO($o%2'\"\n        \";6jl?&G4Aucr?T-d$EE%4ctM(Ev>X$;FV@.PV?B=b/'Z-cpfZ0]Xj=.1(^S#FBlo>q/#+%/\"\n        \"6B2#T++,MkeP;-:ar;?EdkfGUJK_%/XSHc91lQMS'wIU@WK`apr.;6;hV/2;s,t$w]B.*\"\n        \"]-'u$Un0a#Mq?jBP2QK)4(7]6gH7lL$KMG)sN_hLREfX-u3YD#aUp+M=)/\"\n        \")*HH3$6puxl1ULY>#=<a*cOrcB,$_I70_2n+6$N&+QOg?O$Tt5;dKJDW-8biV-=xG>,jBhb=\"\n        \">?l132=hf)\"\n        \"'RMx(q@K60,YpBX;s13aJ:?>#gxqs--d0)<J#tY-CgrA#/\"\n        \"a3b$M6Ts-?\\?<jLP.1Z-OYC]&6_Aj0(_JJ.oNv)4V%%aED-PEt?JxCu?W;3CetKJ)s>W]$h)\"\n        \"*e)adwKP$.5nWHgsKPm7m=G\"\n        \"sOY-QUt'3$AB(7#,Fil%BY6W-j9uBA#f+D#0sqYXG-Xpf%SN/\"\n        \"Tc#Pv7CU`UI%k9xLI%###wF5s-e/O$MrQrhLhn(-M3uID*ov@+4Nf,lL$k3Q/\"\n        \"UP5W-jkx<(v:+5A7uMvL/xocMa:2G`\"\n        \"-DR'OK?CU9o][A,%fQ2(4b/O(0Zc8/\"\n        \"HjE.3%q]8%MM?O(3dO;-SuMD?=[o[u.26(&io3=?N_W;-=KeA?:3js%#5L^uLJ76/\"\n        \"%3uM'OKIIMBt;fUld%_]U8JwB8@bqg?4aY$+%^=7Kwr?#\"\n        \"0S<+3MPsD#+IA8%J#ID*,+:^#Ka`6N_j5D#PRC16bR2o.W;LpA&H$9%KlKAt%1@A,+\"\n        \"lbOoi0%f3hgv;Hq9<Cm+90)*X*3&6q/Qn.XLi5B'EqS%I>XDs#)LA,/\"\n        \"0_LpO6*f3W4OrLMhgKm\"\n        \"/2`$#>@<9..Sl##/It/1jH3Q/fU:8.&N1Dd/\"\n        \"NYS._J@5$Ii5<-+2)2'$RCG)lNEM0%'%<&A+Cf)EcG##+crB#%1]5/\"\n        \"?29f3^*6,MgTdP.e4B>,7u>X(>&<+3G@oT%5H8f3k?jv6,:tG+\"\n        \"o;An1//\"\n        \"DH?Rk)d#S>1?dn3A+`O#N$.+4J30c#HMXRpRQ0`o=8CQr,[H7uw97%3)6:wAi$>'vus-fR(\"\n        \"dOg.:a<mUve43w?AOMgSV6`xn<:](&8q9F7.uASS;-W<ZGRJnu?CPn+2=[KA(H\"\n        \"2lMY$>tqFrSO9MB:s79&b7m3'h`)fO=2:Z-F9B+%RS/\"\n        \"H2oW&B)%RM1)6kjNL]PMP0^SY%&dfv1'GZWCj$kuY>fx;[$Hm$)*L4:$)G.Vv#n6+T.-E?\"\n        \"A4)T9s$4fMB#?3Gg19[oC#^RS[u\"\n        \"4rpl/Zw2mQi;8/\"\n        \"(LrsGhfhQ;-D7_J#11F7)tZp(fW?A3g2IK9JCao3%C;Dk#ID(7#UEaP]&(;]/2[9&$,CTm/\"\n        \"_R(f).73Q/W:`e$@DUv-?]d8/[Xxw#iN%;Q2=hf)t_6$6[1+51[(J80\"\n        \"lL&s$X#c]#bcHs-I;:-<h)r50F@dY,q@M<-j6We$Z,g;-6-0b%NRg(Q%G%C#Jr0[%-'k=\"\n        \"GYS`m$Ygh>-(7,h%6ImO(4`%b/XRG)4:AK?dU]Op@P@Wt(29:N0XDF&#D-4&#@Y:l95Y^-6\"\n        \".p[D*MkJv$&O(02qNh'HVIR8/OM>c4o6T9Ci#:9AM68p3/bc;/\"\n        \"wwpouVuP$7U2t(G<h4(-91`'Rlg22D(q5(-t:'`QM7`P=+WvNFLNPN09@%%#Q,Gj^..GgL?\"\n        \"7^J1<5'eZ1Ar`FXq*,4\"\n        \"IjTv-`aoI*?/=>d$tn=GR)Bq%.sV=Yp/R['?T3>dw]I7nr2</\"\n        \"&fdc;d+J*F@-__$Bpi5<-92SB.;%'h(]:nW-vP8-u>)0i)<W9.$nj@e#RS`du%k=DC$fB^#\"\n        \"QgINMEaIa$J),##^9)8$\"\n        \"U<_+mic.)*W+]]4[*QZ%JRo8%Tc:Z#Ei?<.V,ZA#]%-29Es.[53B'EY3d$=/Xac_=V8w@HP/\"\n        \",j':Vh3?;a5WRYG4=CG_(T:IZ1aue)q%[6k:d2+5t9Dwfx9D1[sr$N.2KEKPB.*[hi?#\"\n        \"#,NF357[x6[bZN))c7C#S'Ig)dk=X(pu8f31m@d)nSID*-%1N(5Llj1(7U`>#f[w#*CAu$l%\"\n        \"W4'AWio0lV?;%*HW8$rN$ENF%mW-1h;N,fW2^#Soq68;vv$v`=At#a(U'#fdS2^7XW$#\"\n        \";Z[0)1RL9Mc%g<$D.Y)4#,]]4(R7f3_Jp5AYFn8%CP,G4L#G:.l@h&6]?wG*^Tbp%Pp$[-\"\n        \"YJ.d)@LR8%gTl&>bdh:%Zj::86k@e#S0P%?ZhN_,$(Q,*@:Z(+ZhEC,U;rG)cxN**5'A*+\"\n        \"ZfrL1G9=:8ej_<9w5-%-H?[i96V22'dA4P;Ar3X.oCt$%4dChN,ZGg1AG+jLs:Rv$0DHb%/\"\n        \"W8f3w<uD#.B8$&wLU*N`$o>QO(g^$/N]O(*X/A,7PGk<Lw(A5IrdM=&Qg1:>l*B,91Jh_\"\n        \"1rtA#n-[F69bi=-)Y8#%%9ve<EHDr&YoBj2-BHM;`ATcGLVA>,mE&#*;a%=d]d`oA&a:%I-\"\n        \"rY<-YwhQ8mAT;.>v:Q/xrZ&4nQXA#mJvJ1'LA8%55%&4iCAC#o4/B7@N<$6GM1Ddh10H[\"\n        \"&..Pd1MBDXp&r3C*cF1$:40@-C3A/\"\n        \"2e:88SWq,IfiBj*%Isn<a<BsYad+m8])0KfL2=LP8ew^#$J$ok9NHSj0`N<P(p@7<.vu/\"\n        \"+*T5Du$IU13(8E%8W;]dIh4hC#K+.VU?.MU7XtuW;-\"\n        \"9id;@U&@#MDvEW#:,Wl=wC*-?7huM(.1wX-MxG>#Ak<PXnK[h#OUvcMK''P#vUJM&#ITa+:.\"\n        \"OF3)DYo.ZcRUdb*(m8cKW9`%0S;-[PE`N<dic)Dqa?#aZ'r.gUMW-<SEL5:)ap^]q)$#\"\n        \"6o/k$/\"\n        \"k#R&Xw[J1@SW@$.kE:.K?#<.BRKfLIVOjLrND-*ThWO#leFHdT+U#X7uj>dodG%I9mqlKtE`\"\n        \"?W(H0IV0:(Lf+hB'IWR#S&#C(,)b[18.%'%<&5ckI)A]G##=Z[0)<L:_ALR^fL\"\n        \"fEPA#d?8d3W6[UT..;3a27wBC(DeLJ(8;=C7@Ga[$Cw:0Gu8Z$&feCa2*n_VTF^*nL'sGCo^\"\n        \"5`HJ-(k&9D@j0SEaP]ckIJ1M]J,3x/_5/9`p>,VCs?>/>uo1NXFb329$C#mS=`uM*cB,\"\n        \"oWG80=W3$6DC5-2hX6#6lrfs)n$wBt[_:$624L^#XX=)#o_d;-L=2e$'a8>,PkVT+BSYY#\"\n        \"39Ks-5seC>h/>)47?v20BsZ&4vBo8%6VA1;`I,;Q'_>]u.ob318q.PdYkZ:([v'k#Ohv1'\"\n        \"Fd<U)8:@5/\"\n        \"U'xU.c_$<SHQ6=&&'grKV$WZ#fS6g3BgdC#D,_^usT6^gWNGFdt=(nu?+hOdteMGZh$,\"\n        \"qKfW/%#/S2I.ZXM?#*gJD#6hX=$kpB:%X:Ls-i.<9/g]7Y.c;R)*qO`S%FpD9)\"\n        \"kmTM')>wY7el.[7?R:F#C(ZCj^0v.3r^>r%xkkf,8>b:=AW[128wZ7-eaCo@OWe>^DKd#-\"\n        \"sOQ-H7q/W-,hrR8[,lA#G[+Y$)?PF%Q0'#A[/_#$0CpFCUxT<hh&eA#(JU5'$fTB#agfI*\"\n        \"66;hLQm`a4=dT3C_Z3>dg2N#$Df(T.3tuFrpD5T'd:35&CEcf(%'%<&[WpQ&Nhi2M@mnO(\"\n        \"cFGH3L#G:.#&%Q/1r7DCUeRUdRY0[K20Biu`4+Aux$jrF;,$eko_g1<++pE@#DSq;;S?>d\"\n        \"DA-D-FdC>8lpBmVREIU7IKh;I@^2<%-xNn0,Mp+'$,-J*)vsI3>I9]$,'30;PcD218NY7I_\"\n        \"iq&5(p(;QX4;f$00O$M$R>L8k-=2C:L3>dw8_+$R8La<6w9B#r]i,)Lx$m/:j8>CZkTIC\"\n        \"o:gV-ks*6L5[.Pdo_A8#.+SaExR?T.jd0'#4pm(#T%T*#(pkn8bVTf3w4eW.6itI)KF.<$/\"\n        \"(On0xJ0s$-Q'<-)noH;HBIT*#7=.-F3OZ0O)cB,VR,W-lS#+%)[txW+=2.Itn@&FBD4Au\"\n        \"qCxfM@fP;-tNpA,tC`e$$jE_&uh1vH?JreMiMrtOo3NK:Som_#XDF&#E@%%#S,Gj^pVr0(.\"\n        \"j^s$uS52=34pL($A(s-R&SF4#':Z'WO2?u^GwM0GbN?k(C.>6:0niLPJ=jLiK/[#oxBI;\"\n        \"9rS,3:wd3`g-I##BdS2^4D>##9H%O(03HA47FUhL&5oC#D6&tu&VZTIcm'^uY+'U#3CU9'\"\n        \"Gs0&=S8-?\\?1435&[,mQWh0bT%bvD#$@@.H3Q7qo.GXuS/L-YG%<j5/1wBw]-LW1F%a,bfu\"\n        \"jn4[$rRcIhWQO>d:ThxM>rmh'*,pu,%'%<&eWAW-]VT49m:ge?M$nDNLt2.(Aa2.IRjjrq@)\"\n        \"#)>/Gj.(Vf9vHuDPn*w9m1<BC$_#>[u##EdS2^wVvw$t4e;%VRG)48'lA#DX_a4%#q3C\"\n        \"]DwFrwxxa#_Em=G8PL6MLnx,VR#O=(F`Aq^c93$#IL7%#`bkh(Z`#&)&*nD3BnPu%F*kp%^/\"\n        \"p5S'9]9:KO1/1U)6<-+].:.tW`-#6,NH2KEaP]_kIJ1@16g)w*gm0dG`9&wgx9..FE:.\"\n        \"_r0%6kUlf)L6#53kw*B0_CQ;-NKxCug1uV$T$n#6PEpOMB[vY#'`87.qjQiLX5P=$Tr[fL<\"\n        \"knO(W=K,3he9:@bb5g)-24m$=FS_#MGY2CZ.15A,m7I,Vqd[,;O&7/=,xr6Vs$21QMp=G\"\n        \"(6B]uOTM@,d*,5T$v`K(tj;TD38t9D/\"\n        \"9s9D,D5;-OG[#-.Y]fLB?@A4;'PA#YiWI)N_h&]cZs?#)CuM(xQ:a#2aTb%@rJ>(%seh(\"\n        \"5qUJ)v>^c30C%X-cl^',K<Y`uak@e#8Z;v#Qts3T\"\n        \"aCOC,/\"\n        \"U]w:sH&;Q+^>[J6_r=G:1PG-[Hof$9REM0`:t;.2VJfLG7SX-1(:+*]R?tJec]+4wVp.*4+\"\n        \"h$[N_j=.(0rC$G,^w6%4^S#QFD.*[h3LDVCi2Rva)re&YiPZSMuFrt^#l'**Lq%\"\n        \"S>4G`HYET%41*I-1O/\"\n        \"72D1IE5704mQ-Xuw#@awX)0@lm'L?^<8mIgZf^gO9%)O$]%I>mA#2K4L#5m;;?[iZ=YBul=\"\n        \"G$jV=YA<pHS,S`du%8V9CsdVU%OYl;-jf[m-XaI_8l]B.*.eBN0\"\n        \"oPVD3;S<+3*akBO<5:Z-tUXEeq645Cu3`19'gM*6o(PP0F7I20%RM1)UZC80bmOh#]PbIhL?\"\n        \"R@,`f*5AIuN#-U=*OWaEj$#Xq$*..C(-M]-'CO3@Bj0LNv)4]MUM73vOG=6UfM=`ZW>C\"\n        \"$ed19avj=GAWD80$tn=G`9#j=w[)_f-u13Vm^3$#^Kb&#B`gT7?v+B,^wnW$*oNn0]K)%%\"\n        \"Zd*P(d+8`&3,7`&5ptNFd/<-&vX0^#0W5W-6q3+%TEKNrWtvYuT(;?#HYYF%mZ&ZulT7U.\"\n        \"a0j=GTu_H-'NA[9j=%a+0QSn0ji),)$S'41TH8&,2xg%%_c^-Q2i:8.AKwUMe8nqMST3F.?\"\n        \"FYI)Q#P)4XIVp.`8i;dd*3I$PUSCMbV0K-Ag=,/xZ6/&n=ge$f>s-&/>i,MfER$&V)f;d\"\n        \"'41@'_imp%Pcd/\"\n        \":q;h^#87>##9hVr7Fa,[-njTq%-M:;$eN;hLq[3r725Ld4ukP;-n-cp0,s$&4oLc'&*rB^#?\"\n        \"PH$8^+Na,AX,hL(-[pgPGE,#PSuD%`=K,39sZ]4]52T/&@Rq$rUFb3\"\n        \",asFr<o.qL0whf)a$M@,cBYG,2>bduvuMvLjmS@#+.,59+SJ=%dhLS._,$h.^LVZ#)lNn0-%\"\n        \".+5V`v[-kT&],Enn8%O=Y)4wHuD#v.<9/tgOU%U`G>#'6h/N7l5/1PcvsK*=4s-hN<r7\"\n        \"'1Xg*L]rP0UXtUI7`d4Cd>O'gL/m/C2$).P`YP/\"\n        \"CTCRr8RMB58uVZ?\\?Mn78%:CRS%366Q8]`O_J>,guC(pH@9SZK<-Oe/\"\n        \";%>H&7WT4T&#HYTd'&fB9rp`UWf<;DD3/?ID*;^9Z-f(]]4\"\n        \"s@&d)hJu-$6LhF=V765/.?Gu>V465/\"\n        \"F`R9C-1niLpXn'=D(`4)-U%Zu33GouI(*kNmv2*M$tv##'X6iL%pO.)jH_Lj+wn5/Ak3Q/\"\n        \"`W]I*=sG,*Z96a3mtIpYRxed]@86M?DrZ4:Tw/I4\"\n        \"(cKs$l=i@@=R[euf5*523C)-::KF&#qt)W-1;b9D5DPV-T9om,NcYY#TCTfL<,OF3*wRa$)+\"\n        \"&s$rTZd3b@u)4+Rw.:XJa`#0LQ3B9978R9j._u?5?,,,i:8..pK+`Y+Xj$::h0,CjTJD\"\n        \"P*U@k$1P]>;>WJ2Gk<MuPQvt$N&###8&M/(N+FD*pk8K1;F9a#AM>Z,r2Gg1Y5#kkf^#;/\"\n        \"+gOxtq6:].3fWiCR;dc.J>V@uP<[:t9KCR#Gv8h=Ed[x+:[K#Qw3l-,SUY$dp2QD-*/Zp%\"\n        \"7L[Q''fNn0RsMhL(mUD34WFL#jL3]-``1I3(Ci;nJj+ru$,_^u=W/\"\n        \"+%JTW;-9Ib2C(q0Pd[Ln;-`b(g@`CrT/am45&O[l^,tGn,Z.s_A*[L2*m%],G4$L[+4p/\"\n        \"0i)NIHg))wZ)4Gt%s-\"\n        \"*^JW$<Utn%V:.%@J@?Yc,RhVD;>tK(m-2hPZwO3C#-grec;uC$K8wp@aST/D420kL2Hf'R/\"\n        \"ogJ*v:P^?f,Q_/P%5p@Qk(T/uxmQWl/]VW,c5g)QO*e.k*_^uV9RH.jZA>#ma;C#aOk9&\"\n        \"I5ts'alVP&/\"\n        \".=X(,3cv*0NcT.m=V0$V_`=-Wwnf$P####'>uu#4#Z3#v6QtLUOvu#AbW3M1ilS.&?F&#D+\"\n        \"2t-[.JfLf:qV-6680-XEM_&5xkA#R&+>.F.$##ejZF%Bk60Hi^u-$&m_#C\"\n        \"NpaR3U)'#_rvg=MHu)?7]f`;Y8NkfMB8O?R4IsJ%JImQWHqPF%5lQ78Kqe`];>%RP@q44N$\"\n        \"en]-3I^l4[1ivPx:K-MF1:x&mp`gL)7GcMZaA%#r&3aaaBV,#4e53#wDmV#*OGF#fejL#\"\n        \"Q$bT#*1rZ#,8W`#/\"\n        \"B=e#0@gi#5Iuu#JBr($iT<5$&IR<$ULaH$jx%T$Vl;[$uAXa$Lvc3%R)=&%*TT:%tJ%H%\"\n        \"bCUn%Qj,V%+XAa%+%wg%8/]l%<<Bq%qrp#&HjuJ&8B[<&Bc9F&bGgQ&\"\n        \"VZ3X&('i_&H#dn&KLSx&]ieH'NXD<'ZogB'K*<h'/\"\n        \"?(Z')1j`'l)p.(qY,r',Knw'XP[D(,6*5(fW_;(5n+B(D9_N(:&tX(S*a))vB$k(Jo>v(>+^\"\n        \"2)([g^)VflN)@+'#*l*qf)5@=m)\"\n        \"B7B>*$h)-*d235*[:B>*q^vG*Wv#c*i62<+efC,+8cl3+(FD<+=u_G+uFNT+XExX+Z_p^+e(\"\n        \"%d+hp;h+um9n+Lxsx+A3?,,9Ss5,wv%A,N$4M,[rds,meEa,PaBj,h4<9-ObL,-csjA-\"\n        \")#NL-N^'R-uibY-T#-g-dfv4.oKp#.RTT+.pd(S.h.QG.f(Wl.o>D[.#//e..qL4/rmj&/\"\n        \";fDL/e>V</6]2L/Ys(W/L$9^/M48.0'W7t/RMgF0/#####YlS.oC+.#2M#<-MeR+%h)'##\"\n        \"&/YaE0]Wh#WtLrZ[dM_&dQd(N/\"\n        \"(1B#SdrIh01L^#iq<on1:h#$-MWY52C-?$KTfuG3LHZ$##QiT:$Ew$G%w@b'5>>#]F[0>\"\n        \"M3o-$.(1B#L[+Vd<UL^#aYkCj1:h#$]xR.q2C-?$@fEPA\"\n        \"3LHZ$i=w1K><jw$:n>YY.JCYG?mv1BujNe$#t7;-T<XoIu5Ad<FKWf:D49?-6J,AF8k)F.D/\"\n        \"Wq)&APcDU^UJDk$cP9GWMDF;F5F%uuo+D#ql>-]tn92gdMk++I2GDYo'?H#-QY5+v4L#\"\n        \"[Z'x0_6qw0>j^uG9T9X1lrIk4+-E/#:,BP8&dCEHp4`PB;^5o1a:XG<2/\"\n        \"_oDCt7FHbmndFgkqdF-4%F-)G@@-G3FGH,C%12AMM=-H1F7/Y1Vq1CTnrL'Ch*#D):@/\"\n        \"5d:J:<N7rL9et3N\"\n        \"5Y>W-xrv9)iaV-#Cv#O-iBGO-aqdT-$GqS-'?Z-.;9^kLJ*xU.,3h'#WG5s-^2TkLcU$\"\n        \"lLp6mp3Ai0JF7DmlE>FK@-1CUO1?6[L28V7ZQpRC`Nn4$##+0A'.6sarLNf=rL]3oiLPVZ-N\"\n        \">2oiL?Z/\"\n        \"eG38vLF%fCkL-:Mt-0aErLc_4rL0)Uk.,gpKF,r0o-T?*1#u<*1#rENvPWpT(MT)rR#(AcY#\"\n        \",VC;$0o$s$41[S%8I<5&tU^l8Ym@M9^/x.:$sul&@$TM'D<5/(HTlf(LmLG)\"\n        \"P/.)*TGe`*X`EA+]x&#,a:^Y,eR>;-ikur-m-VS.qE75/u^nl/#wNM0'90/1+Qgf1/\"\n        \"jGG23,))37D``3;]@A4?uwx4C7XY5GO9;6Khpr6O*QS7SB258WZil8[sIM9`5+/\"\n        \":dMbf:hfBG;\"\n        \"l($)<p@Z`<tX;A=xqrx=&4SY>*L4;?.ekr?2'LS@6?-5A:WdlA>pDMBB2&/\"\n        \"CFJ]fCJc=GDN%u(ER=U`EVU6AFZnmxF_0NYGcH/;HgafrHk#GSIo;(5JsS_lJwl?MK%/\"\n        \"w.L)GWfL-`8GM\"\n        \"1xo(N5:P`N9R1AO=khxOA-IYPEE*;QI^arQMvASRQ8#5SUPYlSYi:MT^+r.UbCRfUf[\"\n        \"3GVjtj(Wn6K`WrN,AXvgcxXot*GV&6`uY*N@VZ.gw7[2)Xo[6A9P]:Yp1^>rPi^B42J_FLi+\"\n        \"`\"\n        \"JeIc`N'+DaR?b%bVWB]bZp#>c_2ZuccJ;Vdgcr7ek%Soeo=4PfsUk1gwnKig%1-Jh)Id+i-\"\n        \"bDciu&FS()Foc2cblh25Lx_#f/mc2k0.+49eF`#j;mc2sTEC5eh#e#?inc2t/-4Ci*He#\"\n        \"D(4)3dhuh26Ox_#h82)3l67+4:hF`#lD2)3tZNC5fk#e#Ar3)3u564Cj-He#F1OD3en(\"\n        \"i27Rx_#jAMD3m<@+4;kF`#nMMD3uaWC5gn#e#C%OD3v;?4Ck0He#H:k`3ft1i28Ux_#lJi`3\"\n        \"nBI+4<nF`#pVi`3vgaC5hq#e#E.k`3wAH4Cl3He#JC0&4g$;i29Xx_#nS.&4oHR+4=qF`#r`\"\n        \".&4wmjC5it#e#G70&4xGQ4Cm6He#LLKA4h*Di2:[x_#p]IA4pN[+4>tF`#tiIA4xssC5\"\n        \"jw#e#I@KA4#NZ4Cn9He#NUg]4i0Mi2;_x_#rfe]4qTe+4?wF`#vre]4#$'D5k$$e#KIg]4$\"\n        \"Td4Co<He#P_,#5j6Vi2<bx_#to*#5rZn+4@$G`#x%+#5$*0D5l'$e#MR,#5%Zm4Cp?He#\"\n        \"RhG>5k<`i2=ex_#vxE>5saw+4A'G`#$/\"\n        \"F>5%09D5m*$e#O[G>5&av4CqBHe#&'32B=.wm2f2$`#q712BER804jJH`#uC12BMwOH5?N%\"\n        \"e#Jq22BNQ79CDmRe#p7LMB@=E33h;-`#tCLMB\"\n        \"Hb]K4lSQ`#xOLMBx;D<BAW.e#M'NMBQa[TCEpRe#r@hiBACN33i>-`#vLhiBIhfK4mVQ`#$\"\n        \"YhiB#BM<BBZ.e#O0jiBRgeTCFsRe#tI-/CBIW33jA-`#xU-/CJnoK4nYQ`#&c-/C$HV<B\"\n        \"C^.e#Q9//\"\n        \"CSmnTC$),##$)>>#hda1#mPh5#vTg5#pHf^?ht^6_#2S4;2ZR%@&g6c+aq-vA4rkL>S=*p.\"\n        \"F.s##GM.tB:oHY-N]IV?:K=?.Ao.R<ZjfsAD0-Q#+^+pAj9Hm#IVoE-@/c[[\"\n        \"ZTC>]DlGQ8d*RH=q1a;.B'hI?\\?C-i<8F4g7ioQV[b+MT..lbD-Lx'<.m7T)0KNI@'F]+Z2.\"\n        \"HZV[#9U<.WKXD-1*6EO`ZwG&Y_bpLx;4/MAbl,M[kQ1MW6ErAQ.r`?IO,Q#$1_A.'$VC?\"\n        \"i6oS1#Yp?-=$)N$hpjHQ<E[g<T=e8.?Cn8.$<*+09MglA13P/11>;E/_UOj$lM5/\"\n        \"Mis]Z0'f:Z[$tOZ[IVxiLkEBB-0_3uL8;5Z2)[S4=Gs2f+3QUD?=5Ok+1KtC?Nx%a+t9_\"\n        \"eH7TDj$\"\n        \"Jk2Q8XO`Y#_0>?84I1gLq.h88mTg5#EmM^[I@'_[0c._J*'#N04El/18x7F/\"\n        \"8o99_b`qpL7]2..KU=5Ma1tv8%N+X%jv>Q&C8H<8ng,ND,K#01`SY<-o*t^-TM@tLL_K?\"\n        \"pewI59RNSE-\"\n        \"7lS?-H-^V[uL+Z2@vv>-qZXv7rR)W[%)^5Bp&aE-$44^-Z,g34B3lV7G),^O[^>01hM8;]+\"\n        \"o>290h';.>X&[-:jL01tQ5293Y)w8;pU01Z,[?-C3aQ8oaT`<*#DT.=<Z][6uY<-%sY<-\"\n        \"7.;t-=*.hLR_vNB0G/?-C[SiB61BQ/\"\n        \"0HesAN)NE-Gxf884cpA(Gs'D?,IAw8_,tD'QN1W1]^W0;l6Ne=Nr.a+SX`=-d[Y=6i)q5##\"\n        \"a0D?6avQ8#rD?8h%wZ?sF:Z[>cMv[,>^;-0`uS.\"\n        \"d>N:.fIY`1)d7Q#xiD^[oV(HOtxa*I_P_;.oYs_&XZ*.-Wdi^#iB;a'J8voL,v&nLXJ4tAm`\"\n        \"/aYx*RV[A)eU%Z:)##N`DZ[nih5#lLM>#`0>?8@8.11,L`Y#qmQ][c<9B-GbE9.C[<9.\"\n        \"wh;?8Ou*[K08o0(2xL$#=;B%BnYAj07v7b+'#vW[h6mh/\"\n        \"?BtW[m26Q#<)d,*1=(,M;PM11c.5F%<b/t[*lu8.uVMj$@M%E+5UL,M?ir11g:5F%@n/\"\n        \"t[.lu8.'&f,&F(O^,cjrD'T>gI-\"\n        \"nWeI-mWeI-'-@:MC8xiLVQ#<-)-@:MED4jLVQ#<-+-@:MGPFjLVQ#<---@:MI]XjLVQ#<-/\"\n        \"-@:MT'G311^-Q#6N3a0KJco.LeiS8PF%U.>=*p.Cb_&4Ht$[-Xt^31/5bW[U?tW[g5Y>-\"\n        \":E%HF:/^5B)/HDO9[4h-3bqY?p=Vt7/\"\n        \"ChB#@h0R0pXQI2Ok5R*sIZj)NQx:.)I?)4J;gJ2@_lA#lL$^(xmoi0WLq-MtS%)*[;w0#\"\n        \"ENh>.=`EC#O5`P-Qmm*MmfC12x@HP8EktW[r89I*\"\n        \"qI(W3lYnV[M0'qLCKV41BG$)*bf-@->u68.==*p.t2Gx9h:_5B+ktpLK&-Q#o?;W3NYWI),\"\n        \"p,*4lS*)**SEjLLYDm0KYmp0em-=.*$GGOG[[D4bhb>-nQ=v-%=ufL]hev6ONjj<7C4g7\"\n        \"-#fm#,7%a+W+$$$qi-<8uG@g%>^3uLVPq%/HsD^[*8WW/\"\n        \">+KE-vLg?-e0WE-$$esA_$d2$'#6k<+vlW--?D_&]A[][djPS3e,K*R&RW5/\"\n        \"&;7-5Eai?B9KJ9io/vERAcM>CQ@#B#sg([K\"\n        \">0]3FwNEp.DxvuP$.0oLGD7p._FOe$E50HbJ3jU1<o?p4lS*)*4(RV[=9q&$X/\"\n        \"o>6rwZ55<smV[i^NmLumY?.s28b-A)8R*ma?R*X#<s.t+SW[S)+p1o+;Z->R/\"\n        \"N-AqYhL,?(&&GWEjL\"\n        \"nkHq9ohN)Y-Z9T(_eMs7]:^;.*dKK-0:%I-S;m)/\"\n        \"u7%N-]_Bil[TXp.#Hu>-*D,XCBWkC?]<3p1kbMG)iEsM62Qsc<xOnY6J=rh55CXv6aQHo0$\"\n        \"s%=7bWQo0*fR>6=Yf88F&eG.@XmP8\"\n        \"Z(-kLE]o;8+dUv@[V[,&cHV].&,*=.V=SF/\"\n        \"JsIg%&w+<8LV'HOK0Y<8hbxV[GQF9B&0:?6fDGY-?)1pprsqY?_?u88aQHo05nw;-a2gr-\"\n        \"Skt)#U7[uNRdamL9dWR8O8S9]:?hc))U&X[\"\n        \"]?tW[>3:o/G*q5#Q`0U[tgt;.&PCs3j-C2:ebE-*/\"\n        \"D+j17uqF.IiF<%$BlW[AS2Z[uBtW[L0$6#D@Ib%o_Ib%%0Pb%r]L9]/\"\n        \":)=-*2#HM%HgnLK1(588pWG<$]bA#u&sc<U)OX(uqIb%\"\n        \"+TSD=6:P&#'Lx>-wS,<-%V,<-CIrIM,sPoL6:)=-7u@Y-_1(F.%.Jb%%0Pb%8)4O+2>hY?\"\n        \"CHNX(:/4O+`OVmLDA2pL%+p+M*G;pLH:)=-:2#HM5SMpLHl+87HUaSA$]bA#/c%pAU)OX(\"\n        \"/LJb%>LxPB#%]u7k'xlB/\"\n        \"HZV[2KL@-5a5<-e(MT.f$h5#UT=Z-JZa$'4DmW[1E(@-IMGHM^L4RMa0nlLQR=RMR>_<.N(\"\n        \"wP5b,RGEb6L[0=-#(&_>3)Fd(nY6lxjDFndXfNanX7MS&c?-\"\n        \"Aa5<-x9]>-P5g,Mg&5;1;k(Z#tco;6Ij@g%a:nS.Hi/\"\n        \"Q#@oi'#Ebl:d+d7Q#hw]][rwB`$nLs5#rlq5#/\"\n        \"YHF%4s1G%VR)##mINX(=3<%$=cDg.VndC?J7VI?+FAw86$/@':bc/Li+kS7\"\n        \";8JoLHF_l8.V&,2a>]N0c3X(NmpL(]jh8*#1J>lLR[+##\";\n\n    return openIconic_compressed_data_base85;\n}\n"
  },
  {
    "path": "demo/korgi.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// NVIDIA CORPORATION and its licensors retain all intellectual property\n// and proprietary rights in and to this software, related documentation\n// and any modifications thereto. Any use, reproduction, disclosure or\n// distribution of this software and related documentation without an express\n// license agreement from NVIDIA CORPORATION is strictly prohibited.\n//\n\n#include \"korgi.h\"\n\n#if KORGI_ENABLED\n\n#include <WinSock2.h>\n#include <mmsystem.h>\n#include <unordered_map>\n\n#ifdef _WIN32\n#pragma comment(lib, \"winmm\")\n#pragma comment(lib, \"ws2_32\")\n#endif\n\n// Help us out during dev by disabling optimisations so we can debug\n//#pragma optimize(\"\", off)\n\nusing namespace std;\n\nnamespace korgi\n{\n\n    bool s_PageBit0 = false;\n    bool s_PageBit1 = false;\n    KORGI_TOGGLE(s_PageBit0, 0, PreviousMarker);\n    KORGI_TOGGLE(s_PageBit1, 0, NextMarker);\n    KORGI_TOGGLE(s_PageBit0, 1, PreviousMarker);\n    KORGI_TOGGLE(s_PageBit1, 1, NextMarker);\n    KORGI_TOGGLE(s_PageBit0, 2, PreviousMarker);\n    KORGI_TOGGLE(s_PageBit1, 2, NextMarker);\n    KORGI_TOGGLE(s_PageBit0, 3, PreviousMarker);\n    KORGI_TOGGLE(s_PageBit1, 3, NextMarker);\n\n    struct Controller\n    {\n        void AddHook(unsigned char controlChannel, Knob* pParam)\n        {\n            knobs[controlChannel].push_back(pParam);\n        }\n\n        void AddHook(unsigned char controlChannel, Button* pParam)\n        {\n            buttons[controlChannel].push_back(pParam);\n            SetLedStatus(controlChannel, pParam);\n        }\n\n        bool Init()\n        {\n            if (!OpenMidiDevice())\n                return false;\n\n            return true;\n        }\n\n        void Shutdown()\n        {\n            CloseMidiDevice();\n        }\n\n        void Update()\n        {\n            int currentPage = (s_PageBit0 ? 1 : 0) | (s_PageBit1 ? 2 : 0);\n            if (currentPage != m_CurrentPage)\n            {\n                m_CurrentPage = currentPage;\n                SetAllLeds();\n            }\n            else\n            {\n                // Update the status of LEDs if the code has changed any of the button values\n                for (const auto& it0 : buttons)\n                {\n                    int cc = it0.first;\n                    for (Button* pButton : it0.second)\n                    {\n                        if (((pButton->GetPage() == -1) || (pButton->GetPage() == m_CurrentPage))\n                            && (pButton->GetLedStatus() != pButton->GetState()))\n                        {\n                            SetLedStatus((unsigned char)cc, pButton);\n                        }\n                    }\n                }\n            }\n        }\n\n        ~Controller()\n        {\n            Shutdown();\n        }\n\n        static Controller* Get()\n        {\n            if (!s_pController)\n            {\n                s_pController = new Controller();\n            }\n            return s_pController;\n        }\n\n    private:\n        static void CALLBACK MidiInCallback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)\n        {\n            if (wMsg != MIM_DATA)\n                return;\n\n            char controlChannel = (dwParam1 >> 8) & 0xff;\n            char midiValue = (dwParam1 >> 16) & 0xff;\n\n            s_pController->HandleMidiInput(controlChannel, midiValue);\n        }\n\n\n        void HandleMidiInput(unsigned char controlChannel, unsigned char midiValue)\n        {\n            auto button = buttons.find(controlChannel);\n            auto knob = knobs.find(controlChannel);\n\n            if (button != buttons.end())\n            {\n                for (auto b : button->second)\n                {\n                    if ((b->GetPage() == -1) || (b->GetPage() == m_CurrentPage))\n                    {\n                        bool isPressed = (midiValue > 0);\n                        switch (b->GetMode())\n                        {\n                        case ButtonMode::Momentary:\n                            // Set the value to the current state of the button\n                            b->SetState(isPressed);\n                            SetLedStatus(controlChannel, b);\n                            break;\n                        case ButtonMode::BoolToggle:\n                        case ButtonMode::IntToggle:\n                            if (isPressed)\n                            {\n                                // Toggle the button\n                                if (b->GetState())\n                                {\n                                    // Turn off\n                                    b->SetState(false);\n                                    SetLedStatus(controlChannel, b);\n                                }\n                                else\n                                {\n                                    // Turn on\n                                    b->SetState(true);\n                                    SetLedStatus(controlChannel, b);\n                                }\n                            }\n                            break;\n                        }\n                    }\n                }\n            }\n            else if (knob != knobs.end())\n            {\n                float fvalue = (float)midiValue / 127.f;\n                fvalue = max(0.f, min(1.f, fvalue));\n\n                for (auto k : knob->second)\n                {\n                    if ((k->GetPage() == -1) || (k->GetPage() == m_CurrentPage))\n                    {\n                        k->SetValue(fvalue);\n                    }\n                }\n            }\n        }\n\n        bool OpenMidiDevice()\n        {\n            if (midiInOpen(&m_MidiInHandle, m_DeviceIdx, (DWORD_PTR)MidiInCallback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)\n            {\n                return false;\n            }\n\n            midiInStart(m_MidiInHandle);\n\n            // Try to open the nanoKONTROL2 as an output device\n            uint32_t numOutputDevices = midiOutGetNumDevs();\n            for (uint32_t i = 0; i < numOutputDevices; ++i)\n            {\n                MIDIOUTCAPS caps;\n                midiOutGetDevCaps(i, &caps, sizeof(MIDIOUTCAPS));\n                printf(caps.szPname);\n                if (strncmp(caps.szPname, \"nanoKONTROL2\", strlen(\"nanoKONTROL2\")) == 0)\n                {\n                    if (midiOutOpen(&m_MidiOutHandle, i, 0, 0, CALLBACK_NULL) == MMSYSERR_NOERROR)\n                    {\n                        // Set the initial status of the LEDs\n                        SetAllLeds();\n                    }\n                    break;\n                }\n            }\n\n            MIDIINCAPS inCaps = {};\n            MMRESULT res = midiInGetDevCaps((UINT_PTR)&m_DeviceIdx, &inCaps, sizeof(MIDIINCAPS));\n\n            // OWRIGHT : We Get MMSYSERR_BADDEVICEID, but it still works.\n            return (res == MMSYSERR_NOERROR) || (res == MMSYSERR_BADDEVICEID);\n        }\n\n        void CloseMidiDevice()\n        {\n            if (m_MidiInHandle)\n            {\n                midiInClose(m_MidiInHandle);\n                m_MidiInHandle = 0;\n            }\n        }\n\n        void ClearAllLeds()\n        {\n            const unsigned char kFirstCcToClear = 32;\n            const unsigned char kFinalCcToClear = 71;\n            for (unsigned char cc = kFirstCcToClear; cc <= kFinalCcToClear; ++cc)\n            {\n                SetLedStatus(cc, nullptr/*pButton*/);\n            }\n        }\n\n        void SetAllLeds()\n        {\n            ClearAllLeds();\n            for (const auto& it0 : buttons)\n            {\n                int cc = it0.first;\n                for (Button* pButton : it0.second)\n                {\n                    if ((pButton->GetPage() == -1) || (pButton->GetPage() == m_CurrentPage))\n                    {\n                        SetLedStatus((unsigned char)cc, pButton);\n                    }\n                }\n            }\n        }\n\n        void SetLedStatus(unsigned char controlChannel, Button* pButton)\n        {\n            if (m_MidiOutHandle)\n            {\n                union\n                {\n                    DWORD dwData;\n                    BYTE bData[4];\n                } u;\n                const uint8_t kMidiChannel = 0;\n                u.bData[0] = 0xb0/*control change*/ | kMidiChannel;  // MIDI status byte\n                u.bData[1] = controlChannel;  // first MIDI data byte  : CC number\n                u.bData[2] = (pButton && pButton->GetState()) ? 127 : 0; // second MIDI data byte : Value\n                u.bData[3] = 0;\n                midiOutShortMsg(m_MidiOutHandle, u.dwData);\n                if (pButton)\n                {\n                    pButton->SetLedStatus(pButton->GetState());\n                }\n            }\n        }\n\n        //static const string device_name = \"nanoKONTROL2\";\n        static Controller* s_pController;\n        HMIDIIN  m_MidiInHandle = {};\n        HMIDIOUT m_MidiOutHandle = {};\n        int m_DeviceIdx = 0;\n        int m_CurrentPage = 0;\n\n        // Maps indexed by control channel\n        unordered_map<int, std::vector<Knob*>>   knobs;\n        unordered_map<int, std::vector<Button*>> buttons;\n    };\n\n    Controller* korgi::Controller::s_pController = nullptr;\n\n    void Init()\n    {\n        Controller::Get()->Init();\n    }\n    void Shutdown()\n    {\n        Controller::Get()->Shutdown();\n    }\n    void Update()\n    {\n        Controller::Get()->Update();\n    }\n\n    Button::Button(int page, Control controlChannel, ButtonMode mode, bool* pValue, const std::function<void(void)>& m_fp)\n        : m_Mode(mode)\n        , m_pValue(pValue ? (void*)pValue : (void*)&m_LocalState)\n        , m_PreviousState(false)\n        , m_LocalState(false)\n        , m_OffValue((int)false)\n        , m_OnValue((int)true)\n        , m_Page(page)\n        , m_Callback(m_fp)\n    {\n        m_LedStatus = GetState();\n        Controller::Get()->AddHook((unsigned char)controlChannel, this);\n    }\n\n    Button::Button(int page, Control controlChannel, int* pValue, int offValue, int onValue)\n        : m_Mode(ButtonMode::IntToggle)\n        , m_pValue((void*)pValue)\n        , m_PreviousState(false)\n        , m_LocalState(false)\n        , m_OffValue(offValue)\n        , m_OnValue(onValue)\n        , m_Page(page)\n    {\n        m_LedStatus = GetState();\n        Controller::Get()->AddHook((unsigned char)controlChannel, this);\n    }\n\n\n    bool Button::GetState() const\n    {\n        if (GetMode() == ButtonMode::IntToggle)\n        {\n            return *reinterpret_cast<const int*>(m_pValue) == m_OnValue;\n        }\n        return *reinterpret_cast<const bool*>(m_pValue);\n    }\n\n    void Button::SetState(bool state)\n    {\n        if (m_Callback)\n        {\n            m_Callback();\n        }\n        if (GetMode() == ButtonMode::IntToggle)\n        {\n            *reinterpret_cast<int*>(m_pValue) = (state ? m_OnValue : m_OffValue);\n            return;\n        }\n        *reinterpret_cast<bool*>(m_pValue) = state;\n    }\n\n    bool Button::WasMomentarilyPressed()\n    {\n        bool retVal = false;\n        const bool state = GetState();\n        if (state && !m_PreviousState)\n        {\n            retVal = true;\n        }\n        // Clear the previous value, so this function only returns true once.\n        m_PreviousState = state;\n        return retVal;\n    }\n\n    Knob::Knob(int page, Control controlChannel, float* pValue, float mi, float ma, const std::function<void(float)>& m_fp)\n        : m_pValue(pValue ? pValue : &m_localValue), m_MinValue(mi), m_MaxValue(ma), m_Page(page), m_Callback(m_fp)\n    {\n        Controller::Get()->AddHook((unsigned char)controlChannel, this);\n    }\n\n} // namespace korgi\n\n#endif // KORGI_ENABLED\n"
  },
  {
    "path": "demo/korgi.h",
    "content": "#pragma once\n//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// NVIDIA CORPORATION and its licensors retain all intellectual property\n// and proprietary rights in and to this software, related documentation\n// and any modifications thereto. Any use, reproduction, disclosure or\n// distribution of this software and related documentation without an express\n// license agreement from NVIDIA CORPORATION is strictly prohibited.\n//\n\n//\n// This module allows for a Korg nanoKontrol 2 USB MIDI controller to be used to tweak in-game variables.\n// The Korg nanoKontrol 2 is a low cost device with buttons and sliders.  In many cases, tweaking using\n// this device can be much more direct and much easier than using ImGUI.\n// https://www.korg.com/uk/products/computergear/nanokontrol2/\n//\n// There are Init, Shutdown and Update calls that need making to initialise the system,\n// shut it down, and Update it (just call Update once per frame).\n//\n// Controls are grouped into pages.  There are 4 pages numbered 0-3.  The current page can be\n// selected using the << and >> buttons as a two bit binary number.  They will illuminate to\n// show the current selected page.\n//\n// There are a couple of helper macros to handle the most common use cases of toggle\n// buttons to control and react to a pre-existing bool, and sliders to control a pre-existing float.\n//\n// Declare a toggle button like this....\n//     KORGI_TOGGLE( Variable, Page, Control )\n//     E.g. to have a cheese toggle button on button S1, you would do this\n//         KORGI_TOGGLE( g_cheeseEnable, 0, S1 )\n//\n// Declare a knob like this...\n//     KORGI_KNOB( Variable, Page, Control, MinValue = 0, MaxValue = 1 )\n//     E.g. to modulate your turbo encabulator on Slider 1, you would do this\n//         KORGI_KNOB( g_turboEncabulator, 0, Slider1 )\n//\n// Another use of buttons is that of 'momentary' actions, where you want to test\n// if a button has been pressed and perform some action.\n// In order to use this mode, you need to Create a korgi::Button variable that you can\n// then call `WasMomentarilyPressed()` on to test if that button has just been pressed.\n//\n// g_launchMissilesButton = korgi::Button( 0, korgi::Control::M1, korgi::ButtonMode::Momentary );\n//\n// if( g_launchMissilesButton.WasMomentarilyPressed() )\n// {\n//     if( GentleConfirmationDialogue( \"Are you sure?\" ) )\n// ...\n//\n\n#include <functional>\n\n#ifdef KORGI_ENABLED\n//  External control\n#   if (KORGI_ENABLED != 0) && (KORGI_ENABLED != 1)\n#      error \"If you define KORGI_ENABLED, please set it to 0 or 1\"\n#   endif\n#else\n//  Enable it by default, otherwise why would you be including it\n#   define KORGI_ENABLED 1\n#endif\n\n// Ensure that korgi is only compiled into Windows platforms\n#if KORGI_ENABLED && !defined(_WIN32)\n#undef KORGI_ENABLED\n#define KORGI_ENABLED 0\n#endif\n\nnamespace korgi\n{\n\n#if KORGI_ENABLED\n    void Init();\n    void Shutdown();\n    void Update();\n#else\n    static inline void Init() {}\n    static inline void Shutdown() {}\n    static inline void Update() {}\n#endif\n\n    // Macro concatenation machinary\n#define KORGI_TOKEN_PASTE(x, y) x##y\n#define KORGI_CAT(x,y) KORGI_TOKEN_PASTE(x,y)\n\n// Helpers for the easy cases to control existing bools, ints and floats\n#if KORGI_ENABLED\n#define KORGI_TOGGLE(variable, page, control) static korgi::Button KORGI_CAT(s_KorgButton_, __LINE__) (page, korgi::Control::##control, korgi::ButtonMode::BoolToggle, &( variable ));\n#define KORGI_BUTTON_CALLBACK(page, control, callback) static korgi::Button KORGI_CAT(s_KorgButton_, __LINE__) (page, korgi::Control::##control, korgi::ButtonMode::BoolToggle, nullptr, callback);\n#define KORGI_INT_TOGGLE(variable, page, control, offValue, onValue) static korgi::Button KORGI_CAT(s_KorgButton_, __LINE__) (page, korgi::Control::##control, (int*) &( variable ), int(offValue), int(onValue));\n#define KORGI_KNOB(variable, page, control, ...) static korgi::Knob KORGI_CAT(s_KorgKnob_, __LINE__) (page, korgi::Control::##control, &( variable ), ##__VA_ARGS__ );\n#define KORGI_KNOB_CALLBACK(page, control, callback, ...) static korgi::Knob KORGI_CAT(s_KorgKnob_, __LINE__) (page, korgi::Control::##control, nullptr, callback, ##__VA_ARGS__ );\n#else\n#define KORGI_TOGGLE(...)\n#define KORGI_INT_TOGGLE(...)\n#define KORGI_KNOB(...)\n#endif\n\n    enum class ButtonMode\n    {\n        Momentary, // Use Button::wasMomentarilyPressed() to Get a single 'true' for each press.\n        BoolToggle,\n        IntToggle,\n    };\n\n    // Enum for all the control channels on the Korg nanoKONTROL2\n    // Numbering starts from 1 to match the Confluence page descriptions.\n    enum class Control : unsigned char\n    {\n        // 'S' Buttons\n        S1 = 32,\n        S2 = 33,\n        S3 = 34,\n        S4 = 35,\n        S5 = 36,\n        S6 = 37,\n        S7 = 38,\n        S8 = 39,\n\n        // 'M' Buttons\n        M1 = 48,\n        M2 = 49,\n        M3 = 50,\n        M4 = 51,\n        M5 = 52,\n        M6 = 53,\n        M7 = 54,\n        M8 = 55,\n\n        // 'R' Buttons\n        R1 = 64,\n        R2 = 65,\n        R3 = 66,\n        R4 = 67,\n        R5 = 68,\n        R6 = 69,\n        R7 = 70,\n        R8 = 71,\n\n        // Other buttons\n        PreviousTrack = 58,\n        NextTrack = 59,\n        Cycle = 46,\n        SetMarker = 60,\n        PreviousMarker = 61,\n        NextMarker = 62,\n        Rewind = 43,\n        FastForward = 44,\n        Stop = 42,\n        Play = 41,\n        Record = 45,\n\n        // Knobs\n        Knob1 = 16,\n        Knob2 = 17,\n        Knob3 = 18,\n        Knob4 = 19,\n        Knob5 = 20,\n        Knob6 = 21,\n        Knob7 = 22,\n        Knob8 = 23,\n\n        // Sliders\n        Slider1 = 0,\n        Slider2 = 1,\n        Slider3 = 2,\n        Slider4 = 3,\n        Slider5 = 4,\n        Slider6 = 5,\n        Slider7 = 6,\n        Slider8 = 7,\n    };\n\n    struct Button\n    {\n#if KORGI_ENABLED\n        // Constructor for controlling a bool variable\n        Button(int page, Control controlChannel, ButtonMode mode, bool* pValue = nullptr, const std::function<void(void)>& m_fp = nullptr);\n        // Constructor for toggling an int variable between two states\n        Button(int page, Control controlChannel, int* pValue, int offValue = 0, int onValue = 1);\n\n        // Returns true only once as the button is pressed\n        // (will not continue to return true as the button is held)\n        bool WasMomentarilyPressed();\n\n    private:\n        friend struct Controller;\n\n        bool GetState() const;\n        void SetState(bool state);\n        bool GetLedStatus() const { return m_LedStatus; }\n        void SetLedStatus(bool status) { m_LedStatus = status; }\n        int GetPage() const { return m_Page; }\n        ButtonMode GetMode() const { return m_Mode; }\n\n        ButtonMode m_Mode;\n        void* m_pValue;\n        int m_OffValue;\n        int m_OnValue;\n        bool m_LocalState; // If pValue initialised as nullptr\n        bool m_PreviousState;\n        bool m_LedStatus;\n        int m_Page;\n        const std::function<void(void)> m_Callback;\n#else\n        Button(int, Control, ButtonMode, bool* pValue = nullptr)\n        {\n            (void)pValue;\n        }\n        bool WasMomentarilyPressed() { return false; }\n#endif\n    };\n\n    struct Knob\n    {\n#if KORGI_ENABLED\n        Knob(int page, Control controlChannel, float* pValue, float mi = 0.0f, float ma = 1.0f, const std::function<void(float)>& m_fp = nullptr);\n    private:\n        friend struct Controller;\n\n        void SetValue(const float newRawValue)\n        {\n            *m_pValue = m_MinValue * (1.f - newRawValue) + m_MaxValue * newRawValue;\n            if (m_Callback)\n            {\n                m_Callback(*m_pValue);\n            }\n        }\n        int GetPage() const { return m_Page; }\n\n        float* m_pValue;\n        float m_localValue; // if pValue initialized as nullptr\n        float m_MinValue;\n        float m_MaxValue;\n        int m_Page;\n        const std::function<void(float)> m_Callback;\n#else\n        Knob(int page, Control, float*, float mi = 0.0f, float ma = 1.0f)\n        {\n            (void)mi; (void)ma;\n        }\n#endif\n    };\n\n} // namespace korgi\n"
  },
  {
    "path": "demo/lerp_keyframes_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef LERP_KEYFRAMES_PARAMS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define LERP_KEYFRAMES_PARAMS_H\n\nstruct LerpKeyFramesParams\n{\n    uint32_t numVertices;\n    float animTime;\n};\n\n#endif // LERP_KEYFRAMES_PARAMS_H"
  },
  {
    "path": "demo/lighting_cb.h",
    "content": "/*\n * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef LIGHTING_CB_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define LIGHTING_CB_H\n\n#include <donut/shaders/light_cb.h>\n#include <donut/shaders/view_cb.h>\n\nstruct LightingConstants\n{\n    float4 ambientColor;\n\n    LightConstants light;\n    PlanarViewConstants view;\n};\n\n#endif // LIGHTING_CB_H"
  },
  {
    "path": "demo/maya_logger.cpp",
    "content": "\n#include \"./maya_logger.h\"\n\n#include <array>\n\nconstexpr float3 const blue = { 0.f, 0.f, 1.f };\nconstexpr float3 const green = { 0.f, 1.f, 0.f };\nconstexpr float3 const red = { 1.f, 0.f, 0.f };\n\ntemplate <typename VEC3> constexpr char const* float3Format()\n{\n    static std::array<char const*, 2> const _formats = { \"%f %f %f   \", \"%lf %lf %lf   \", };\n    if constexpr (std::same_as<VEC3, float3>)\n        return _formats[0];\n    else if constexpr (std::same_as<VEC3, double3>)\n        return _formats[1];\n    else\n    {\n        return \"unknown type\";\n        assert(0);\n    }\n}\n\ntemplate <int ncols = 5> inline void newLine(FILE* m_fp, uint32_t i)\n{\n    if (i > 0 && (((i + 1) % ncols) == 0))\n        std::fprintf(m_fp, \"\\n\\t\");\n}\n\ntemplate <typename VEC3> static void fillVectorAttr(FILE* m_fp,\n    char const* attrName, VEC3 const& value, uint32_t nvalues)\n{\n\n    std::fprintf(m_fp, \"setAttr \\\"%s\\\" -type \\\"vectorArray\\\" %d \\n\\t\", attrName, nvalues);\n    for (uint32_t i = 0; i < nvalues; ++i)\n    {\n        std::fprintf(m_fp, float3Format<VEC3>(), value[0], value[1], value[2]);\n        newLine(m_fp, i);\n    }\n    std::fprintf(m_fp, \";\\n\\n\");\n}\n\ntemplate <typename VEC3> static void setVectorAttr(FILE* m_fp,\n    char const* attrName, std::vector<VEC3> const& values)\n{\n\n    uint32_t nvalues = (uint32_t)values.size();\n    std::fprintf(m_fp, \"setAttr \\\"%s\\\" -type \\\"vectorArray\\\" %d \\n\\t\", attrName, nvalues);\n    for (uint32_t i = 0; i < nvalues; ++i)\n    {\n        std::fprintf(m_fp, float3Format<VEC3>(), values[i][0], values[i][1], values[i][2]);\n        newLine(m_fp, i);\n    }\n    std::fprintf(m_fp, \";\\n\\n\");\n}\n\n//\n//\n//\n\nvoid writeHeader(FILE* m_fp, uint32_t mayaVersion = 2023)\n{\n\n    std::fprintf(m_fp, \"//Maya ASCII %d scene\", mayaVersion);\n    std::fprintf(m_fp, \"requires maya \\\"2023\\\";\\n\");\n    std::fprintf(m_fp, \"requires -nodeType \\\"nvCapsuleNode\\\" \\\"nvidiaCapsule\\\" \\\"1.0.0\\\";\\n\");\n    std::fprintf(m_fp, \"currentUnit -l centimeter -a degree -t film;\\n\");\n}\n\nvoid writeFooter(FILE* m_fp, char const* m_filepath)\n{\n    std::fprintf(m_fp, \"// End of %s\", m_filepath ? m_filepath : \"scene file\");\n}\n\nstatic void createNode(FILE* m_fp, char const* type, char const* name, char const* parent = nullptr)\n{\n    std::fprintf(m_fp, \"createNode %s -n \\\"%s\\\"\", type, name);\n    if (parent)\n        std::fprintf(m_fp, \" -p \\\"%s\\\"\", parent);\n    std::fprintf(m_fp, \";\\n\");\n}\n\nstatic void createTransformNode(FILE* m_fp, char const* name, char const* parent = nullptr)\n{\n    createNode(m_fp, \"transform\", name, parent);\n}\n\nstatic void createParticleNode(FILE* m_fp, char const* name, char const* parent, bool streaks = false)\n{\n    createNode(m_fp, \"particle\", name, parent);\n\n    if (streaks)\n    {\n        std::fprintf(m_fp, \"setAttr \\\".particleRenderType\\\" 6;\\n\");\n\n        std::fprintf(m_fp, \"addAttr -is true -ci true \"\n            \"-sn \\\"lineWidth\\\" -ln \\\"lineWidth\\\" -dv 1 -min 1 -max 20 -at \\\"long\\\";\\n\");\n        std::fprintf(m_fp, \"setAttr -k on \\\".lineWidth\\\" 2;\\n\");\n\n        std::fprintf(m_fp, \"addAttr -is true -ci true \"\n            \"-sn \\\"tailFade\\\" -ln \\\"tailFade\\\" -min -1 -max 1 -at \\\"float\\\";\\n\");\n        std::fprintf(m_fp, \"setAttr \\\".tailFade\\\" 1;\\n\");\n\n        std::fprintf(m_fp, \"addAttr -is true -ci true \"\n            \"-sn \\\"tailSize\\\" -ln \\\"tailSize\\\" -dv 1 -min -100 -max 100 -at \\\"float\\\";\\n\");\n        std::fprintf(m_fp, \"setAttr \\\".tailSize\\\" 1;\\n\");\n    }\n}\n\nstatic void setParticleIDs(FILE* m_fp, uint32_t nparticles)\n{\n    std::fprintf(m_fp, \"setAttr \\\".id0\\\" -type \\\"doubleArray\\\" %d \\n\\t\", nparticles);\n    for (uint32_t i = 0; i < nparticles; ++i)\n    {\n        std::fprintf(m_fp, \"%d \", i);\n        newLine<30>(m_fp, i);\n    }\n    std::fprintf(m_fp, \";\\n\");\n    std::fprintf(m_fp, \"setAttr \\\".nid0\\\" %d;\\n\\n\", nparticles);\n}\n\nstatic void addParticleColorAttr(FILE* m_fp,\n    char const* shapeName, char const* shapePath, float3 const& value, uint32_t nvalues)\n{\n\n    std::fprintf(m_fp, \"addAttr -s false -ci true -sn \\\"rgbPP\\\" -ln \\\"rgbPP\\\" -dt \\\"vectorArray\\\";\\n\");\n    std::fprintf(m_fp, \"addAttr -ci true -h true -sn \\\"rgbPP0\\\" -ln \\\"rgbPP0\\\" -dt \\\"vectorArray\\\";\\n\");\n\n    fillVectorAttr(m_fp, \".rgbPP0\", value, nvalues);\n\n    std::fprintf(m_fp, \"connectAttr \\\"%s|%s.xo[0]\\\" \\\"%s|%s.rgbPP\\\";\\n\\n\",\n        shapePath, shapeName, shapePath, shapeName);\n}\n\nstatic void addParticleColorAttr(FILE* m_fp,\n    char const* shapeName, char const* shapePath, std::vector<float3> const& values)\n{\n    std::fprintf(m_fp, \"addAttr -s false -ci true -sn \\\"rgbPP\\\" -ln \\\"rgbPP\\\" -dt \\\"vectorArray\\\";\\n\");\n    std::fprintf(m_fp, \"addAttr -ci true -h true -sn \\\"rgbPP0\\\" -ln \\\"rgbPP0\\\" -dt \\\"vectorArray\\\";\\n\");\n\n    setVectorAttr(m_fp, \".rgbPP0\", values);\n\n    std::fprintf(m_fp, \"connectAttr \\\"%s|%s.xo[0]\\\" \\\"%s|%s.rgbPP\\\";\\n\\n\",\n        shapePath, shapeName, shapePath, shapeName);\n\n}\n\nstatic void addParticlePositionAttr(FILE* m_fp, std::vector<float3> const& positions)\n{\n    setVectorAttr(m_fp, \".pos0\", positions);\n}\n\n//\n//\n//\n\nstd::unique_ptr<MayaLogger> MayaLogger::Create(char const* m_filepath)\n{\n\n    if (FILE* m_fp = fopen(m_filepath, \"w\"))\n    {\n        writeHeader(m_fp);\n        auto logger = std::make_unique<MayaLogger>();\n        logger->m_filepath = std::string(m_filepath);\n        logger->m_fp = m_fp;\n        return logger;\n    }\n    return nullptr;\n}\n\nMayaLogger::~MayaLogger()\n{\n    writeFooter(m_fp, m_filepath.c_str());\n    fclose(m_fp);\n}\n\n\nvoid MayaLogger::CreateParticles(ParticleDescriptor const& desc)\n{\n\n    if (desc.positions.empty())\n        return;\n\n    uint32_t nparticles = (uint32_t)desc.positions.size();\n\n    std::string parent = desc.nodePath;\n\n    createTransformNode(m_fp, desc.nodeName.c_str(), parent.c_str());\n    std::string shapePath = parent + \"|\" + desc.nodeName;\n\n    std::string shapeName = desc.nodeName + \"_Shape\";\n    createParticleNode(m_fp, shapeName.c_str(), shapePath.c_str());\n\n    setParticleIDs(m_fp, nparticles);\n\n    addParticlePositionAttr(m_fp, desc.positions);\n\n    if (!desc.colors.empty())\n        addParticleColorAttr(m_fp, shapeName.c_str(), shapePath.c_str(), desc.colors);\n}\n"
  },
  {
    "path": "demo/maya_logger.h",
    "content": "\n#pragma once\n\n#include <cstdint>\n#include <cstdio>\n#include <memory>\n#include <string>\n#include <vector>\n#include <cassert>\n\n#include <donut/core/math/math.h>\n\nusing namespace donut::math;\n\nclass MayaLogger\n{\n\npublic:\n    static std::unique_ptr<MayaLogger> Create(char const* m_filepath);\n\n    ~MayaLogger();\n\n    struct Descriptor\n    {\n        std::string nodeName;\n        std::string nodePath;\n    };\n\n    // particles\n    struct ParticleDescriptor : Descriptor\n    {\n\n        uint32_t renderType = 3;\n\n        std::vector<float3> positions;\n        std::vector<float3> velocities;\n        std::vector<float3> colors;\n\n        uint32_t pointSize = 2;\n    };\n    void CreateParticles(ParticleDescriptor const& desc);\n\nprivate:\n\n    std::string m_filepath;\n    FILE* m_fp = nullptr;\n};\n"
  },
  {
    "path": "demo/motion_vectors_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IFclust ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#ifdef __cplusplus\n#include <donut/core/math/math.h>\nusing namespace donut::math;\n#endif\n\nstatic const uint32_t kMotionVectorsNumThreadsX = 8;\nstatic const uint32_t kMotionVectorsNumThreadsY = 8;\n"
  },
  {
    "path": "demo/ray_payload.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef RAY_PAYLOAD_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RAY_PAYLOAD_H\n\nstruct RayPayload\n{\n    uint instanceID;\n    uint primitiveIndex;\n    uint geometryIndex;\n    float2 barycentrics;\n\n    uint pathWeight; // RGBe9995 \n    uint pathContribution; // RGBe9995\n    uint bounce;\n    uint multipurposeField; // can be re-used as the shuffled subpixel index, ray direction (PT)\n    float3 rayOrigin; // for path tracing\n    float pdf;\n    uint seed;\n    float hitT;\n};\n\nstruct TestPayload\n{\n    int missed;\n    uint instanceID;\n    uint primitiveIndex;\n    uint geometryIndex;\n    float2 barycentrics;\n\n    float3 rayDir;\n\n    float3 color;\n};\n\nstruct ShadowRayPayload\n{\n    int missed;\n};\n\n#endif // RAY_PAYLOAD_H"
  },
  {
    "path": "demo/render_params.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef RENDER_PARAMS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RENDER_PARAMS_H\n\n#include \"rtxmg_demo.h\"\n\n#ifdef __cplusplus\n#include <donut/core/math/math.h>\nusing namespace donut::math;\n#endif\n\n#define ENABLE_DUMP_FLOAT 0\n\n#define RTXMG_NVAPI_SHADER_EXT_SLOT 1000\n\nstatic const uint32_t kInvalidBindlessIndex = ~0u;\n\n// Camera struct for constant bfuffer\nstruct CameraConstants\n{\n    float4x4 view;\n    float4x4 viewInv;\n    float4x4 proj;\n    float4x4 projInv;\n    float2   dims;\n    float2   dimsInv;\n\n#ifndef __cplusplus\n    float3 unprojectPixelToWorld_lineardepth(float2 pixel, float z)\n    {\n        float4 ndcPos = float4(\n            pixel.x * dimsInv.x * 2.f - 1.f,\n            (1.0f - pixel.y * dimsInv.y) * 2.f - 1.f,\n            0.f, 1.f);\n\n        float4 clipPos = ndcPos * z;\n        float4 viewPos = mul(projInv, clipPos);\n        float4 worldPos = mul(viewInv, float4(viewPos.xyz, 1.0f));\n\n        return worldPos.xyz;\n    }\n\n    float3 unprojectPixelToWorld_hwdepth(float2 pixel, float zNDC)\n    {\n        // zNDC --> zCam\n        float  A = proj[2][2]; //[10];\n        float  B = proj[2][3]; //[11];\n        float  C = proj[3][2]; //[14];\n        float  zLinear = -B / (C * zNDC - A);\n\n        return unprojectPixelToWorld_lineardepth(pixel, zLinear);\n    }\n\n    float3 unprojectPixelToWorldDirection(float2 pixel)\n    {\n        float4 ndcPos = float4(\n            pixel.x * dimsInv.x * 2.f - 1.f,\n            (1.0f - pixel.y * dimsInv.y) * 2.f - 1.f,\n            0.f,\n            1.f);\n\n        // Position on near plane\n        float4 clipPos = ndcPos;\n        float4 viewPos = mul(projInv, clipPos);\n        float4 worldDir = mul(viewInv, float4(viewPos.xyz, 0.0f));\n\n        // Unnormalized world direction\n        return worldDir.xyz;\n    }\n\n    float2 projectWorldToPixel(float3 p)\n    {\n        const float4 viewPos = mul(view, float4(p, 1.0f));\n        const float4 clipPos = mul(proj, viewPos);\n        const float4 ndcPos = clipPos / clipPos.w;\n        float2 screenPos = 0.5f * (ndcPos.xy + 1.0f);\n        screenPos.y = 1.0f - screenPos.y;\n        const float2 pixel = screenPos * dims;\n\n        return pixel;\n    }\n\n    float3 projectWorldToClip(float3 p)\n    {\n        const float4 viewPos = mul(view, float4(p, 1.0f));\n        const float4 clipPos = mul(proj, viewPos);\n        const float4 ndcPos = clipPos / clipPos.w;\n        return ndcPos.xyz;\n    }\n\n    float2 projectWorldDirectionToPixel(float3 v)\n    {\n        const float4 viewDir = mul(view, float4(v, 0.0f));\n        const float4 clipDir = mul(proj, viewDir);\n        const float4 ndcPos = clipDir / clipDir.w;\n        float2 screenPos = 0.5f * (ndcPos.xy + 1.f);\n        screenPos.y = 1.0f - screenPos.y;\n        const float2 pixel = screenPos * dims;\n\n        return pixel;\n    }\n#endif\n};\n\n#ifdef __cplusplus\nstatic_assert((sizeof(CameraConstants) % 16) == 0);\n#endif\n\nstruct RenderParams\n{\n    ColorMode colorMode;\n    ShadingMode shadingMode;\n    uint32_t spp;\n    uint32_t subFrameIndex;\n\n    int enableWireframe;\n    float wireframeThickness;\n    float fireflyMaxIntensity;\n    float roughnessOverride;\n\n    uint32_t isolationLevel;\n    uint32_t clusterPattern;\n    float globalDisplacementScale;\n    int debugSurfaceIndex;\n\n    float3 missColor;\n    uint32_t ptMaxBounces;\n\n    float3 eye;\n    float zFar;\n\n    float3 U;\n    int enableTimeView;\n\n    float3 V;\n    float pad1;\n\n    float3 W;\n    float pad2;\n\n    float2 jitter;\n    int2 debugPixel;\n\n    CameraConstants camera;\n    CameraConstants prevCamera;\n\n    // for wireframe thickness\n    float4x4 viewProjectionMatrix;\n\n    int hasEnvironmentMap;\n    float envmapIntensity;\n    int enableEnvmapHeatmap;\n    DenoiserMode denoiserMode;\n\n    float4x4 envmapRotation;\n    float4x4 envmapRotationInv;\n};\n\nstruct SubdInstance\n{\n    // Bindless buffer indices\n    uint32_t plansBindlessIndex;\n    uint32_t stencilMatrixBindlessIndex;\n    uint32_t subpatchTreesBindlessIndex;\n    uint32_t patchPointIndicesBindlessIndex;\n\n    uint32_t vertexSurfaceDescriptorBindlessIndex;\n    uint32_t vertexControlPointIndicesBindlessIndex;\n    uint32_t positionsBindlessIndex;\n    uint32_t positionsPrevBindlessIndex;\n\n    uint32_t surfaceToGeometryIndexBindlessIndex;\n    uint32_t topologyQualityBindlessIndex;\n    \n    float3x4 prevLocalToWorld;\n    float3x4 worldToLocal;\n\n#ifdef __cplusplus\n    SubdInstance()\n        : plansBindlessIndex(kInvalidBindlessIndex)\n        , stencilMatrixBindlessIndex(kInvalidBindlessIndex)\n        , subpatchTreesBindlessIndex(kInvalidBindlessIndex)\n        , patchPointIndicesBindlessIndex(kInvalidBindlessIndex)\n        , vertexSurfaceDescriptorBindlessIndex(kInvalidBindlessIndex)\n        , vertexControlPointIndicesBindlessIndex(kInvalidBindlessIndex)\n        , positionsBindlessIndex(kInvalidBindlessIndex)\n        , positionsPrevBindlessIndex(kInvalidBindlessIndex)\n        , surfaceToGeometryIndexBindlessIndex(kInvalidBindlessIndex)\n        , topologyQualityBindlessIndex(kInvalidBindlessIndex)\n    {}\n\n    bool operator==(const SubdInstance& other) const\n    {\n        return memcmp(this, &other, sizeof(*this)) == 0;\n    }\n#endif\n};\n\n#endif // RENDER_PARAMS_H\n"
  },
  {
    "path": "demo/render_targets.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n\n#include \"render_targets.h\"\n\nRenderTargets::RenderTargets(nvrhi::IDevice* device, uint32_t width, uint32_t height)\n{\n    auto CreateCommonTexture = [device, width, height](nvrhi::Format format, const char* debugName, nvrhi::TextureHandle& texture) {\n        nvrhi::TextureDesc desc;\n        desc.width = width;\n        desc.height = height;\n        desc.format = format;\n        desc.debugName = debugName;\n        desc.isVirtual = false;\n        desc.initialState = nvrhi::ResourceStates::UnorderedAccess;\n        desc.isRenderTarget = false;\n        desc.isUAV = true;\n        desc.dimension = nvrhi::TextureDimension::Texture2D;\n        desc.keepInitialState = true;\n        desc.isTypeless = false;\n\n        texture = device->createTexture(desc);\n        };\n\n    CreateCommonTexture(nvrhi::Format::R32_FLOAT, \"denoiserViewspaceZ\", denoiserViewSpaceZ);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserMotionVectors\", denoiserMotionVectors);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserNormalRoughness\", denoiserNormalRoughness);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserEmissive\", denoiserEmissive);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserDiffuseAbedo\", denoiserDiffuseAlbedo);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserSpecularAbedo\", denoiserSpecularAlbedo);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserInDiffRadianceHitDist\", denoiserInDiffRadianceHitDist);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserInSpecRadianceHitDist\", denoiserInSpecRadianceHitDist);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserOutDiffRadianceHitDist\", denoiserOutDiffRadianceHitDist);\n    CreateCommonTexture(nvrhi::Format::RGBA16_FLOAT, \"denoiserOutSpecRadianceHitDist\", denoiserOutSpecRadianceHitDist);\n    CreateCommonTexture(nvrhi::Format::RGBA8_UNORM, \"denoiserValidation\", denoiserValidation);\n}"
  },
  {
    "path": "demo/render_targets.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <nvrhi/nvrhi.h>\n\nclass RenderTargets\n{\npublic:\n    RenderTargets(nvrhi::IDevice* device, uint32_t width, uint32_t height);\n\n    nvrhi::TextureHandle denoiserViewSpaceZ;\n    nvrhi::TextureHandle denoiserNormalRoughness;\n    nvrhi::TextureHandle denoiserMotionVectors;\n    nvrhi::TextureHandle denoiserEmissive;\n    nvrhi::TextureHandle denoiserDiffuseAlbedo;\n    nvrhi::TextureHandle denoiserSpecularAlbedo;\n\n    nvrhi::TextureHandle denoiserInDiffRadianceHitDist;\n    nvrhi::TextureHandle denoiserInSpecRadianceHitDist;\n\n    nvrhi::TextureHandle denoiserOutDiffRadianceHitDist;\n    nvrhi::TextureHandle denoiserOutSpecRadianceHitDist;\n\n    nvrhi::TextureHandle denoiserValidation;\n};"
  },
  {
    "path": "demo/rtxmg_demo.cpp",
    "content": "/*\n * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n\n#include <donut/app/ApplicationBase.h>\n#include <donut/app/DeviceManager.h>\n#include <donut/core/log.h>\n#include <nvrhi/utils.h>\n\nextern \"C\" {\n\n#ifdef DONUT_D3D_AGILITY_SDK_ENABLED\n    _declspec(dllexport) extern const UINT D3D12SDKVersion = DONUT_D3D_AGILITY_SDK_VERSION;\n    _declspec(dllexport) extern const char* D3D12SDKPath = \".\\\\D3D12\\\\\";\n#endif\n\n    _declspec(dllexport) DWORD NvOptimusEnablement = 0x0000001;\n}\n\n#include \"rtxmg_demo_app.h\"\n#include \"gui.h\"\n\nusing namespace donut;\n\nint main(int argc, const char** argv)\n{\n    donut::log::ConsoleApplicationMode();\n    donut::log::EnableOutputToMessageBox(true);\n\n    nvrhi::GraphicsAPI api = app::GetGraphicsAPIFromCommandLine(argc, argv);\n\n#if !DONUT_WITH_DX12\n    if (api == nvrhi::GraphicsAPI::D3D12)\n    {\n        donut::log::fatal(\"This demo supports D3D12 but needs to be compiled with DONUT_WITH_DX12 enabled in cmake\");\n    }\n#endif\n#if !DONUT_WITH_VULKAN\n    if (api == nvrhi::GraphicsAPI::VULKAN)\n    {\n        donut::log::fatal(\"This demo supports Vulkan but needs to be compiled with DONUT_WITH_VULKAN enabled in cmake\");\n    }\n#endif\n    if (api == nvrhi::GraphicsAPI::D3D11)\n    {\n        donut::log::fatal(\"This demo only supports D3D12 or Vulkan\");\n    }\n\n    app::DeviceManager* deviceManager = app::DeviceManager::Create(api);\n\n    std::string title = \"RTX Mega Geometry \" RTXMG_VERSION + std::string(api == nvrhi::GraphicsAPI::D3D12 ? \" (D3D12)\" : \" (VULKAN)\");\n\n    try \n    {\n        {\n            RTXMGDemoApp app(deviceManager, title, argc, argv);\n            UserInterface gui(app);\n            if (app.Init() && gui.CustomInit(app.GetRenderer().GetShaderFactory()))\n            {\n                deviceManager->AddRenderPassToBack(&app);\n                deviceManager->AddRenderPassToBack(&gui);\n                deviceManager->RunMessageLoop();\n                deviceManager->RemoveRenderPass(&gui);\n                deviceManager->RemoveRenderPass(&app);\n            }\n\n            Profiler::Terminate();\n        }\n        \n        deviceManager->Shutdown();\n        delete deviceManager;\n    }\n    catch (const std::exception& e)\n    {\n        donut::log::fatal(e.what());\n    }\n    return 0;\n}\n\n#ifdef WIN32\nint WinMain(_In_ HINSTANCE hInstance,\n    _In_opt_ HINSTANCE hPrevInstance,\n    _In_ LPSTR lpCmdLine,\n    _In_ int nCmdShow)\n{\n    return main(__argc, (const char**)__argv);\n}\n#endif"
  },
  {
    "path": "demo/rtxmg_demo.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n#pragma once\n\nenum class ShadingMode { PRIMARY_RAYS = 0, AO, PT, SHADING_MODE_COUNT };\n\nenum class ColorMode\n{\n    BASE_COLOR = 0,\n    COLOR_BY_NORMAL,\n    // Shading modes that only work for true cluster builds start here\n    COLOR_BY_TEXCOORD,\n    COLOR_BY_MATERIAL,\n    COLOR_BY_GEOMETRY_INDEX,\n    COLOR_BY_SURFACE_INDEX,\n    COLOR_BY_CLUSTER_ID,\n    COLOR_BY_MICROTRI_ID,\n    COLOR_BY_CLUSTER_UV,\n    COLOR_BY_MICROTRI_AREA,\n    COLOR_BY_TOPOLOGY,\n    COLOR_MODE_COUNT\n};\n\nenum class TonemapOperator\n{\n    Linear = 0,\n    Srgb,\n    Aces,  // Academy Color Encoding System\n    Hable, // Uncharted 2\n    Count\n};\n\nenum class BlitDecodeMode\n{\n    None,\n    SingleChannel,\n    Depth,\n    Normals,\n    MotionVectors,\n    InstanceId,\n    SurfaceIndex,\n    SurfaceUv,\n    Texcoord\n};\n\nenum class MvecDisplacement\n{\n    FromSubdEval,\n    FromMaterial,\n    Count\n};\n\nenum class DenoiserMode\n{\n    None,\n    DlssSr,\n    DlssRr\n};\n\n#ifdef __cplusplus\n#include <array>\nconstexpr auto kColorModeNames = std::to_array<const char *>(\n{\n    \"Base Color\",\n    \"Surface Normal\",\n    \"Tex Coord\",\n    \"Material\",\n    \"Geometry Index\",\n    \"Surface Index\",\n    \"Cluster ID\",\n    \"MicroTri ID\",\n    \"Cluster UV\",\n    \"MicroTri Area\",\n    \"Topology Quality\"\n});\nstatic_assert(kColorModeNames.size() == size_t(ColorMode::COLOR_MODE_COUNT));\n\nconstexpr auto kToneMapOperatorNames = std::to_array<const char*>(\n{\n    \"Linear\",\n    \"sRGB\",\n    \"ACES\",\n    \"Hable\"\n});\nstatic_assert(kToneMapOperatorNames.size() == size_t(TonemapOperator::Count));\n\nconstexpr auto kShadingModeNames = std::to_array<const char*>(\n{\n    \"Primary Rays\",\n    \"Ambient Occlusion\",\n    \"Path Tracing\"\n});\nstatic_assert(kShadingModeNames.size() == size_t(ShadingMode::SHADING_MODE_COUNT));\n\nconstexpr auto kMvecDisplacementNames = std::to_array<const char*>(\n{\n    \"From Subd Eval\",\n    \"From Material\"\n});\nstatic_assert(kMvecDisplacementNames.size() == size_t(MvecDisplacement::Count));\n#endif"
  },
  {
    "path": "demo/rtxmg_demo_app.cpp",
    "content": "\n/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"rtxmg_demo_app.h\"\n#include \"maya_logger.h\"\n#include \"korgi.h\"\n\n#include <donut/app/ApplicationBase.h>\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/engine/TextureCache.h>\n\n#include <filesystem>\n#include <iostream>\n#include <fstream>\n#include <utility>\n\n#include \"rtxmg/profiler/statistics.h\"\n#include \"rtxmg/scene/scene.h\"\n#include \"rtxmg/subdivision/subdivision_surface.h\"\n#include \"rtxmg/cluster_builder/tessellator_config.h\"\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/utils/debug.h\"\n\n#include \"lerp_keyframes_params.h\"\n\n#define GLFW_INCLUDE_NONE // Do not include any OpenGL headers\n#include <GLFW/glfw3.h>\n\n#if DONUT_WITH_VULKAN\n#include <vulkan/vulkan.hpp>\n#endif\n\nusing namespace donut;\n\nnamespace fs = std::filesystem;\n\n// Sample application, so use dummy app id\nstatic constexpr int kStreamlineAppId = 1;\n\n// search \"up-ward\" from the start path for a given directory name\nstatic fs::path findDir(fs::path const& startPath, fs::path const& dirname,\n    int maxDepth)\n{\n    std::filesystem::path searchPath = \"\";\n\n    for (int depth = 0; depth < maxDepth; depth++)\n    {\n        fs::path currentPath = startPath / searchPath / dirname;\n\n        if (fs::is_directory(currentPath))\n            return currentPath.lexically_normal();\n\n        searchPath = \"..\" / searchPath;\n    }\n    return {};\n}\n\nstatic fs::path findMediaFolder(fs::path const& startdir, char const* dirname,\n    int maxdepth = 5)\n{\n    fs::path mediapath;\n    try\n    {\n        fs::path start = fs::canonical(startdir).parent_path();\n        mediapath = findDir(start, dirname, maxdepth);\n    }\n    catch (std::exception const& e)\n    {\n        fprintf(stderr, \"%s\\n\", e.what());\n    }\n    return mediapath;\n}\n\nvoid RTXMGDemoApp::MessageCallback::message(nvrhi::MessageSeverity severity, const char* messageText)\n{\n    donut::log::Severity donutSeverity = donut::log::Severity::Info;\n    switch (severity)\n    {\n    case nvrhi::MessageSeverity::Info:\n        donutSeverity = donut::log::Severity::Info;\n        break;\n    case nvrhi::MessageSeverity::Warning:\n        donutSeverity = donut::log::Severity::Warning;\n        break;\n    case nvrhi::MessageSeverity::Error:\n        donutSeverity = donut::log::Severity::Error;\n        break;\n    case nvrhi::MessageSeverity::Fatal:\n        donutSeverity = donut::log::Severity::Fatal;\n        break;\n    }\n\n    // Framecount\n    if (m_deviceManager)\n    {\n        donut::log::message(donutSeverity, \"[%u] %s\", m_deviceManager->GetFrameIndex(), messageText);\n    }\n}\n\nvoid RTXMGDemoApp::UpdateParams()\n{\n    m_denoiserMode = m_args.enableDenoiser ? DenoiserMode::DlssRr : DenoiserMode::None;\n\n    m_renderParams.colorMode = m_args.colorMode;\n    m_renderParams.shadingMode = m_args.shadingMode;\n    m_renderParams.spp = m_args.spp;\n    m_renderParams.enableWireframe = m_args.enableWireframe;\n    m_renderParams.wireframeThickness = m_args.wireframeThickness;\n    m_renderParams.fireflyMaxIntensity = m_args.firefliesClamp;\n    m_renderParams.roughnessOverride = m_args.roughnessOverride;\n    m_renderParams.missColor = float3(m_args.missColor);\n    m_renderParams.ptMaxBounces = m_args.ptMaxBounces;\n    m_renderParams.denoiserMode = m_denoiserMode;\n    m_renderParams.enableTimeView = m_args.enableTimeView;\n\n    m_renderParams.isolationLevel = m_args.globalIsolationLevel;\n    m_renderParams.clusterPattern = uint32_t(m_args.clusterPattern);\n    m_renderParams.globalDisplacementScale = m_args.dispScale;\n\n    m_renderParams.hasEnvironmentMap = 0;\n    m_renderParams.enableEnvmapHeatmap = 0;\n    m_renderParams.debugPixel = int2(0, 0);\n    m_renderParams.debugSurfaceIndex = m_debugSurfaceClusterLaneIndex[0]; // Initialize from GUI variable\n\n    if (m_renderer)\n    {\n        GetRenderer().SetEnvMapAzimuth(0);\n        GetRenderer().SetEnvMapElevation(0);\n        GetRenderer().SetEnvMapIntensity(1);\n\n        for (size_t i = 0; i < m_args.textures.size(); ++i)\n        {\n            if (!m_args.textures[i].empty())\n            {\n                if (TextureType(i) == TextureType::ENVMAP)\n                    SetEnvmapTex(m_args.textures[i]);\n            }\n        }\n        GetRenderer().ResetSubframes();\n        if (GetRenderer().GetEnvMap())\n            m_renderParams.hasEnvironmentMap = 1;\n\n        GetRenderer().SetShadingMode(m_args.shadingMode);\n        GetRenderer().SetColorMode(m_args.colorMode);\n        GetRenderer().SetTonemapOperator(m_args.tonemapOperator);\n        GetRenderer().SetExposure(m_args.exposure);\n        GetRenderer().SetDebugSurfaceIndex(m_debugSurfaceClusterLaneIndex[0]); // Initialize debug surface highlighting\n    }\n}\n\nbool RTXMGDemoApp::SetEnvmapTex(const std::string& filePath)\n{\n    if (m_scene)\n    {\n        // current environment map might be in use\n        GetDevice()->waitForIdle();\n        m_commandList->open();\n        GetRenderer().SetEnvMap(filePath, m_commandList);\n        GetRenderer().SetEnvMapAzimuth(m_args.envmapAzimuth);\n        GetRenderer().SetEnvMapElevation(m_args.envmapElevation);\n        GetRenderer().SetEnvMapIntensity(m_args.envmapIntensity);\n        m_commandList->close();\n        GetDevice()->executeCommandList(m_commandList);\n        m_renderParams.hasEnvironmentMap = 1;\n        return true;\n    }\n    return false;\n}\n\nRTXMGDemoApp::RTXMGDemoApp(app::DeviceManager* deviceManager,\n    std::string &windowTitle,\n    int argc, const char** argv)\n    : app::ApplicationBase(deviceManager)\n    , m_messageCallback(deviceManager)\n{\n    m_argc = argc;\n    m_argv = argv;\n\n    m_binaryPath = app::GetDirectoryWithExecutable().lexically_normal();\n    m_mediaPath = findMediaFolder(m_binaryPath, \"assets\");\n\n    m_args.Parse(m_argc, m_argv);\n    m_displaySize = int2(m_args.width, m_args.height);\n\n    UpdateParams();\n\n    app::DeviceCreationParameters deviceParams;\n    deviceParams.enableHeapDirectlyIndexed = true; // Needed for bindless look up in motion_vectors.hlsl\n    deviceParams.enableRayTracingExtensions = true;\n    deviceParams.backBufferWidth = m_displaySize.x;\n    deviceParams.backBufferHeight = m_displaySize.y;\n    deviceParams.enablePerMonitorDPI = true;\n    deviceParams.allowModeSwitch = false; // Make Alt+Enter not switch monitor resolutions\n    deviceParams.startMaximized = m_args.startMaximized;\n    deviceParams.swapChainFormat = nvrhi::Format::RGBA8_UNORM;\n    deviceParams.messageCallback = &m_messageCallback;\n    if (m_args.debug)\n    {\n        deviceParams.enableDebugRuntime = true;\n        deviceParams.enableNvrhiValidationLayer = true;\n    }\n\n    if (m_args.gpuValidation)\n    {\n        deviceParams.enableGPUValidation = true;\n    }\n\n    if (m_args.aftermath)\n    {\n#if DONUT_WITH_AFTERMATH\n        deviceParams.enableAftermath = true;\n#endif\n        deviceParams.logBufferLifetime = true;\n    }\n\n#if DONUT_WITH_STREAMLINE\n    if (m_args.enableStreamlineLog)\n    {\n        deviceParams.enableStreamlineLog = true;\n    }\n    deviceParams.streamlineAppId = kStreamlineAppId;\n#endif\n\n    if (!deviceManager->CreateWindowDeviceAndSwapChain(deviceParams,\n        windowTitle.c_str()))\n    {\n        log::fatal(\n            \"Cannot initialize a graphics device with the requested parameters\");\n    }\n\n    if (!deviceManager->GetDevice()->queryFeatureSupport(\n        nvrhi::Feature::RayTracingPipeline))\n    {\n        log::fatal(\"The graphics device does not support Ray Tracing Pipelines\");\n    }\n\n    if (!deviceManager->GetDevice()->queryFeatureSupport(\n        nvrhi::Feature::RayTracingClusters))\n    {\n        log::fatal(\"The graphics device does not support Clusters\");\n    }\n\n    if (!deviceManager->GetDevice()->queryFeatureSupport(\n        nvrhi::Feature::HeapDirectlyIndexed))\n    {\n        log::fatal(\"The graphics device does not support directly indexing heaps (ResourceDescriptorHeap) (SamplerDescriptorHeap)\");\n    }\n\n    korgi::Init();\n}\n\nRTXMGDemoApp::~RTXMGDemoApp()\n{\n    korgi::Shutdown();\n}\n\nRTXMGRenderer& RTXMGDemoApp::GetRenderer()\n{\n    if (!m_renderer)\n    {\n        RTXMGRenderer::Options rendererOptions{\n            .params = m_renderParams,\n            .device = GetDevice()\n        };\n\n        m_renderer = std::make_unique<RTXMGRenderer>(rendererOptions);\n    }\n    return *m_renderer;\n}\n\nstatic constexpr size_t const kMinVram = 10;\nstatic constexpr size_t const kGigabyte = 1024 * 1024 * 1024;\n\nbool RTXMGDemoApp::Init()\n{\n    // Check to see if we have enough vram to run the demo\n\n    std::vector<app::AdapterInfo> adapters;\n    if (!GetDeviceManager()->EnumerateAdapters(adapters))\n    {\n        donut::log::fatal(\"Failed to enumerate adapters for vram check\");\n    }\n\n    app::AdapterInfo::LUID luid = {};\n    app::AdapterInfo::UUID uuid = {};\n\n    const app::AdapterInfo* pAdapter = nullptr;\n\n#if DONUT_WITH_DX12\n    if (GetDevice()->getGraphicsAPI() == nvrhi::GraphicsAPI::D3D12)\n    {\n        ID3D12Device* rawDevice = (ID3D12Device*)GetDevice()->getNativeObject(nvrhi::ObjectTypes::D3D12_Device);\n        LUID dxLuid = rawDevice->GetAdapterLuid();\n        static_assert(luid.size() == sizeof(dxLuid));\n        memcpy(luid.data(), &dxLuid, luid.size());\n\n        for (const auto& adapter : adapters)\n        {\n            if (adapter.luid == luid)\n            {\n                pAdapter = &adapter;\n                break;\n            }\n        }\n    }\n#endif\n#if DONUT_WITH_VULKAN\n    if (GetDevice()->getGraphicsAPI() == nvrhi::GraphicsAPI::VULKAN)\n    {\n        vk::PhysicalDevice rawDevice = (VkPhysicalDevice)GetDevice()->getNativeObject(nvrhi::ObjectTypes::VK_PhysicalDevice).pointer;\n        vk::PhysicalDeviceProperties2 properties2;\n        vk::PhysicalDeviceIDProperties idProperties;\n        properties2.pNext = &idProperties;\n        rawDevice.getProperties2(&properties2);\n\n        app::AdapterInfo::UUID uuid;\n        static_assert(uuid.size() == idProperties.deviceUUID.size());\n        memcpy(uuid.data(), idProperties.deviceUUID.data(), uuid.size());\n        \n        for (const auto& adapter : adapters)\n        {\n            if (adapter.uuid == uuid)\n            {\n                pAdapter = &adapter;\n                break;\n            }\n        }\n    }\n#endif\n    \n    if (!pAdapter)\n    {\n        donut::log::fatal(\"Failed to find active adapter for vram check\");\n    }\n\n    size_t vram = pAdapter->dedicatedVideoMemory;\n    if (vram < kMinVram * kGigabyte)\n    {\n        donut::log::error(\"GPU has %.2fGB of VRAM and is below the required %dGB.\\n\\n\"\n            \"Expect the following:\\n\"\n            \"1. Performance degradation or out of memory crashes.\\n\"\n            \"2. Flickering and missing surfaces after adjusting the memory budget down\", float(vram) / kGigabyte, kMinVram);\n    }\n        \n    std::filesystem::path sceneFileName;\n    if (!m_args.meshInputFile.empty())\n    {\n        sceneFileName = app::GetDirectoryWithExecutable().parent_path() / \"assets\" /\n            m_args.meshInputFile;\n\n        if (!std::filesystem::exists(sceneFileName))\n        {\n            std::filesystem::path mediaSceneFileName = GetMediaPath() / m_args.meshInputFile;\n            if (mediaSceneFileName != sceneFileName)\n            {\n                sceneFileName = mediaSceneFileName;\n\n                if (!std::filesystem::exists(sceneFileName))\n                {\n                    donut::log::error(\"Could not find mesh input %s\", sceneFileName);\n                    sceneFileName = \"\";\n                }\n            }\n            else\n            {\n                donut::log::error(\"Could not find mesh input %s\", sceneFileName.c_str());\n                sceneFileName = \"\";\n            }\n        }\n    }\n\n    RTXMGRenderer& renderer = GetRenderer();\n    m_TextureCache = renderer.GetTextureCache();\n    m_CommonPasses = renderer.GetCommonPasses();\n    m_commandList = GetDevice()->createCommandList();\n\n    m_lerpKeyFramesParamsBuffer = GetDevice()->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(LerpKeyFramesParams), \"LerpKeyFramesParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    SetAsynchronousLoadingEnabled(false);\n    HandleSceneLoad(sceneFileName.lexically_normal().generic_string(),\n        GetMediaPath().generic_string());\n\n    return true;\n}\n\nvoid RTXMGDemoApp::HandleSceneLoad(const std::string& sceneFileName,\n    const std::string& mediaPath,\n    int2 frameRange)\n{\n    auto nativeFS = std::make_shared<vfs::NativeFileSystem>();\n\n    // don't have a way to pass this through to the scene loader\n    m_loadFrameRange = frameRange;\n\n    m_args.sceneArgs() = {};\n    auto& renderer = GetRenderer();\n    renderer.ClearEnvMap();\n\n    BeginLoadingScene(nativeFS, sceneFileName);\n\n    m_sunLight = std::make_shared<engine::DirectionalLight>();\n    m_scene->GetSceneGraph()->AttachLeafNode(\n        m_scene->GetSceneGraph()->GetRootNode(), m_sunLight);\n\n    m_sunLight->angularSize = 0.53f;\n    m_sunLight->irradiance = 3.f;\n\n    m_scene->FinishedLoading(GetFrameIndex());\n\n    m_args.Parse(m_argc, m_argv); // re-parse command line args in case they override anything in the scene\n\n    UpdateParams();\n\n    m_zRenderer = std::make_unique<ZRenderer>(renderer.GetShaderFactory());\n\n    m_commandList->open();\n    {\n        nvrhi::utils::ScopedMarker marker(m_commandList, \"Scene Load\");\n        if (auto* zbuffer = renderer.GetZBuffer())\n        {\n            zbuffer->Clear(m_commandList);\n        }\n        m_scene->Refresh(m_commandList, GetFrameIndex());\n    }\n    m_commandList->close();\n    GetDevice()->executeCommandList(m_commandList);\n\n    ResetCamera();\n    m_tesselationCamera = m_camera;\n    m_accelBuilderNeedsUpdate = true;\n\n    GetRenderer().SceneFinishedLoading(m_scene);\n}\n\nvoid RTXMGDemoApp::ResetCamera()\n{\n    m_cameraReset = true;\n    if (!m_args.camString.empty())\n    {\n        m_camera.Set(m_args.camString);\n    }\n    else if (const View* view = m_scene->GetView())\n    {\n        m_camera.SetEye(view->position);\n        m_camera.SetLookat(view->lookat);\n        m_camera.SetUp(view->up);\n        m_camera.SetFovY(view->fov);\n    }\n    else\n    {\n        box3 aabb = m_scene->GetSceneGraph()->GetRootNode()->GetGlobalBoundingBox();\n        m_camera.Frame(aabb);\n    }\n\n    m_camera.SetAspectRatio(float(m_displaySize.x) / float(m_displaySize.y));\n\n    m_trackBall.SetGimbalLock(true);\n    m_trackBall.SetCamera(&m_camera);\n    m_trackBall.SetMoveSpeed(m_scene->GetAttributes().averageInstanceScale);\n    m_trackBall.SetReferenceFrame({ 1.f, 0.f, 0.f }, { 0.f, 0.f, 1.f },\n        { 0.f, 1.f, 0.f });\n}\n\nbool RTXMGDemoApp::LoadScene(std::shared_ptr<vfs::IFileSystem> fs,\n    const std::filesystem::path& sceneFileName)\n{\n    stats::evaluatorSamplers = {};\n    stats::memUsageSamplers = {};\n\n    auto shaderFactory = GetRenderer().GetShaderFactory();\n    RTXMGScene* scene =\n        new RTXMGScene(GetDevice(), GetMediaPath(),\n            m_CommonPasses, *shaderFactory, fs, m_TextureCache,\n            GetRenderer().GetDescriptorTable(), nullptr,\n            m_loadFrameRange, m_args.isoLevelSharp, m_args.isoLevelSmooth);\n\n    if (scene->Load(sceneFileName))\n    {\n        m_args.meshInputFile = scene->GetInputPath();\n        const Json::Value& settings = scene->GetSceneSettings();\n        m_args << settings;\n\n        if (std::string& envarg = m_args.textures[TextureType::ENVMAP]; envarg.empty())\n            if (const Json::Value& envmap = settings[\"envmap\"]; envmap.isString())\n                if (fs::path filepath = RTXMGScene::ResolveMediapath(envmap.asString(), m_mediaPath); !filepath.empty())\n                    envarg = filepath.lexically_normal().generic_string();\n\n\n        m_scene = std::unique_ptr<RTXMGScene>(scene);\n        m_accelBuilderNeedsUpdate = true;\n        UpdateParams();\n        return true;\n    }\n\n    log::fatal(\"Failed to load scene from file: %s\", sceneFileName.string().c_str());\n\n    return false;\n}\n\nbool RTXMGDemoApp::KeyboardUpdate(int key, int scancode, int action,\n    int mods)\n{\n    auto& renderer = GetRenderer();\n    m_trackBall.KeyboardUpdate(key, scancode, action, mods);\n\n    if (action == GLFW_PRESS)\n    {\n        switch (key)\n        {\n        case GLFW_KEY_ESCAPE:\n            glfwSetWindowShouldClose(GetDeviceManager()->GetWindow(), true);\n            break;\n        case GLFW_KEY_1:\n            NextShadingMode();\n            break;\n        case GLFW_KEY_2:\n            IncrementColorMode(1);\n            break;\n        case GLFW_KEY_3:\n            ToggleWireframe();\n            break;\n        case GLFW_KEY_4:\n            IncrementColorMode(-1);\n            break;\n        case GLFW_KEY_5:\n            NextTonemapper();\n            break;\n        case GLFW_KEY_C:\n            m_camera.Print();\n            break;\n        case GLFW_KEY_F:\n            ResetCamera();\n            break;\n        case GLFW_KEY_R:\n            if (mods & GLFW_MOD_CONTROL)\n                ReloadShaders();\n            break;\n        case GLFW_KEY_P:\n            SaveScreenshot();\n            break;\n        case GLFW_KEY_T:\n            ToggleTimeView();\n            break;\n        case GLFW_KEY_SLASH:\n            ToggleUpdateTessellationCamera();\n            break;\n        case GLFW_KEY_RIGHT:\n            IncrementMaxBounces(1);\n            break;\n        case GLFW_KEY_LEFT:\n            IncrementMaxBounces(-1);\n            break;\n        }\n    }\n    return true;\n}\n\nbool RTXMGDemoApp::MousePosUpdate(double xpos, double ypos)\n{\n    int2 pos = { static_cast<int>(xpos), static_cast<int>(ypos) };\n    int2 canvas = { m_displaySize.x, m_displaySize.y };\n    m_trackBall.MouseTrackingUpdate(pos, canvas);\n    return true;\n}\n\nbool RTXMGDemoApp::MouseButtonUpdate(int button, int action, int mods)\n{\n    if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS)\n    {\n        double mousex = 0, mousey = 0;\n        glfwGetCursorPos(GetDeviceManager()->GetWindow(), &mousex, &mousey);\n\n        float2 renderScale = float2(m_renderSize) / float2(m_displaySize);\n        float2 mousePos = float2(float(mousex), float(mousey));\n        float2 debugPixel = mousePos * renderScale;\n\n        m_renderParams.debugPixel = int2(debugPixel);\n\n        m_dumpPixelDebug = true;\n    }\n\n    m_trackBall.MouseButtonUpdate(button, action, mods);\n    return true;\n}\n\nbool RTXMGDemoApp::MouseScrollUpdate(double xoffset, double yoffset)\n{\n    if (yoffset != 0)\n    {\n        m_trackBall.MouseWheelUpdate((int)yoffset);\n    }\n    return true;\n}\n\nvoid RTXMGDemoApp::BackBufferResizing() {}\n\nvoid RTXMGDemoApp::BackBufferResized(const uint32_t width,\n    const uint32_t height,\n    const uint32_t sampleCount)\n{\n    m_cameraReset = true;\n    m_camera.SetAspectRatio(float(width) / float(height));\n    m_displaySize = int2(width, height);\n\n    // We need to cache here since we can't query the window during application quit\n    // Donut doesn't have a callback to the application for window pos, so we skip saving that\n    GLFWwindow* window = GetDeviceManager()->GetWindow();\n    m_windowState.isMaximized = glfwGetWindowAttrib(window, GLFW_MAXIMIZED) != 0;\n    m_windowState.isFullscreen = glfwGetWindowMonitor(window) != nullptr;\n    if (!m_windowState.isMaximized && !m_windowState.isFullscreen)\n    {\n        // Only save the non-maximized size\n        glfwGetWindowSize(window, &m_windowState.windowSize.x, &m_windowState.windowSize.y);\n    }\n}\n\nvoid RTXMGDemoApp::UpdateDLSSSettings()\n{\n#if DONUT_WITH_STREAMLINE\n    using StreamlineInterface = donut::app::StreamlineInterface;\n\n    StreamlineInterface& streamline = donut::app::DeviceManager::GetStreamline();\n\n    const uint32_t kViewportId = 0;\n    streamline.SetViewport(kViewportId);\n\n    DenoiserMode denoiserMode = GetRenderer().GetShowMicroTriangles() ? DenoiserMode::None : m_denoiserMode;\n    if (denoiserMode == DenoiserMode::DlssSr ||\n        denoiserMode == DenoiserMode::DlssRr)\n    {\n        bool isDlssRr = denoiserMode == DenoiserMode::DlssRr;\n\n        StreamlineInterface::DLSSOptions dlssOptions = {};\n        dlssOptions.mode = m_ui.dlssMode;\n        dlssOptions.outputWidth = m_displaySize.x;\n        dlssOptions.outputHeight = m_displaySize.y;\n        dlssOptions.colorBuffersHDR = true;\n        dlssOptions.sharpness = m_RecommendedDLSSSettings.sharpness;\n\n        dlssOptions.preset = m_ui.dlssPreset;\n        dlssOptions.useAutoExposure = false;\n\n        StreamlineInterface::DLSSRROptions dlssRROptions = {};\n        dlssRROptions.mode = m_ui.dlssMode;\n        dlssRROptions.outputWidth = m_displaySize.x;\n        dlssRROptions.outputHeight = m_displaySize.y;\n        dlssRROptions.sharpness = m_RecommendedDLSSRRSettings.sharpness;\n        dlssRROptions.preExposure = 1.0f;\n        dlssRROptions.exposureScale = 1.0f;\n        dlssRROptions.colorBuffersHDR = true;\n        dlssRROptions.normalRoughnessMode = StreamlineInterface::DLSSRRNormalRoughnessMode::eUnpacked;\n\n        dlssRROptions.preset = m_ui.dlssRRPreset;\n\n        float4x4 worldToViewRowMajor = transpose(m_camera.GetViewMatrix());\n        dlssRROptions.worldToCameraView = worldToViewRowMajor;\n        dlssRROptions.cameraViewToWorld = inverse(worldToViewRowMajor);\n\n        // Changing presets requires a restart of DLSS\n        // Current bug, should get fixed in new version of streamline past 2.7\n        if (m_dlssLastPreset != m_ui.dlssPreset)\n        {\n            streamline.CleanupDLSS(true);\n            m_dlssLastPreset = m_ui.dlssPreset;\n        }\n\n        if (isDlssRr)\n            streamline.SetDLSSRROptions(dlssRROptions);\n        else\n            streamline.SetDLSSOptions(dlssOptions);\n\n        // Check if we need to update the rendertarget size.\n        bool DLSS_resizeRequired = (m_ui.dlssMode != m_dlssLastMode) || (m_displaySize.x != m_dlssLastDisplaySize.x) || (m_displaySize.y != m_dlssLastDisplaySize.y);\n        if (DLSS_resizeRequired)\n        {\n            // Only quality, target width and height matter here\n            streamline.QueryDLSSOptimalSettings(dlssOptions, m_RecommendedDLSSSettings);\n            streamline.QueryDLSSRROptimalSettings(dlssRROptions, m_RecommendedDLSSRRSettings);\n\n            int2& optimalRenderSize = isDlssRr ? m_RecommendedDLSSRRSettings.optimalRenderSize :\n                m_RecommendedDLSSSettings.optimalRenderSize;\n\n            if (optimalRenderSize.x <= 0 || optimalRenderSize.y <= 0)\n            {\n                donut::log::warning(\"DLSS Recommended Settings returned render size %d,%d\", optimalRenderSize.x, optimalRenderSize.y);\n                denoiserMode = DenoiserMode::None;\n            }\n            else\n            {\n                m_dlssLastMode = m_ui.dlssMode;\n                m_dlssLastDisplaySize = m_displaySize;\n                m_renderSize = optimalRenderSize;\n            }\n        }\n\n        float texLodXDimension = (float)m_renderSize.x;\n\n        // Use the formula of the DLSS programming guide for the Texture LOD Bias...\n        float optimalLodBias = std::log2f(texLodXDimension / m_displaySize.x) - 1;\n        float lodBias = m_ui.dlssUseLodBiasOverride ? m_ui.dlssLodBiasOverride : optimalLodBias;\n        if (lodBias != m_lodBias)\n        {\n            m_lodBias = lodBias;\n\n            GetDevice()->waitForIdle();\n            {\n                nvrhi::SamplerDesc samplerDescPoint = m_CommonPasses->m_PointClampSampler->getDesc();\n                nvrhi::SamplerDesc samplerDescLinear = m_CommonPasses->m_LinearClampSampler->getDesc();\n                nvrhi::SamplerDesc samplerDescLinearWrap = m_CommonPasses->m_LinearWrapSampler->getDesc();\n                nvrhi::SamplerDesc samplerDescAniso = m_CommonPasses->m_AnisotropicWrapSampler->getDesc();\n                samplerDescPoint.mipBias = lodBias;\n                samplerDescLinear.mipBias = lodBias;\n                samplerDescLinearWrap.mipBias = lodBias;\n                samplerDescAniso.mipBias = lodBias;\n                m_CommonPasses->m_PointClampSampler = GetDevice()->createSampler(samplerDescPoint);\n                m_CommonPasses->m_LinearClampSampler = GetDevice()->createSampler(samplerDescLinear);\n                m_CommonPasses->m_LinearWrapSampler = GetDevice()->createSampler(samplerDescLinearWrap);\n                m_CommonPasses->m_AnisotropicWrapSampler = GetDevice()->createSampler(samplerDescAniso);\n            }\n        }\n    }\n#else\n    denoiserMode = DenoiserMode::None;\n#endif\n\n    // Off, or disabled due to invalid settings.\n    if (denoiserMode == DenoiserMode::None)\n    {\n#if DONUT_WITH_STREAMLINE\n        StreamlineInterface::DLSSOptions dlssOptions = {};\n        dlssOptions.mode = StreamlineInterface::DLSSMode::eOff;\n        streamline.SetDLSSOptions(dlssOptions);\n        m_dlssLastMode = StreamlineInterface::DLSSMode::eOff;\n#endif\n        m_renderSize = m_displaySize;\n        m_lodBias = 1.0f;\n    }\n\n    // Update effective denoiser mode\n    if (m_renderParams.denoiserMode != denoiserMode)\n    {\n        m_renderParams.denoiserMode = denoiserMode;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::Animate(float fElapsedTimeSeconds)\n{\n    korgi::Update();\n    m_trackBall.Animate(GetCPUFrameTime() / 1000.f);\n\n    const auto& animState = m_ui.timeLineEditorState;\n    float animTime = animState.AnimationTime();\n    float animRate = animState.frameRate;\n\n    m_animationUpdated = m_animationTime != animTime;\n    if (m_animationUpdated)\n    {\n        // animation looped so we need to reset\n        if (animTime == 0)\n        {\n            GetRenderer().ResetDenoiser();\n        }\n\n        m_scene->Animate(animTime, animRate);\n\n        GetRenderer().ResetSubframes();\n        m_animationTime = animTime;\n        m_accelBuilderNeedsUpdate = true;\n    }\n}\n\nvoid RTXMGDemoApp::LerpVertices(\n    nvrhi::IBuffer* outBuffer,\n    nvrhi::IBuffer* keyFrame0Buffer,\n    nvrhi::IBuffer* keyFrame1Buffer,\n    unsigned int numVertices, float animTime)\n{\n    constexpr int blockSize = 32;\n    const int numBlocks = (numVertices + blockSize - 1) / blockSize;\n\n    LerpKeyFramesParams params;\n    params.numVertices = numVertices;\n    params.animTime = animTime;\n\n    m_commandList->writeBuffer(m_lerpKeyFramesParamsBuffer, &params, sizeof(LerpKeyFramesParams));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, keyFrame0Buffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(1, keyFrame1Buffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, outBuffer))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_lerpKeyFramesParamsBuffer));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(GetDevice(), nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_lerpVerticesBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for lerp_keyframes.hlsl\");\n    }\n\n    if (!m_lerpVerticesPSO)\n    {\n        nvrhi::ShaderHandle shader = GetRenderer().GetShaderFactory()->CreateShader(\"rtxmg_demo/lerp_keyframes.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_lerpVerticesBL);\n\n        m_lerpVerticesPSO = GetDevice()->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_lerpVerticesPSO)\n        .addBindingSet(bindingSet);\n    m_commandList->setComputeState(state);\n    m_commandList->dispatch(numBlocks, 1, 1);\n}\n\nvoid RTXMGDemoApp::DispatchGPUAnimation()\n{\n    nvrhi::utils::ScopedMarker marker(m_commandList, \"GPU Animation\");\n\n    auto& subdMeshes = m_scene->GetSubdMeshes();\n    auto& instances = m_scene->GetInstances();\n    for (auto& subd : subdMeshes)\n    {\n        if (!subd->HasAnimation())\n            continue;\n\n        // Cache to previous\n        m_commandList->copyBuffer(subd->m_positionsPrevBuffer, 0, subd->m_positionsBuffer, 0, subd->m_positionsBuffer->getDesc().byteSize);\n\n        LerpVertices(subd->m_positionsBuffer,\n            subd->m_positionKeyframeBuffers[subd->m_f0],\n            subd->m_positionKeyframeBuffers[subd->m_f1],\n            subd->NumVertices(),\n            subd->m_dt);\n    }\n}\n\nvoid RTXMGDemoApp::Render(nvrhi::IFramebuffer* framebuffer)\n{\n    auto& profiler = Profiler::Get();\n\n    auto& renderer = GetRenderer();\n    if (m_reloadShaders)\n    {\n        GetDevice()->waitForIdle();\n\n        m_accelBuilderNeedsUpdate = true;\n        renderer.ReloadShaders();\n\n        m_lerpVerticesPSO.Reset();\n        m_reloadShaders = false;\n    }\n\n    // Calculate DLSS settings\n    UpdateDLSSSettings();\n\n    renderer.SetRenderSize(m_renderSize, m_displaySize);\n    renderer.SetRenderCamera(m_camera, m_cameraReset);\n    m_sunLight->SetDirection(double3(m_camera.GetDirection()));\n\n    m_prevFrameStart = GetFrameIndex() > 0 ? m_currFrameStart : std::chrono::steady_clock::now();\n    m_currFrameStart = std::chrono::steady_clock::now();\n\n    if (profiler.IsRecording())\n        stats::frameSamplers.cpuFrameTime.PushBack(std::chrono::duration<float, std::milli>(m_currFrameStart - m_prevFrameStart).count());\n\n    profiler.FrameStart(m_currFrameStart);\n    m_commandList->open();\n    {\n        renderer.CreateOutputs(m_commandList);\n\n        std::string frameMarker = \"Frame Rendering \" + std::to_string(GetFrameIndex());\n        nvrhi::utils::ScopedMarker marker(m_commandList, frameMarker.c_str());\n\n        DispatchGPUAnimation();\n\n        stats::frameSamplers.gpuFrameTime.Start(m_commandList);\n        if (m_accelBuilderNeedsUpdate || m_ui.forceRebuildAccelStruct)\n        {\n            const TessellatorConfig tessConfig =\n            {\n                .memorySettings = m_args.tessMemorySettings,\n                .visMode = m_args.visMode,\n                .tessMode = m_args.tessMode,\n                .fineTessellationRate = m_args.fineTessellationRate,\n                .coarseTessellationRate = m_args.coarseTessellationRate,\n                .enableFrustumVisibility = m_args.enableFrustumVisibility,\n                .enableHiZVisibility = m_args.enableHiZVisibility,\n                .enableBackfaceVisibility = m_args.enableBackfaceVisibility,\n                .enableLogging = m_args.enableAccelBuildLogging,\n                .enableMonolithicClusterBuild = m_ui.enableMonolithicClusterBuild,\n                .enableVertexNormals = m_args.enableVertexNormals,\n                .viewportSize = { (uint32_t)m_renderSize.x, (uint32_t)m_renderSize.y },\n                .edgeSegments = m_args.edgeSegments,\n                .isolationLevel = m_renderParams.isolationLevel,\n                .clusterPattern = (ClusterPattern)m_renderParams.clusterPattern,\n                .quantNBits = m_args.quantNBits,\n                .displacementScale = m_renderParams.globalDisplacementScale,\n                .camera = &m_tesselationCamera,\n                .zbuffer = renderer.GetZBuffer(),\n                .debugSurfaceIndex = m_debugSurfaceClusterLaneIndex[0],\n                .debugClusterIndex = m_debugSurfaceClusterLaneIndex[1],\n                .debugLaneIndex = m_debugSurfaceClusterLaneIndex[2],\n            };\n\n            renderer.UpdateAccelerationStructures(tessConfig, m_BuildStats, GetFrameIndex(), m_commandList);\n            m_accelBuilderNeedsUpdate = false;\n        }\n\n        if (m_args.updateTessCamera)\n        {\n            ZBuffer* zbuffer = renderer.GetZBuffer();\n            \n            m_zRenderer->Render(m_camera, renderer.GetTopLevelAS(), zbuffer->GetCurrent(), m_commandList);\n            zbuffer->ReduceHierarchy(m_commandList);\n\n            m_tesselationCamera = m_camera;\n        }\n\n        renderer.Launch(m_commandList, GetFrameIndex(), m_sunLight);\n        renderer.DlssUpscale(m_commandList, GetFrameIndex());\n        renderer.BlitFramebuffer(m_commandList, framebuffer);\n\n        stats::frameSamplers.gpuFrameTime.Stop();\n\n        if (m_dumpFineTess)\n        {\n            // dump cluster vertex positions for debugging\n            DoDumpFineTess();\n            m_dumpFineTess = false;\n        }\n\n        if (m_dumpDebugBuffer)\n        {\n            // dump debugging output\n            DoDumpDebugBuffer();\n            m_dumpDebugBuffer = false;\n        }\n\n        if (m_dumpPixelDebug)\n        {\n            GetRenderer().DumpPixelDebugBuffers(m_commandList);\n            m_dumpPixelDebug = false;\n        }\n    }\n    m_commandList->close();\n    GetDevice()->executeCommandList(m_commandList);\n\n    profiler.FrameEnd();\n\n    if (profiler.IsRecording())\n    {\n        stats::clusterAccelSamplers.numClusters.PushBack(m_BuildStats.desired.m_numClusters);\n        stats::clusterAccelSamplers.numClusters.max = m_BuildStats.allocated.m_numClusters;\n        stats::clusterAccelSamplers.numTriangles.PushBack(m_BuildStats.desired.m_numTriangles);\n\n        stats::clusterAccelSamplers.renderSize = m_renderSize;\n\n        stats::memUsageSamplers.blasSize.PushBack(m_BuildStats.desired.m_blasSize);\n        stats::memUsageSamplers.clasSize.PushBack(m_BuildStats.desired.m_clasSize);\n        stats::memUsageSamplers.blasScratchSize.PushBack(m_BuildStats.desired.m_blasScratchSize);\n        stats::memUsageSamplers.vertexBufferSize.PushBack(m_BuildStats.desired.m_vertexBufferSize);\n        stats::memUsageSamplers.vertexNormalsBufferSize.PushBack(m_BuildStats.desired.m_vertexNormalsBufferSize);\n        stats::memUsageSamplers.clusterShadingDataSize.PushBack(m_BuildStats.desired.m_clusterDataSize);\n\n        stats::memUsageSamplers.blasSize.max = m_BuildStats.allocated.m_blasSize;\n        stats::memUsageSamplers.clasSize.max = m_BuildStats.allocated.m_clasSize;\n        stats::memUsageSamplers.blasScratchSize.max = m_BuildStats.allocated.m_blasScratchSize;\n        stats::memUsageSamplers.vertexBufferSize.max = m_BuildStats.allocated.m_vertexBufferSize;\n        stats::memUsageSamplers.vertexNormalsBufferSize.max = m_BuildStats.allocated.m_vertexNormalsBufferSize;\n        stats::memUsageSamplers.clusterShadingDataSize.max = m_BuildStats.allocated.m_clusterDataSize;\n    }\n\n    if (m_screenshot)\n    {\n        nvrhi::ITexture* framebufferTexture = framebuffer->getDesc().colorAttachments[0].texture;\n        DoSaveScreenshot(framebufferTexture, \"\");\n        m_screenshot = false;\n    }\n\n    m_cameraReset = false;\n}\n\nvoid RTXMGDemoApp::SetTessMemSettings(const TessellatorConfig::MemorySettings& settings)\n{\n    if (settings != m_args.tessMemorySettings)\n    {\n        m_args.tessMemorySettings = settings;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetFineTessellationRate(float rate)\n{\n    if (rate != m_args.fineTessellationRate)\n    {\n        m_args.fineTessellationRate = rate;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetCoarseTessellationRate(float rate)\n{\n    if (rate != m_args.coarseTessellationRate)\n    {\n        m_args.coarseTessellationRate = rate;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetTessellatorVisibilityMode(TessellatorConfig::VisibilityMode visMode)\n{\n    if (visMode != m_args.visMode)\n    {\n        m_args.visMode = visMode;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetBackfaceVisibilityEnabled(bool enabled)\n{\n    if (enabled != m_args.enableBackfaceVisibility)\n    {\n        m_args.enableBackfaceVisibility = enabled;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetQuantizationBits(int quantNBits)\n{\n    quantNBits = std::clamp(quantNBits, 0, 32);\n    if (quantNBits != m_args.quantNBits)\n    {\n        m_args.quantNBits = quantNBits;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetGlobalIsolationLevel(uint32_t isolationLevel)\n{\n    isolationLevel = std::clamp(isolationLevel, TessellatorConfig::kMinIsolationLevel, TessellatorConfig::kMaxIsolationLevel);\n    if (isolationLevel != m_renderParams.isolationLevel)\n    {\n        m_renderParams.isolationLevel = isolationLevel;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n    }\n}\n\nvoid RTXMGDemoApp::SetDisplacementScale(float scale)\n{\n    if (scale != m_renderParams.globalDisplacementScale)\n    {\n        m_renderParams.globalDisplacementScale = scale;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetClusterTessellationPattern(ClusterPattern clusterPattern)\n{\n    m_renderParams.clusterPattern = uint32_t(clusterPattern);\n    m_accelBuilderNeedsUpdate = true;\n    GetRenderer().ResetSubframes();\n    GetRenderer().ResetDenoiser();\n}\n\nvoid RTXMGDemoApp::SetAdaptiveTessellationMode(TessellatorConfig::AdaptiveTessellationMode mode)\n{\n    if (mode != m_args.tessMode)\n    {\n        m_args.tessMode = mode;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetFrustumVisibilityEnabled(bool enabled)\n{\n    if (enabled != m_args.enableFrustumVisibility)\n    {\n        m_args.enableFrustumVisibility = enabled;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetHiZVisibilityEnabled(bool enabled)\n{\n    if (enabled != m_args.enableHiZVisibility)\n    {\n        m_args.enableHiZVisibility = enabled;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetUpdateTessellationCamera(bool update)\n{\n    if (update != m_args.updateTessCamera)\n    {\n        m_args.updateTessCamera = update;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetVertexNormalsEnabled(bool enabled)\n{\n    if (enabled != m_args.enableVertexNormals)\n    {\n        m_args.enableVertexNormals = enabled;\n        m_accelBuilderNeedsUpdate = true;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::SetDenoiserMode(DenoiserMode denoiserMode)\n{\n    if (denoiserMode != m_denoiserMode)\n    {\n        m_denoiserMode = denoiserMode;\n        GetRenderer().ResetSubframes();\n        GetRenderer().ResetDenoiser();\n    }\n}\n\nvoid RTXMGDemoApp::DumpFineTess()\n{\n    m_dumpFineTess = true;\n    m_accelBuilderNeedsUpdate = true; // force a re-build\n    GetRenderer().ResetSubframes(); // force a re-render\n}\n\nvoid RTXMGDemoApp::DoDumpFineTess(std::string const& filepath)\n{\n    if (filepath.empty())\n    {\n        static char const base_name[] = \"rtxmg_fine_tess_\";\n        int index = GetUniqueFileIndex(base_name, \".ma\");\n        char buf[32];\n        std::snprintf(buf, std::size(buf), \"%s%04d.ma\", base_name, index);\n        DoDumpFineTess(buf);\n    }\n    else\n    {\n        auto logger = MayaLogger::Create(filepath.c_str());\n        MayaLogger::ParticleDescriptor desc;\n\n        auto& sceneAccels = GetRenderer().GetSceneAccels();\n        desc.positions = sceneAccels->clusterVertexPositionsBuffer.Download(m_commandList);\n        logger->CreateParticles(desc);\n    }\n}\n\nvoid RTXMGDemoApp::SaveScreenshot()\n{\n    m_screenshot = true;\n}\n\nvoid RTXMGDemoApp::DoSaveScreenshot(nvrhi::ITexture* framebufferTexture, std::string const& filepath)\n{\n    if (filepath.empty())\n    {\n        static char const base_name[] = \"rtxmg_screenshot_\";\n        int index = GetUniqueFileIndex(base_name, \".png\");\n\n        char buf[32];\n        std::snprintf(buf, std::size(buf), \"%s%04d.png\", base_name, index);\n        DoSaveScreenshot(framebufferTexture, buf);\n    }\n    else\n    {\n        SaveTextureToFile(GetDevice(), GetRenderer().GetCommonPasses().get(), framebufferTexture, nvrhi::ResourceStates::Unknown, filepath.c_str());\n    }\n}\n\nvoid RTXMGDemoApp::DumpDebugBuffer()\n{\n    m_dumpDebugBuffer = true;\n    m_accelBuilderNeedsUpdate = true; // force a re-build\n    GetRenderer().ResetSubframes(); // force a re-render\n}\n\nvoid RTXMGDemoApp::DoDumpDebugBuffer(std::string const& filepath)\n{\n    if (filepath.empty())\n    {\n        static char const base_name[] = \"rtxmg_debug_buffer_\";\n        int index = GetUniqueFileIndex(base_name, \".txt\");\n        char buf[32];\n        std::snprintf(buf, std::size(buf), \"%s%04d.txt\", base_name, index);\n        DoDumpDebugBuffer(buf);\n    }\n    else\n    {\n        auto debugContents = GetRenderer().GetAccelBuilder()->GetDebugBuffer().Download(m_commandList);\n\n        log::info(\"accel builder debug contents: \");\n        \n        vectorlog::OutputStream(debugContents, ShaderDebugElement::OutputLambda, nullptr, { .wrap = false, .header = false, .elementIndex = false, .startIndex = 1 });\n\n        std::ofstream fileStream(filepath);\n        vectorlog::OutputStream(debugContents, ShaderDebugElement::OutputLambda, &fileStream, { .wrap = false, .header = false, .elementIndex = false, .startIndex = 1 });\n    }\n}\n\nfloat RTXMGDemoApp::GetCPUFrameTime() const\n{\n    return std::chrono::duration<float, std::milli>(m_currFrameStart -\n        m_prevFrameStart)\n        .count();\n}\n\nvoid RTXMGDemoApp::SetWindowState(const WindowState &state) \n{\n    GLFWwindow* window = GetDeviceManager()->GetWindow();\n    \n    // Prioritize commandline options and ignore restoring window state\n    if (m_args.resolutionSetByCmdLine || m_args.startMaximized)\n        return;\n\n    if (all(state.windowSize > 0))\n    {\n        glfwSetWindowSize(window, state.windowSize.x, state.windowSize.y);\n    }\n    \n    if (state.isMaximized)\n    {\n        glfwMaximizeWindow(window);\n    }\n    else if (state.isFullscreen)\n    {\n        GLFWmonitor* monitor = glfwGetPrimaryMonitor();\n        const GLFWvidmode* mode = glfwGetVideoMode(monitor);\n        glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);\n    }\n}\n"
  },
  {
    "path": "demo/rtxmg_demo_app.h",
    "content": "#pragma once\n\n/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <array>\n#include <donut/app/ApplicationBase.h>\n#include <donut/app/Camera.h>\n#include <donut/app/DeviceManager.h>\n#include <donut/core/math/quat.h>\n#include <donut/core/vfs/VFS.h>\n#include <donut/engine/BindingCache.h>\n#include <donut/engine/DescriptorTableManager.h>\n#include <donut/engine/Scene.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/engine/View.h>\n\n#include \"args.h\"\n#include \"rtxmg_demo.h\"\n#include \"rtxmg_renderer.h\"\n#include \"gui.h\"\n#include \"zrenderer.h\"\n#include \"trackball.h\"\n\nusing namespace donut::math;\n\n#include \"render_params.h\"\n\n#include \"rtxmg/scene/camera.h\"\n#include \"rtxmg/scene/scene.h\"\n\nclass UserInterface;\n\nclass RTXMGDemoApp : public donut::app::ApplicationBase\n{\npublic:\n    struct WindowState\n    {\n        donut::math::int2 windowSize = { 0,0 };\n        bool isMaximized = false;\n        bool isFullscreen = false;\n    };\n\nprivate:\n    std::shared_ptr<RTXMGScene> m_scene;\n    Camera m_camera;\n    Camera m_prevCamera;\n    bool m_cameraReset = false;\n    Camera m_tesselationCamera;\n    Trackball m_trackBall;\n    \n    std::shared_ptr<donut::engine::DirectionalLight> m_sunLight;\n    nvrhi::CommandListHandle m_commandList;\n\n    // path to executable (because argv0 is not reliable)\n    std::filesystem::path m_binaryPath;\n\n    // path to local folder w/ media assets\n    std::filesystem::path m_mediaPath;\n\n    UIData m_ui;\n    Args m_args;\n    RenderParams m_renderParams;\n    int2 m_displaySize;\n    int2 m_renderSize;\n    float m_lodBias = 1.0f;\n    DenoiserMode m_denoiserMode = DenoiserMode::None; // Desired denoiser mode, but m_renderParams contains effective denoiser mode\n\n    int2 m_loadFrameRange = { std::numeric_limits<int>::max(), std::numeric_limits<int>::min() };\n    std::chrono::steady_clock::time_point m_currFrameStart = {};\n    std::chrono::steady_clock::time_point m_prevFrameStart = {};\n    float m_animationTime = 0.0f;\n\n    std::unique_ptr<RTXMGRenderer> m_renderer;\n    std::unique_ptr<ZRenderer> m_zRenderer;\n\n    nvrhi::BufferHandle m_lerpKeyFramesParamsBuffer;\n    nvrhi::BindingLayoutHandle m_lerpVerticesBL;\n    nvrhi::ComputePipelineHandle m_lerpVerticesPSO;\n\n    bool m_reloadShaders = false;\n    bool m_animationUpdated = false;\n    bool m_accelBuilderNeedsUpdate = true;\n    bool m_dumpFineTess = false;\n    bool m_screenshot = false;\n    bool m_dumpDebugBuffer = false;\n    bool m_dumpPixelDebug = false;\n\n    std::array<int, 3> m_debugSurfaceClusterLaneIndex = { -1, -1, -1 };\n\n    // DLSS State\n#if DONUT_WITH_STREAMLINE\n    using StreamlineInterface = donut::app::StreamlineInterface;\n    StreamlineInterface::DLSSPreset m_dlssLastPreset = StreamlineInterface::DLSSPreset::eDefault;\n    StreamlineInterface::DLSSMode m_dlssLastMode = StreamlineInterface::DLSSMode::eOff;\n    int2 m_dlssLastDisplaySize;\n\n    StreamlineInterface::DLSSSettings m_RecommendedDLSSSettings;\n    StreamlineInterface::DLSSRRSettings m_RecommendedDLSSRRSettings;\n#endif\n\n    int m_argc;\n    const char** m_argv;\n\n    UserInterface* m_gui;\n    WindowState m_windowState;\n\n    struct MessageCallback : public nvrhi::IMessageCallback\n    {\n        explicit MessageCallback(donut::app::DeviceManager* deviceManager)\n            : m_deviceManager(deviceManager)\n        {}\n        const donut::app::DeviceManager* m_deviceManager;\n        void message(nvrhi::MessageSeverity severity, const char* messageText) override;\n    };\n    MessageCallback m_messageCallback;\n\n    void UpdateParams();\n    void UpdateDLSSSettings();\n\n    void DoSaveScreenshot(nvrhi::ITexture* framebufferTexture, std::string const& filename = \"\");\n    void DoDumpFineTess(std::string const& filename = \"\");\n    void DoDumpDebugBuffer(std::string const& filename = \"\");\n        \n    void LerpVertices(nvrhi::IBuffer* outBuffer,\n        nvrhi::IBuffer* keyFrame0Buffer,\n        nvrhi::IBuffer* keyFrame1Buffer,\n        unsigned int numVertices, float animTime);\n    void DispatchGPUAnimation();\n\npublic:\n    // AppBase Overrides\n    bool LoadScene(std::shared_ptr<donut::vfs::IFileSystem> fs,\n        const std::filesystem::path& sceneFileName) override;\n\n    bool KeyboardUpdate(int key, int scancode, int action, int mods) override;\n    bool MousePosUpdate(double xpos, double ypos) override;\n    bool MouseButtonUpdate(int button, int action, int mods) override;\n    bool MouseScrollUpdate(double xoffset, double yoffset) override;\n\n    void BackBufferResizing() override;\n    void BackBufferResized(const uint32_t width, const uint32_t height,\n        const uint32_t sampleCount) override;\n    void Animate(float fElapsedTimeSeconds) override;\n    void Render(nvrhi::IFramebuffer* framebuffer) override;\n\npublic:\n    RTXMGDemoApp(donut::app::DeviceManager* deviceManager, std::string &windowTitle, int argc,\n        const char** argv);\n    virtual ~RTXMGDemoApp();\n\n    bool Init();\n    void ResetCamera();\n    bool SetEnvmapTex(const std::string& filePath);\n\n    void HandleSceneLoad(std::string const& m_filepath,\n        std::string const& mediapathm, int2 frameRange = { std::numeric_limits<int>::max(), std::numeric_limits<int>::min() });\n    const RTXMGScene& GetScene() const { return *m_scene; }\n    RTXMGRenderer& GetRenderer();\n\n    float GetCPUFrameTime() const;\n\n    ///////////////////////////////////////////////////////\n    // GUI access\n    ///////////////////////////////////////////////////////\n    void SetGui(UserInterface* gui) { m_gui = gui; }\n    const std::filesystem::path& GetBinaryPath() const { return m_binaryPath; }\n    const std::filesystem::path& GetMediaPath() const { return m_mediaPath; }\n    void SetMediaPath(const std::filesystem::path& path) { m_mediaPath = path; }\n    UIData& GetUIData() { return m_ui; }\n\n    WindowState GetWindowState() const { return m_windowState; }\n    void SetWindowState(const WindowState& state);\n\n    void DumpFineTess();\n    void SaveScreenshot();\n    void DumpDebugBuffer();\n    void ReloadShaders() { m_reloadShaders = true; }\n    void RebuildAS() { m_accelBuilderNeedsUpdate = true; }\n\n    TessellatorConfig::MemorySettings GetTessMemSettings() const { return m_args.tessMemorySettings; }\n    void SetTessMemSettings(const TessellatorConfig::MemorySettings& settings);\n\n    bool GetUpdateTessellationCamera() const { return m_args.updateTessCamera; }\n    void SetUpdateTessellationCamera(bool update);\n\n    float GetFineTessellationRate() const { return m_args.fineTessellationRate; }\n    void  SetFineTessellationRate(float rate);\n\n    float GetCoarseTessellationRate() const { return m_args.coarseTessellationRate; }\n    void  SetCoarseTessellationRate(float rate);\n\n    int GetIsolationLevelSharp() const { return m_args.isoLevelSharp; }\n    int GetIsolationLevelSmooth() const { return m_args.isoLevelSmooth; }\n    \n    void SetGlobalIsolationLevel(uint32_t isolationLevel);\n    uint32_t GetGlobalIsolationLevel() const { return m_renderParams.isolationLevel; }\n\n    TessellatorConfig::VisibilityMode GetTessellatorVisibilityMode() const { return m_args.visMode; }\n    void                              SetTessellatorVisibilityMode(TessellatorConfig::VisibilityMode visMode);\n\n    bool GetBackfaceVisibilityEnabled() const { return m_args.enableBackfaceVisibility; }\n    void SetBackfaceVisibilityEnabled(bool enabled);\n\n    int GetQuantizationBits() const { return m_args.quantNBits; }\n    void SetQuantizationBits(int quantNBits);\n\n    void SetDisplacementScale(float scale);\n    float GetDisplacementScale() const { return m_renderParams.globalDisplacementScale; }\n\n    ClusterPattern GetClusterTessellationPattern() const\n    {\n        return (ClusterPattern)m_renderParams.clusterPattern;\n    }\n    void SetClusterTessellationPattern(ClusterPattern clusterPattern);\n\n    TessellatorConfig::AdaptiveTessellationMode GetAdaptiveTessellationMode() const { return m_args.tessMode; }\n    void SetAdaptiveTessellationMode(TessellatorConfig::AdaptiveTessellationMode mode);\n\n    bool GetFrustumVisibilityEnabled() const { return m_args.enableFrustumVisibility; }\n    void SetFrustumVisibilityEnabled(bool enabled);\n\n    bool GetHiZVisibilityEnabled() const { return m_args.enableHiZVisibility; }\n    void SetHiZVisibilityEnabled(bool enabled);\n\n    bool GetVertexNormalsEnabled() const { return m_args.enableVertexNormals; }\n    void SetVertexNormalsEnabled(bool enabled);\n\n    bool GetAccelBuildLoggingEnabled() const { return m_args.enableAccelBuildLogging; }\n    void SetAccelBuildLoggingEnabled(bool enabled) { m_args.enableAccelBuildLogging = enabled; }\n\n    std::array<int, 3>& GetDebugSurfaceClusterLaneIndex() { return m_debugSurfaceClusterLaneIndex; }\n\n    // Desired denoiser mode\n    void SetDenoiserMode(DenoiserMode denoiserMode);\n    DenoiserMode GetDenoiserMode() const { return m_denoiserMode; }\n    DenoiserMode GetEffectiveDenoiserMode() const { return m_renderParams.denoiserMode; }\n\n    void NextShadingMode()\n    {\n        auto& renderer = GetRenderer();\n        if (renderer.GetShowMicroTriangles())\n            return;\n\n        ShadingMode shadingMode = ShadingMode((int(renderer.GetShadingMode()) + 1) % int(ShadingMode::SHADING_MODE_COUNT));\n        renderer.SetShadingMode(shadingMode);\n    }\n\n    void NextTonemapper()\n    {\n        auto& renderer = GetRenderer();\n        if (renderer.GetShowMicroTriangles())\n            return;\n\n        TonemapOperator op = TonemapOperator((int(renderer.GetTonemapOperator()) + 1) % int(TonemapOperator::Count));\n        renderer.SetTonemapOperator(op);\n    }\n\n    void IncrementMaxBounces(int delta)\n    {\n        auto& renderer = GetRenderer();\n        if (renderer.GetEffectiveShadingMode() == ShadingMode::PT)\n        {\n            int maxBounces = std::min(renderer.GetPTMaxBounces() + delta, 10);\n            renderer.SetPTMaxBounces(maxBounces);\n        }\n    }\n\n    void ToggleWireframe()\n    {\n        auto& renderer = GetRenderer();\n        if (renderer.GetShowMicroTriangles())\n            return;\n\n        bool wireframe = !renderer.GetWireframe();\n        renderer.SetWireframe(wireframe);\n    }\n\n    void IncrementColorMode(int delta)\n    {\n        auto& renderer = GetRenderer();\n        if (renderer.GetShowMicroTriangles())\n            return;\n\n        ColorMode colorMode = ColorMode((int(renderer.GetColorMode()) + int(ColorMode::COLOR_MODE_COUNT) + delta) % int(ColorMode::COLOR_MODE_COUNT));\n        renderer.SetColorMode(colorMode);\n    }\n\n    void ToggleTimeView()\n    {\n        auto& renderer = GetRenderer();\n        if (renderer.GetEffectiveShadingMode() == ShadingMode::PT)\n        {\n            bool timeView = !renderer.GetTimeView();\n            renderer.SetTimeView(timeView);\n        }\n    }\n\n    void ToggleUpdateTessellationCamera()\n    {\n        SetUpdateTessellationCamera(!GetUpdateTessellationCamera());\n    }\n\n    ClusterStatistics m_BuildStats;\n};"
  },
  {
    "path": "demo/rtxmg_renderer.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include \"rtxmg_renderer.h\"\n#include \"rtxmg/utils/buffer.h\"\n#include \"ray_payload.h\"\n#include \"render_targets.h\"\n\n#include <donut/app/ApplicationBase.h>\n#include <donut/app/StreamlineInterface.h>\n#include <donut/core/math/math.h>\n#include <donut/engine/CommonRenderPasses.h>\n#include <nvrhi/utils.h>\n\n#include <filesystem>\n#include <string>\n#include <sstream>\n\nusing namespace donut;\nusing namespace donut::math;\n\n#include \"lighting_cb.h\"\n#include \"gbuffer.h\"\n\n#include \"rtxmg/utils/debug.h\"\n#include \"rtxmg/scene/scene.h\"\n#include \"rtxmg/scene/camera.h\"\n#include \"rtxmg/cluster_builder/fill_instance_descs_params.h\"\n#include \"rtxmg/profiler/statistics.h\"\n#include \"rtxmg/subdivision/subdivision_surface.h\"\n#include \"envmap/preprocess_envmap_params.h\"\n\nRTXMGRenderer::RTXMGRenderer(Options const& opts)\n    : m_options(opts), m_params(opts.params)\n{\n    std::filesystem::path frameworkShaderPath =\n        app::GetDirectoryWithExecutable() / \"shaders/framework\" /\n        app::GetShaderTypeName(GetDevice()->getGraphicsAPI());\n\n    std::filesystem::path appShaderPath =\n        app::GetDirectoryWithExecutable() / \"shaders/rtxmg_demo\" /\n        app::GetShaderTypeName(GetDevice()->getGraphicsAPI()) / \"shaders\";\n\n    auto fs = std::make_shared<vfs::RootFileSystem>();\n\n    auto mount = [&fs, this](std::string const& dir, std::string const& alias = \"\")\n        {\n            std::filesystem::path shaderPath =\n                app::GetDirectoryWithExecutable() / \"shaders\" / dir /\n                app::GetShaderTypeName(GetDevice()->getGraphicsAPI()) / \"shaders\";\n\n            std::string aliasStr = (alias.empty() ? dir : alias);\n            fs->mount(std::format(\"/shaders/{}\", aliasStr), shaderPath.string());\n        };\n\n    mount(\"rtxmg_demo\");\n    mount(\"cluster_builder\");\n    mount(\"envmap\");\n    mount(\"hiz\");\n    mount(\"subdivision\");\n\n    fs->mount(\"/shaders/donut\", frameworkShaderPath);\n\n    m_shaderFactory =\n        std::make_shared<engine::ShaderFactory>(GetDevice(), fs, \"/shaders\");\n    m_commonPasses = std::make_shared<engine::CommonRenderPasses>(\n        GetDevice(), m_shaderFactory);\n\n    m_bindingCache = std::make_unique<engine::BindingCache>(GetDevice());\n\n    nvrhi::BindlessLayoutDesc bindlessLayoutDesc;\n    bindlessLayoutDesc.visibility = nvrhi::ShaderType::All;\n    bindlessLayoutDesc.firstSlot = 0;\n    bindlessLayoutDesc.maxCapacity = 1024;\n    bindlessLayoutDesc.layoutType = nvrhi::BindlessLayoutDesc::LayoutType::MutableSrvUavCbv;\n    m_bindlessLayout = GetDevice()->createBindlessLayout(bindlessLayoutDesc);\n\n    nvrhi::BindingLayoutDesc globalBindingLayoutDesc;\n    globalBindingLayoutDesc.visibility = nvrhi::ShaderType::All;\n    globalBindingLayoutDesc.bindings = {\n        nvrhi::BindingLayoutItem::VolatileConstantBuffer(0), // lighting constants\n        nvrhi::BindingLayoutItem::VolatileConstantBuffer(1), // render parameters\n        nvrhi::BindingLayoutItem::RayTracingAccelStruct(0),  // TLAS\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(1),   // instance buffer\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(2),   // geometry buffer\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(3),   // material buffer\n        nvrhi::BindingLayoutItem::Texture_SRV(4),            // env map\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(5),   // env map conditional CDF\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(6),   // env map marginal CDF\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(7),   // env map conditional Func\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(8),   // env map marginal Func\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(9),   // cluster shading data\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(10),  // cluster vertex positions\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(11),  // cluster vertex normals\n        nvrhi::BindingLayoutItem::StructuredBuffer_SRV(12),  // topology quality\n        nvrhi::BindingLayoutItem::Sampler(0),                // linear wrap\n        nvrhi::BindingLayoutItem::Texture_UAV(0),            // accum\n        nvrhi::BindingLayoutItem::Texture_UAV(1),            // depth\n        nvrhi::BindingLayoutItem::Texture_UAV(2),            // normal\n        nvrhi::BindingLayoutItem::Texture_UAV(3),            // albedo\n        nvrhi::BindingLayoutItem::Texture_UAV(4),            // specular\n        nvrhi::BindingLayoutItem::Texture_UAV(5),            // specular hitT\n        nvrhi::BindingLayoutItem::Texture_UAV(6),            // roughness\n        nvrhi::BindingLayoutItem::StructuredBuffer_UAV(7),   // hit result\n\n        // DEBUG\n#if ENABLE_DUMP_FLOAT\n        nvrhi::BindingLayoutItem::Texture_UAV(8),        // debug\n        nvrhi::BindingLayoutItem::Texture_UAV(9),       // debug\n        nvrhi::BindingLayoutItem::Texture_UAV(10),       // debug\n        nvrhi::BindingLayoutItem::Texture_UAV(11),       // debug\n#endif\n#if ENABLE_SHADER_DEBUG\n        nvrhi::BindingLayoutItem::StructuredBuffer_UAV(12),  // pixel debug buffer\n#endif\n        nvrhi::BindingLayoutItem::TypedBuffer_UAV(13)        // Timeview buffer\n    };\n\n    if (GetDevice()->getGraphicsAPI() == nvrhi::GraphicsAPI::D3D12)\n    {\n        // for nvidia extensions\n        globalBindingLayoutDesc.bindings.push_back(nvrhi::BindingLayoutItem::TypedBuffer_UAV(RTXMG_NVAPI_SHADER_EXT_SLOT)); \n    }\n    \n    m_bindingLayout = GetDevice()->createBindingLayout(globalBindingLayoutDesc);\n\n    m_descriptorTable = std::make_shared<engine::DescriptorTableManager>(\n        GetDevice(), m_bindlessLayout);\n\n    m_lightingConstantsBuffer =\n        GetDevice()->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n            sizeof(LightingConstants), \"LightingConstants\",\n            engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_renderParamsBuffer =\n        GetDevice()->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n            sizeof(RenderParams), \"RenderParams\",\n            engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_fillInstanceDescsParams =\n        GetDevice()->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n            sizeof(FillInstanceDescsParams), \"FillInstanceDescsParams\",\n            engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_timeViewBuffer = CreateBuffer(2, sizeof(uint32_t), \"TimeViewBuffer\", GetDevice(), nvrhi::Format::R32_UINT);\n\n    auto nativeFS = std::make_shared<vfs::NativeFileSystem>();\n    m_textureCache = std::make_shared<engine::TextureCache>(GetDevice(), nativeFS,\n        m_descriptorTable);\n\n    m_blitParamsBuffer = GetDevice()->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(BlitParams), \"BlitParams\",\n        engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_preprocessEnvMapResources.m_params = GetDevice()->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(PreprocessEnvMapParams), \"PreprocessEnvMapParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_scanSystem.Init(m_shaderFactory, GetDevice());\n}\n\nRTXMGRenderer::~RTXMGRenderer() {}\n\nvoid RTXMGRenderer::ReloadShaders()\n{\n    m_shaderFactory->ClearCache();\n\n    m_needsRebind = true;\n    \n    // Clear all ray tracing permutations\n    for (auto& pipeline : m_rayPipelines)\n    {\n        pipeline.Reset();\n    }\n    for (auto& shaderTable : m_shaderTables)\n    {\n        shaderTable.Reset();\n    }\n    \n    m_clusterAccelBuilder = std::make_unique<ClusterAccelBuilder>(*m_shaderFactory, m_commonPasses, GetDescriptorTable()->GetDescriptorTable(), GetDevice());\n    m_sceneAccels = std::make_unique<ClusterAccels>();\n    m_zbuffer.reset();\n\n    m_scanSystem.Init(m_shaderFactory, GetDevice());\n    if (m_envMap)\n    {\n        m_needsEnvMapUpdate = true;\n    }\n\n    for (auto& pso : m_motionVectorsPSO)\n    {\n        pso.Reset();\n    }\n    m_blitPipeline.Reset();\n    m_preprocessEnvMapShaders.m_computeConditionalPSO.Reset();\n    m_preprocessEnvMapShaders.m_computeMarginalPSO.Reset();\n    m_fillInstanceDescsPSO.Reset();\n}\n\n\n\nvoid RTXMGRenderer::ComputeMotionVectors(nvrhi::ICommandList* commandList)\n{\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_renderParamsBuffer))\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(0, m_outputTextures[uint32_t(OutputTexture::Depth)]))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(1, m_hitResultBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(2, m_subdInstancesBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(3, m_scene->GetInstanceBuffer()))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(4, m_scene->GetGeometryBuffer()))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(5, m_scene->GetMaterialBuffer()))\n        .addItem(nvrhi::BindingSetItem::Texture_UAV(0, m_outputTextures[uint32_t(OutputTexture::MotionVectors)]))\n#if ENABLE_SHADER_DEBUG\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(1, m_motionVectorsPixelDebugBuffer))\n#endif\n        .addItem(nvrhi::BindingSetItem::Sampler(0, m_commonPasses->m_LinearWrapSampler));\n\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(GetDevice(), nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_motionVectorsBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for motion_vectors.hlsl\");\n    }\n\n    nvrhi::ComputePipelineHandle& motionVectorsPSO = m_motionVectorsPSO[uint32_t(m_mvecDisplacement)];\n    if (!motionVectorsPSO)\n    {\n        std::vector<donut::engine::ShaderMacro> macros;\n        macros.push_back(donut::engine::ShaderMacro(\"MVEC_DISPLACEMENT\", \n            m_mvecDisplacement == MvecDisplacement::FromSubdEval ? \"MVEC_DISPLACEMENT_FROM_SUBD_EVAL\" : \"MVEC_DISPLACEMENT_FROM_MATERIAL\"));\n        nvrhi::ShaderHandle shader = m_shaderFactory->CreateShader(\"rtxmg_demo/motion_vectors.hlsl\", \"main\", &macros, nvrhi::ShaderType::Compute);\n\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_motionVectorsBL)\n            .addBindingLayout(m_bindlessLayout);\n        \n        motionVectorsPSO = GetDevice()->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(motionVectorsPSO)\n        .addBindingSet(bindingSet)\n        .addBindingSet(m_descriptorTable->GetDescriptorTable());\n\n    commandList->setComputeState(state);\n    commandList->dispatch(div_ceil(m_renderSize.x, kMotionVectorsNumThreadsY), div_ceil(m_renderSize.y, kMotionVectorsNumThreadsY), 1);\n}\n\nvoid RTXMGRenderer::DlssUpscale(nvrhi::ICommandList *commandList, uint32_t frameIndex)\n{\n    ScopedGPUTimer timer(stats::frameSamplers.gpuDenoiserTime, commandList);\n\n#if DONUT_WITH_STREAMLINE\n    using StreamlineInterface = donut::app::StreamlineInterface;\n    if (m_params.denoiserMode == DenoiserMode::None)\n        return;\n\n    StreamlineInterface& streamline = donut::app::DeviceManager::GetStreamline();\n    const uint32_t kViewportId = 0;\n    streamline.SetViewport(kViewportId);\n\n    // SET STREAMLINE CONSTANTS\n    {\n        // This section of code updates the streamline constants every frame. Regardless of whether we are utilising the streamline plugins, as long as streamline is in use, we must set its constants.\n        affine3 viewReprojection = m_view.GetInverseViewMatrix() * m_viewPrevious.GetViewMatrix();\n        float4x4 reprojectionMatrix = m_view.GetInverseProjectionMatrix(false) * affineToHomogeneous(viewReprojection) * m_viewPrevious.GetProjectionMatrix(false);\n        float aspectRatio = float(m_renderSize.x) / float(m_renderSize.y);\n        \n        float2 jitterOffset = m_view.GetPixelOffset();\n\n        StreamlineInterface::Constants slConstants = {};\n        slConstants.cameraAspectRatio = aspectRatio;\n        slConstants.cameraFOV = dm::radians(m_camera.GetFovY());\n        slConstants.cameraFar = m_camera.GetZFar();\n        slConstants.cameraMotionIncluded = true;\n        slConstants.cameraNear = m_camera.GetZNear();\n        slConstants.cameraPinholeOffset = { 0.f, 0.f };\n        slConstants.cameraPos = m_view.GetInverseViewMatrix().m_translation;\n        slConstants.cameraFwd = m_view.GetInverseViewMatrix().m_linear[2];\n        slConstants.cameraUp = m_view.GetInverseViewMatrix().m_linear[1];\n        slConstants.cameraRight = m_view.GetInverseViewMatrix().m_linear[0];\n        slConstants.cameraViewToClip = m_view.GetProjectionMatrix(false);\n        slConstants.clipToCameraView = m_view.GetInverseProjectionMatrix(false);\n        slConstants.clipToPrevClip = reprojectionMatrix;\n        slConstants.prevClipToClip = inverse(reprojectionMatrix);\n        slConstants.depthInverted = m_view.IsReverseDepth();\n        slConstants.jitterOffset = -jitterOffset; // Jitter application to primary rays is negated relative to DLSS expectations.\n        slConstants.mvecScale = { 1.0f / m_renderSize.x , 1.0f / m_renderSize.y }; // This are scale factors used to normalize mvec (to -1,1) and donut has mvec in pixel space\n        slConstants.reset = m_resetDenoiser;\n        slConstants.motionVectors3D = false;\n        slConstants.motionVectorsInvalidValue = FLT_MIN;\n\n        streamline.SetConstants(slConstants);\n    }\n\n    streamline.TagResourcesGeneral(commandList,\n       m_view.GetChildView(ViewType::PLANAR, 0),\n       m_outputTextures[uint32_t(OutputTexture::MotionVectors)],\n       m_outputTextures[uint32_t(OutputTexture::Depth)],\n       m_outputTextures[uint32_t(OutputTexture::Accumulation)]);\n\n    if (m_params.denoiserMode == DenoiserMode::DlssSr)\n    {\n        streamline.TagResourcesDLSSNIS(commandList,\n            m_view.GetChildView(ViewType::PLANAR, 0),\n            m_outputTextures[uint32_t(OutputTexture::DlssOutputColor)],\n            m_outputTextures[uint32_t(OutputTexture::Accumulation)]);\n\n        streamline.EvaluateDLSS(commandList);\n    } \n    else if (m_params.denoiserMode == DenoiserMode::DlssRr)\n    {\n        streamline.TagResourcesDLSSRR(commandList,\n            m_view.GetChildView(ViewType::PLANAR, 0),\n            m_renderSize,\n            m_displaySize,\n            m_outputTextures[uint32_t(OutputTexture::Accumulation)],\n            m_outputTextures[uint32_t(OutputTexture::Albedo)],\n            m_outputTextures[uint32_t(OutputTexture::Specular)],\n            m_outputTextures[uint32_t(OutputTexture::Normals)],\n            m_outputTextures[uint32_t(OutputTexture::Roughness)],\n            m_outputTextures[uint32_t(OutputTexture::SpecularHitT)],\n            nullptr,\n            m_outputTextures[uint32_t(OutputTexture::DlssOutputColor)]);\n\n        streamline.EvaluateDLSSRR(commandList);\n    }\n#endif\n\n    m_resetDenoiser = false;\n}\n\nvoid RTXMGRenderer::CreateOutputs(nvrhi::ICommandList *commandList)\n{ \n    auto UpdateTexture = [this](nvrhi::TextureHandle& handle, int2 size, nvrhi::Format format, const char* debugName)\n        {\n            if (handle && handle->getDesc().width == size.x && handle->getDesc().height == size.y)\n                return;\n\n            nvrhi::TextureDesc desc;\n            desc.width = size.x;\n            desc.height = size.y;\n            desc.isUAV = true;\n            desc.keepInitialState = true;\n            desc.format = format;\n            desc.initialState = nvrhi::ResourceStates::UnorderedAccess;\n            desc.debugName = debugName;\n            handle = GetDevice()->createTexture(desc);\n\n            m_bindingSet = nullptr;\n        };\n\n    auto UpdateRenderTexture = [&UpdateTexture, this](nvrhi::TextureHandle& handle, nvrhi::Format format, const char* debugName)\n        {\n            UpdateTexture(handle, m_renderSize, format, debugName);\n        };\n    auto UpdateDisplayTexture = [&UpdateTexture, this](nvrhi::TextureHandle& handle, nvrhi::Format format, const char* debugName)\n        {\n            UpdateTexture(handle, m_displaySize, format, debugName);\n        };\n\n    auto UpdateRenderBuffer = [this](nvrhi::BufferHandle& handle, size_t elementSize, nvrhi::Format format, const char* debugName)\n        {\n            size_t newSize = m_renderSize.x * m_renderSize.y * elementSize;\n            if (handle && handle->getDesc().byteSize == newSize)\n                return;\n\n            nvrhi::BufferDesc bufferDesc =\n                nvrhi::BufferDesc()\n                .setByteSize(m_renderSize.x * m_renderSize.y * elementSize)\n                .setCanHaveTypedViews(true)\n                .setCanHaveUAVs(true)\n                .setFormat(format)\n                .setStructStride(uint32_t(elementSize))\n                .setDebugName(debugName)\n                .setInitialState(nvrhi::ResourceStates::UnorderedAccess)\n                .setKeepInitialState(true);\n\n            handle = GetDevice()->createBuffer(bufferDesc);\n\n            m_bindingSet = nullptr;\n        };\n\n    UpdateDisplayTexture(m_outputTextures[uint32_t(OutputTexture::DlssOutputColor)], nvrhi::Format::RGBA16_FLOAT, \"DlssOutputColor\");    \n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::Accumulation)], nvrhi::Format::RGBA32_FLOAT, \"Accum\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::Depth)], nvrhi::Format::R32_FLOAT, \"Depth\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::Normals)], nvrhi::Format::RGBA16_FLOAT, \"Normals\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::Albedo)], nvrhi::Format::RGBA8_UNORM, \"Albedo\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::Specular)], nvrhi::Format::RGBA8_UNORM, \"Specular\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::SpecularHitT)], nvrhi::Format::R32_FLOAT, \"SpecularHitT\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::Roughness)], nvrhi::Format::R8_UNORM, \"Roughness\");\n    UpdateRenderTexture(m_outputTextures[uint32_t(OutputTexture::MotionVectors)], nvrhi::Format::RG16_FLOAT, \"MotionVectors\");\n    UpdateRenderBuffer(m_hitResultBuffer, sizeof(HitResult), nvrhi::Format::UNKNOWN, \"HitResult\");\n\n    UpdateDisplayTexture(m_displayTexture, nvrhi::Format::RGBA8_UNORM, \"Display\");\n\n#if ENABLE_SHADER_DEBUG\n    if (!m_pixelDebugBuffer.GetBuffer())\n    {\n        m_pixelDebugBuffer.Create(64, \"PixelDebugBuffer\", GetDevice());\n        m_motionVectorsPixelDebugBuffer.Create(64, \"MotionVectorPixelDebugBuffer\", GetDevice());\n    }\n#endif\n\n#if ENABLE_DUMP_FLOAT\n    if (!m_outputTextures[index(OutputTexture::Debug1)])\n    {\n        static_assert(index(OutputTexture::Debug1) < index(OutputTexture::Debug4));\n        for (size_t i = index(OutputTexture::Debug1); i <= index(OutputTexture::Debug4); i++)\n        {\n            std::string debugName = \"Debug Texture \" + std::to_string(i - index(OutputTexture::Debug1) + 1);\n            UpdateRenderTexture(m_outputTextures[i], nvrhi::Format::RGBA16_FLOAT, debugName.c_str());\n        }\n    }\n#endif\n\n\n    if (!m_dummyBuffer)\n    {\n        m_dummyBuffer = CreateAndUploadBuffer(std::vector<float>{0.f}, \"DummyBuffer\", commandList, nvrhi::Format::R32_FLOAT);\n    }\n\n    if (!m_zbuffer)\n    {\n        m_zbuffer = ZBuffer::Create(uint2(m_renderSize.x, m_renderSize.y), m_commonPasses, m_shaderFactory, commandList);\n    }\n}\n\nvoid RTXMGRenderer::Launch(nvrhi::ICommandList* commandList,\n    uint32_t frameIndex,\n    std::shared_ptr<engine::Light> light)\n{\n    if (m_needsEnvMapUpdate)\n    {\n        UpdateEnvMapSampling(commandList);\n        m_needsEnvMapUpdate = false;\n    }\n\n    if (!m_bindingSet || m_needsRebind)\n    {\n        nvrhi::BufferHandle conditionalCDF = m_envMap ?\n            m_preprocessEnvMapResources.m_conditionalCdf :\n            m_dummyBuffer;\n        nvrhi::BufferHandle marginalCDF = m_envMap ?\n            m_preprocessEnvMapResources.m_marginalCdf :\n            m_dummyBuffer;\n        nvrhi::BufferHandle conditionalFunc = m_envMap ?\n            m_preprocessEnvMapResources.m_conditionalFunc :\n            m_dummyBuffer;\n        nvrhi::BufferHandle marginalFunc = m_envMap ?\n            m_preprocessEnvMapResources.m_marginalFunc :\n            m_dummyBuffer;\n\n        m_needsRebind = false;\n        nvrhi::BindingSetDesc bindingSetDesc;\n        bindingSetDesc.bindings = {\n            nvrhi::BindingSetItem::ConstantBuffer(0, m_lightingConstantsBuffer),\n            nvrhi::BindingSetItem::ConstantBuffer(1, m_renderParamsBuffer),\n            nvrhi::BindingSetItem::RayTracingAccelStruct(0, m_topLevelAS),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(1, m_scene->GetInstanceBuffer()),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(2, m_scene->GetGeometryBuffer()),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(3, m_scene->GetMaterialBuffer()),\n            nvrhi::BindingSetItem::Texture_SRV(4, m_envMap ? m_envMap->texture : m_commonPasses->m_BlackTexture),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(5, conditionalCDF),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(6, marginalCDF),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(7, conditionalFunc),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(8, marginalFunc),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(9, m_sceneAccels->clusterShadingDataBuffer),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(10, m_sceneAccels->clusterVertexPositionsBuffer),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(11, m_sceneAccels->clusterVertexNormalsBuffer),\n            nvrhi::BindingSetItem::StructuredBuffer_SRV(12, m_subdInstancesBuffer),\n            nvrhi::BindingSetItem::Sampler(0, m_commonPasses->m_LinearWrapSampler),\n            nvrhi::BindingSetItem::Texture_UAV(0, m_outputTextures[uint32_t(OutputTexture::Accumulation)]),\n            nvrhi::BindingSetItem::Texture_UAV(1, m_outputTextures[uint32_t(OutputTexture::Depth)]),\n            nvrhi::BindingSetItem::Texture_UAV(2, m_outputTextures[uint32_t(OutputTexture::Normals)]),\n            nvrhi::BindingSetItem::Texture_UAV(3, m_outputTextures[uint32_t(OutputTexture::Albedo)]),\n            nvrhi::BindingSetItem::Texture_UAV(4, m_outputTextures[uint32_t(OutputTexture::Specular)]),\n            nvrhi::BindingSetItem::Texture_UAV(5, m_outputTextures[uint32_t(OutputTexture::SpecularHitT)]),\n            nvrhi::BindingSetItem::Texture_UAV(6, m_outputTextures[uint32_t(OutputTexture::Roughness)]),\n            nvrhi::BindingSetItem::StructuredBuffer_UAV(7, m_hitResultBuffer),\n        #if ENABLE_DUMP_FLOAT\n            nvrhi::BindingSetItem::Texture_UAV(8, m_outputTextures[index(OutputTexture::Debug1)]),\n            nvrhi::BindingSetItem::Texture_UAV(9, m_outputTextures[index(OutputTexture::Debug2)]),\n            nvrhi::BindingSetItem::Texture_UAV(10, m_outputTextures[index(OutputTexture::Debug3)]),\n            nvrhi::BindingSetItem::Texture_UAV(11, m_outputTextures[index(OutputTexture::Debug4)]),\n        #endif\n        #if ENABLE_SHADER_DEBUG\n            nvrhi::BindingSetItem::StructuredBuffer_UAV(12, m_pixelDebugBuffer),\n        #endif\n            nvrhi::BindingSetItem::TypedBuffer_UAV(13, m_timeViewBuffer)\n        };\n\n        if (GetDevice()->getGraphicsAPI() == nvrhi::GraphicsAPI::D3D12)\n        {\n            bindingSetDesc.bindings.push_back(nvrhi::BindingSetItem::TypedBuffer_UAV(RTXMG_NVAPI_SHADER_EXT_SLOT, nullptr)); // for nvidia extensions\n        }\n\n\n        m_bindingSet =\n            GetDevice()->createBindingSet(bindingSetDesc, m_bindingLayout);\n    }\n\n    {\n#if ENABLE_SHADER_DEBUG\n        commandList->clearBufferUInt(m_pixelDebugBuffer, 0);\n        commandList->clearBufferUInt(m_motionVectorsPixelDebugBuffer, 0);\n#endif\n        nvrhi::utils::ScopedMarker marker(commandList, \"Ray Tracing Pass\");\n\n        LightingConstants constants = {};\n        constants.ambientColor = float4(0.05f);\n\n        light->FillLightConstants(constants.light);\n        commandList->writeBuffer(m_lightingConstantsBuffer, &constants, sizeof(constants));\n\n        RenderParams params = m_params;\n        // Override settings\n        params.colorMode = m_showMicroTriangles ? ColorMode::COLOR_BY_MICROTRI_ID : m_colorMode;\n        params.shadingMode = m_showMicroTriangles ? ShadingMode::PRIMARY_RAYS : m_shadingMode;\n        commandList->writeBuffer(m_renderParamsBuffer, &params, sizeof(params));\n\n        // Get the appropriate pipeline and shader table for current permutation\n        RayTracingPermutation permutation(m_enableVertexNormals);\n        \n        auto GetRayTracingShaderTable = [this](const RayTracingPermutation& rtPermutation) -> nvrhi::rt::IShaderTable*\n            {\n                uint32_t index = rtPermutation.index();\n                if (!m_rayPipelines[index])\n                {\n                    // Create shader macros based on permutation\n                    std::vector<engine::ShaderMacro> macros;\n                    macros.push_back(engine::ShaderMacro(\"VERTEX_NORMALS\", rtPermutation.isVertexNormalsEnabled() ? \"1\" : \"0\"));\n\n                    nvrhi::ShaderLibraryHandle shaderLibrary =\n                        m_shaderFactory->CreateShaderLibrary(\"rtxmg_demo/rtxmg_demo_path_tracer.hlsl\", &macros);\n\n                    if (!shaderLibrary)\n                        return nullptr;\n\n                    nvrhi::rt::PipelineDesc pipelineDesc;\n                    pipelineDesc.globalBindingLayouts = { m_bindingLayout, m_bindlessLayout };\n                    pipelineDesc.shaders =\n                    {\n                        {\"\", shaderLibrary->getShader(\"RayGen\", nvrhi::ShaderType::RayGeneration), nullptr},\n                        {\"\", shaderLibrary->getShader(\"Miss\", nvrhi::ShaderType::Miss), nullptr},\n                        {\"\", shaderLibrary->getShader(\"ShadowMiss\", nvrhi::ShaderType::Miss), nullptr}\n                    };\n\n                    pipelineDesc.hitGroups =\n                    { {\n                        \"HitGroup\",\n                        shaderLibrary->getShader(\"ClosestHit\", nvrhi::ShaderType::ClosestHit),\n                        nullptr, // anyHitShader\n                        nullptr, // intersectionShader\n                        nullptr, // bindingLayout\n                        false    // isProceduralPrimitive\n                    },\n                    {\n                        \"ShadowHitGroup\",\n                        nullptr, // closestHitShader\n                        nullptr, // anyHitShader\n                        nullptr, // intersectionShader\n                        nullptr, // bindingLayout\n                        false    // isProceduralPrimitive\n                    } };\n\n                    pipelineDesc.maxPayloadSize = sizeof(RayPayload);\n                    pipelineDesc.maxRecursionDepth = m_params.ptMaxBounces + 1;\n                    pipelineDesc.hlslExtensionsUAV = int32_t(RTXMG_NVAPI_SHADER_EXT_SLOT);\n\n                    m_rayPipelines[index] = GetDevice()->createRayTracingPipeline(pipelineDesc);\n\n                    if (!m_rayPipelines[index])\n                        return nullptr;\n\n                    m_shaderTables[index] = m_rayPipelines[index]->createShaderTable();\n\n                    if (!m_shaderTables[index])\n                        return nullptr;\n\n                    m_shaderTables[index]->setRayGenerationShader(\"RayGen\");\n                    m_shaderTables[index]->addHitGroup(\"HitGroup\");\n                    m_shaderTables[index]->addHitGroup(\"ShadowHitGroup\");\n                    m_shaderTables[index]->addMissShader(\"Miss\");\n                    m_shaderTables[index]->addMissShader(\"ShadowMiss\");\n                }\n                return m_shaderTables[index].Get();\n            };\n\n        nvrhi::rt::IShaderTable *shaderTable = GetRayTracingShaderTable(permutation);\n        \n        nvrhi::rt::State state;\n        state.shaderTable = shaderTable;\n        state.bindings = { m_bindingSet, m_descriptorTable->GetDescriptorTable() };\n        commandList->setRayTracingState(state);\n\n        nvrhi::rt::DispatchRaysArguments args;\n        args.width = m_renderSize.x;\n        args.height = m_renderSize.y;\n\n        stats::frameSamplers.gpuRenderTime.Start(commandList);\n        commandList->dispatchRays(args);\n        stats::frameSamplers.gpuRenderTime.Stop();\n    }\n\n    // Motion vectors\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"Motion Vectors\");\n        ScopedGPUTimer timer(stats::frameSamplers.computeMotionVectorsTimer, commandList);\n        ComputeMotionVectors(commandList);\n    }\n\n    if (m_displayZBuffer)\n    {\n        m_zbuffer->Display(m_outputTextures[uint32_t(OutputTexture::Accumulation)], commandList);\n    }\n\n    ++m_params.subFrameIndex;\n}\n\nvoid RTXMGRenderer::BlitFramebuffer(nvrhi::ICommandList* commandList, nvrhi::IFramebuffer* framebuffer)\n{\n    nvrhi::utils::ScopedMarker marker(commandList, \"Blit\");\n\n    BlitParams blitParams;\n    blitParams.m_blitDecodeMode = BlitDecodeMode::None;\n    blitParams.m_tonemapOperator = m_showMicroTriangles ? TonemapOperator::Linear : m_tonemapOperator;\n    blitParams.m_exposure = m_showMicroTriangles ? 1.0f : m_exposure;\n    blitParams.m_zNear = m_camera.GetZNear();\n    blitParams.m_zFar = m_camera.GetZFar();\n    blitParams.m_separator = m_params.denoiserMode != DenoiserMode::None ? m_denoiserSeparator : 1.0f;\n\n    nvrhi::ITexture* outputTexture = nullptr;\n\n    nvrhi::ITexture* denoisedOutput = m_outputTextures[uint32_t(OutputTexture::DlssOutputColor)];\n\n    Output outputIndex = m_outputIndex;\n    switch (outputIndex)\n    {\n    case Output::DlssOutputColor:\n    case Output::Accumulation:\n    case Output::Albedo:\n    case Output::Specular:\n    default:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::None;\n        break;\n    case Output::Depth:\n    case Output::SpecularHitT:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::Depth;\n        break;\n    case Output::Normals:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::Normals;\n        break;\n    case Output::Roughness:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::SingleChannel;\n        break;\n    case Output::MotionVectors:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::MotionVectors;\n        break;\n    case Output::InstanceId:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::InstanceId;\n        break;\n    case Output::SurfaceIndex:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::SurfaceIndex;\n        break;\n    case Output::SurfaceUv:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::SurfaceUv;\n        break;\n    case Output::Texcoord:\n        blitParams.m_blitDecodeMode = BlitDecodeMode::Texcoord;\n        break;\n    case Output::HiZ:\n        outputIndex = Output::Accumulation;\n        blitParams.m_blitDecodeMode = BlitDecodeMode::Depth;\n        break;\n    }\n\n    OutputTexture outputTextureIndex = uint32_t(outputIndex) < uint32_t(OutputTexture::Count) ?\n        OutputTexture(uint32_t(outputIndex)) :\n        OutputTexture::Accumulation;\n\n    outputTexture = m_outputTextures[uint32_t(outputTextureIndex)];\n    \n    commandList->writeBuffer(m_blitParamsBuffer, &blitParams, sizeof(blitParams));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_blitParamsBuffer))\n        .addItem(nvrhi::BindingSetItem::Texture_UAV(0, m_displayTexture))\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(0, outputTexture))\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(1, denoisedOutput))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(3, m_hitResultBuffer))\n        .addItem(nvrhi::BindingSetItem::Sampler(0, m_commonPasses->m_LinearClampSampler));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(GetDevice(), nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_blitBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set for blit\");\n    }\n\n    if (!m_blitPipeline)\n    {\n        nvrhi::ShaderHandle shader = m_shaderFactory->CreateShader(\"rtxmg_demo/blit.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n\n        auto pipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_blitBL);\n\n        m_blitPipeline = GetDevice()->createComputePipeline(pipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_blitPipeline)\n        .addBindingSet(bindingSet);\n    commandList->setComputeState(state);\n    const int blockSize = 32;\n\n    commandList->dispatch(div_ceil(m_displayTexture->getDesc().width, blockSize), div_ceil(m_displayTexture->getDesc().height, blockSize));\n\n    donut::engine::BlitParameters params =\n    {\n        .targetFramebuffer = framebuffer,\n        .sourceTexture = m_displayTexture,\n    };\n    GetCommonPasses()->BlitTexture(commandList, params, m_bindingCache.get());\n}\n\nvoid RTXMGRenderer::ResetSubframes() \n{ \n    if (m_params.enableTimeView != 0 || m_params.denoiserMode == DenoiserMode::None)\n    {\n        // The denoiser really doesn't like it when its noise is related between frames.\n        // Since we don't feed the accumulation buffer to the denoiser, it won't matter\n        // if we reset the subframe index or not.\n        m_params.subFrameIndex = 0;\n    }\n}\n\nstatic float VanDerCorput(size_t base, size_t index)\n{\n    float ret = 0.0f;\n    float denominator = float(base);\n    while (index > 0)\n    {\n        size_t multiplier = index % base;\n        ret += float(multiplier) / denominator;\n        index = index / base;\n        denominator *= base;\n    }\n    return ret;\n}\n\nvoid RTXMGRenderer::SetRenderCamera(Camera& camera, bool isCameraCut)\n{\n    donut::math::float3 eye = camera.GetEye();\n    donut::math::float3 direction = camera.GetDirection();\n    if (!all(isfinite(eye)) || !all(isfinite(direction)))\n    {\n        donut::log::error(\"Camera contains NaNs!: (%f %f %f) -> (%f %f %f)\", eye.x, eye.y, eye.z, direction.x, direction.y, direction.z);\n        return;\n    }\n\n    const uint32_t kBasePhaseCount = 8;\n    uint32_t phaseCount = uint32_t(std::ceilf(kBasePhaseCount * powf(float(m_displaySize.y) / float(m_renderSize.y), 2.0f)));\n    uint32_t index = (m_params.subFrameIndex % phaseCount) + 1;\n    m_params.jitter = float2{ VanDerCorput(2, index), VanDerCorput(3, index) } - 0.5f;\n\n    m_previousCamera = m_camera;\n    m_viewPrevious = m_view;\n\n    m_camera = camera;\n\n    float4x4 viewMatrixRowMajor = transpose(m_camera.GetViewMatrix());\n    float4x4 projectionRowMajor = transpose(m_camera.GetProjectionMatrix());\n\n    m_view.SetViewport(nvrhi::Viewport(float(m_renderSize.x), float(m_renderSize.y)));\n    m_view.SetMatrices(homogeneousToAffine(viewMatrixRowMajor), projectionRowMajor);\n    m_view.SetPixelOffset(m_params.jitter);\n    m_view.UpdateCache();\n    \n    if (isCameraCut)\n    {\n        m_previousCamera = m_camera;\n        m_viewPrevious = m_view;\n        ResetDenoiser();\n    }\n\n    // Assumes that prev camera has the same output resolution\n    auto MakeCameraConstants = [this](const Camera& camera)\n        {\n            return CameraConstants{\n                camera.GetViewMatrix(),\n                inverse(camera.GetViewMatrix()),\n                camera.GetProjectionMatrix(),\n                inverse(camera.GetProjectionMatrix()),\n                float2(float(m_renderSize.x), float(m_renderSize.y)),\n                float2(1.0f / m_renderSize.x, 1.0f / m_renderSize.y) };\n        };\n\n    auto const& [u, v, w] = camera.GetBasis();\n\n    if (any(eye != m_params.eye) ||\n         any(u != m_params.U) ||\n         any(v != m_params.V) ||\n         any(w != m_params.W))\n    {\n        ResetSubframes();\n    }\n\n    m_params.camera = MakeCameraConstants(m_camera);\n    m_params.prevCamera = MakeCameraConstants(m_previousCamera);\n    m_params.zFar = m_camera.GetZFar();\n    m_params.eye = eye;\n    m_params.U = u;\n    m_params.V = v;\n    m_params.W = w;\n    m_params.viewProjectionMatrix = camera.GetViewProjectionMatrix();\n}\n\nvoid RTXMGRenderer::SetRenderSize(int2 renderSize, int2 displaySize)\n{\n    bool renderSizeChanged = any(renderSize != m_renderSize);\n    bool displaySizeChanged = any(displaySize != m_displaySize);\n\n    m_renderSize = renderSize;\n    m_displaySize = displaySize;\n    \n    if (renderSizeChanged || displaySizeChanged)\n    {\n        m_zbuffer = nullptr;\n        m_bindingCache->Clear();\n        ResetSubframes();\n        ResetDenoiser();\n        GetDevice()->waitForIdle(); // About to free render buffers\n    }\n}\n\nvoid RTXMGRenderer::SetTimeView(bool timeView)\n{\n    m_params.enableTimeView = timeView;\n    ResetSubframes();\n    ResetDenoiser();\n}\n\nvoid RTXMGRenderer::SceneFinishedLoading(std::shared_ptr<RTXMGScene> scene)\n{\n    m_bindingSet = nullptr;\n    m_bindingCache->Clear();\n\n    m_scene = scene;\n    CreateAccelStructs();\n    ResetSubframes();\n    ResetDenoiser();\n}\n\nvoid RTXMGRenderer::CreateAccelStructs()\n{\n    nvrhi::rt::AccelStructDesc tlasDesc;\n    tlasDesc.isTopLevel = true;\n    tlasDesc.topLevelMaxInstances =\n        m_scene->GetSceneGraph()->GetMeshInstances().size();\n    m_topLevelAS = GetDevice()->createAccelStruct(tlasDesc);\n\n    m_clusterAccelBuilder = std::make_unique<ClusterAccelBuilder>(*m_shaderFactory, m_commonPasses, GetDescriptorTable()->GetDescriptorTable(), GetDevice());\n    m_sceneAccels = std::make_unique<ClusterAccels>();\n}\n\nvoid RTXMGRenderer::UpdateEnvMapTransform()\n{\n    affine3 Ry = rotation(float3(0, 1, 0), m_environmentMapAzimuth);\n    affine3 Rx = rotation(float3(1, 0, 0), m_environmentMapElevation);\n    m_params.envmapRotation = affineToHomogeneous(Ry * Rx);\n    m_params.envmapRotationInv = transpose(m_params.envmapRotation);\n}\n\nvoid RTXMGRenderer::SetEnvMap(const std::string& filePath, nvrhi::ICommandList* commandList)\n{\n    m_needsRebind = true;\n    m_needsEnvMapUpdate = true;\n\n    auto existing = m_textureCache->GetLoadedTexture(filePath);\n    if (existing)\n    {\n        m_envMap = existing;\n    }\n    else\n    {\n        m_envMap = m_textureCache->LoadTextureFromFile(filePath, false, m_commonPasses.get(), commandList);\n    }\n}\n\nvoid RTXMGRenderer::UpdateEnvMapSampling(nvrhi::ICommandList* commandList)\n{\n    // allocate importance sampling buffers\n\n    bool dumpIntermediateResults = false;\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"RTXMGScene::UpdateEnvMapSampling\");\n\n    const uint32_t inputWidth = m_envMap->texture->getDesc().width;\n    const uint32_t inputHeight = m_envMap->texture->getDesc().height;\n\n    // CDFs are two wider than their function, this gives room to store the integral in the final position\n    m_preprocessEnvMapResources.m_conditionalFunc.Create(inputWidth * inputHeight, \"Conditional Func\", GetDevice(), nvrhi::Format::R32_FLOAT);\n    m_preprocessEnvMapResources.m_conditionalCdf.Create((inputWidth + 2) * inputHeight, \"Conditional CDF\", GetDevice(), nvrhi::Format::R32_FLOAT);\n    m_preprocessEnvMapResources.m_marginalFunc.Create(inputHeight, \"Marginal Func\", GetDevice(), nvrhi::Format::R32_FLOAT);\n    m_preprocessEnvMapResources.m_marginalCdf.Create(inputHeight + 2, \"Marginal CDF\", GetDevice(), nvrhi::Format::R32_FLOAT);\n\n    m_preprocessEnvMapResources.m_sampler = m_commonPasses->m_LinearClampSampler;\n\n    PreprocessEnvMapParams params;\n    params.envMapHeight = inputHeight;\n    params.envMapWidth = inputWidth;\n\n    commandList->writeBuffer(m_preprocessEnvMapResources.m_params, &params, sizeof(params));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(0, m_envMap->texture))\n        .addItem(nvrhi::BindingSetItem::TypedBuffer_UAV(0, m_preprocessEnvMapResources.m_conditionalFunc))\n        .addItem(nvrhi::BindingSetItem::TypedBuffer_UAV(1, m_preprocessEnvMapResources.m_conditionalCdf))\n        .addItem(nvrhi::BindingSetItem::TypedBuffer_UAV(2, m_preprocessEnvMapResources.m_marginalFunc))\n        .addItem(nvrhi::BindingSetItem::TypedBuffer_UAV(3, m_preprocessEnvMapResources.m_marginalCdf))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_preprocessEnvMapResources.m_params))\n        .addItem(nvrhi::BindingSetItem::Sampler(0, m_preprocessEnvMapResources.m_sampler));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(GetDevice(), nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_preprocessEnvMapShaders.m_bindingLayout, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for preprocess envmap shaders\");\n    }\n\n    auto runShader = [&params, &dumpIntermediateResults, &bindingSet, &commandList, this](nvrhi::ComputePipelineHandle &computePipeline, \n        const char *shaderPath, const char *entryPointName, uint32_t x, uint32_t y)\n        {\n            if (!computePipeline)\n            {\n                auto shader = m_shaderFactory->CreateShader(shaderPath, entryPointName, nullptr, nvrhi::ShaderType::Compute);\n\n                auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n                    .setComputeShader(shader)\n                    .addBindingLayout(m_preprocessEnvMapShaders.m_bindingLayout);\n\n                computePipeline = GetDevice()->createComputePipeline(computePipelineDesc);\n            }\n\n            // dumping the results will close and reopen the command list, so we need to re-write\n            // the params buffer\n            if (dumpIntermediateResults)\n                commandList->writeBuffer(m_preprocessEnvMapResources.m_params, &params, sizeof(params));\n\n            auto state = nvrhi::ComputeState()\n                .setPipeline(computePipeline)\n                .addBindingSet(bindingSet);\n\n            commandList->setComputeState(state);\n            commandList->dispatch(x, y);\n        };\n\n    // step 1: convert input image to luminance, store in conditionalFunc\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"Compute Conditional Func\");\n        runShader(m_preprocessEnvMapShaders.m_computeConditionalPSO, \n            \"envmap/compute_conditional.hlsl\", \"main\", div_ceil(inputWidth, 16), div_ceil(inputHeight, 16));\n    }\n\n    if (dumpIntermediateResults)\n        WriteBufferToCSV(commandList, m_preprocessEnvMapResources.m_conditionalFunc, \"01_conditional_func.csv\", inputWidth, inputHeight);\n\n    // step 2: compute the conditional CDF using a prefix scan\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"prefix scan conditional CDF\");\n        m_scanSystem.PrefixScan(m_preprocessEnvMapResources.m_conditionalFunc, m_preprocessEnvMapResources.m_conditionalCdf, inputWidth, inputHeight, commandList);\n    }\n\n    if (dumpIntermediateResults)\n        WriteBufferToCSV(commandList, m_preprocessEnvMapResources.m_conditionalCdf, \"02_conditional_cdf.csv\", inputWidth + 2, inputHeight);\n    else\n        nvrhi::utils::BufferUavBarrier(commandList, m_preprocessEnvMapResources.m_conditionalCdf);\n\n    // step 3: Copy the CDF integrals to the marginal func.\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"Compute  Marginal Func\");\n        runShader(m_preprocessEnvMapShaders.m_computeMarginalPSO,\n            \"envmap/compute_marginal.hlsl\", \"main\", 1, div_ceil(inputHeight, 32));\n    }\n\n    if (dumpIntermediateResults)\n        WriteBufferToCSV(commandList, m_preprocessEnvMapResources.m_marginalFunc, \"03_marginal_func.csv\", inputHeight, 1);\n\n    // step 4: Compute the marginal CDF using a prefix scan\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"prefix scan marginal CDF\");\n        m_scanSystem.PrefixScan(m_preprocessEnvMapResources.m_marginalFunc, m_preprocessEnvMapResources.m_marginalCdf, inputHeight, 1, commandList);\n    }\n\n    if (dumpIntermediateResults)\n        WriteBufferToCSV(commandList, m_preprocessEnvMapResources.m_marginalCdf, \"04_marginal_cdf.csv\", inputHeight + 2, 1);\n}\n\nvoid RTXMGRenderer::FillInstanceDescs(nvrhi::ICommandList* commandList, nvrhi::IBuffer* outInstanceDescs, nvrhi::IBuffer* blasAddresses, uint32_t numInstances)\n{\n    assert(outInstanceDescs->getDesc().byteSize / sizeof(nvrhi::rt::IndirectInstanceDesc) >= numInstances);\n    assert(blasAddresses->getDesc().byteSize / sizeof(nvrhi::GpuVirtualAddress) >= numInstances);\n\n    FillInstanceDescsParams params = {};\n    params.numInstances = numInstances;\n    commandList->writeBuffer(m_fillInstanceDescsParams, &params, sizeof(params));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, blasAddresses))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, outInstanceDescs))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_fillInstanceDescsParams));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(GetDevice(), nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_fillInstanceDescsBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for fill_instance_descs.hlsl\");\n    }\n\n    if (!m_fillInstanceDescsPSO)\n    {\n        nvrhi::ShaderHandle shader = m_shaderFactory->CreateShader(\"cluster_builder/fill_instance_descs.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_fillInstanceDescsBL);\n\n        m_fillInstanceDescsPSO = GetDevice()->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_fillInstanceDescsPSO)\n        .addBindingSet(bindingSet);\n    commandList->setComputeState(state);\n    commandList->dispatch(div_ceil(numInstances, kFillInstanceDescsThreads), 1, 1);\n}\n\nvoid RTXMGRenderer::UpdateAccelerationStructures(const TessellatorConfig &tessConfig,\n    ClusterStatistics& buildStats,\n    uint32_t frameIndex,\n    nvrhi::ICommandList* commandList)\n{\n    m_enableVertexNormals = tessConfig.enableVertexNormals;\n\n    if (!m_scene->GetSubdMeshes().empty())\n    {\n        m_clusterAccelBuilder->BuildAccel(*m_scene, tessConfig, *m_sceneAccels, buildStats, frameIndex, commandList);\n        m_needsRebind = true;\n    }\n    \n    m_scene->Refresh(commandList, frameIndex);\n\n    std::vector<nvrhi::rt::InstanceDesc> instances;\n    std::vector<SubdInstance> subdInstances;\n\n    for (const auto& instance : m_scene->GetSubdMeshInstances())\n    {\n        auto donutInstance = instance.meshInstance;\n        auto& mesh = m_scene->GetSubdMeshes()[instance.meshID];\n\n        unsigned int writeDepthFlag = !(mesh->HasAnimation());\n\n        nvrhi::rt::InstanceDesc instanceDesc;\n        instanceDesc.blasDeviceAddress = 0; // will get filled out later by fill instance desc indirect arg\n        instanceDesc.instanceMask = (writeDepthFlag << 1) | 0x1;\n        instanceDesc.instanceID = donutInstance->GetInstanceIndex();\n        instanceDesc.instanceContributionToHitGroupIndex = 0;\n\n        auto node = donutInstance->GetNode();\n        assert(node);\n        dm::affineToColumnMajor(node->GetLocalToWorldTransformFloat(),\n            instanceDesc.transform);\n\n        instances.push_back(instanceDesc);\n\n        auto getDescriptorHeapIndex = [](const DescriptorHandle& descriptor) -> uint32_t\n            {\n                return descriptor.IsValid() ? uint32_t(descriptor.GetIndexInHeap()) : kInvalidBindlessIndex;\n            };\n\n        SubdInstance subdInstance;\n        subdInstance.plansBindlessIndex = getDescriptorHeapIndex(mesh->GetTopologyMap()->plansDescriptor);\n        subdInstance.stencilMatrixBindlessIndex = getDescriptorHeapIndex(mesh->GetTopologyMap()->stencilMatrixDescriptor);\n        subdInstance.subpatchTreesBindlessIndex = getDescriptorHeapIndex(mesh->GetTopologyMap()->subpatchTreesDescriptor);\n        subdInstance.patchPointIndicesBindlessIndex = getDescriptorHeapIndex(mesh->GetTopologyMap()->patchPointIndicesDescriptor);\n\n        subdInstance.vertexSurfaceDescriptorBindlessIndex = getDescriptorHeapIndex(mesh->m_vertexSurfaceDescriptorDescriptor);\n        subdInstance.vertexControlPointIndicesBindlessIndex = getDescriptorHeapIndex(mesh->m_vertexControlPointIndicesDescriptor);\n        subdInstance.positionsBindlessIndex = getDescriptorHeapIndex(mesh->m_positionsDescriptor);\n        subdInstance.positionsPrevBindlessIndex = getDescriptorHeapIndex(mesh->m_positionsPrevDescriptor);\n        subdInstance.surfaceToGeometryIndexBindlessIndex = getDescriptorHeapIndex(mesh->m_surfaceToGeometryIndexDescriptor);\n        subdInstance.topologyQualityBindlessIndex = getDescriptorHeapIndex(mesh->m_topologyQualityDescriptor);\n        \n        affineToColumnMajor(node->GetPrevLocalToWorldTransformFloat(), subdInstance.prevLocalToWorld);\n        affineToColumnMajor(inverse(node->GetLocalToWorldTransformFloat()), subdInstance.worldToLocal);\n        subdInstances.push_back(subdInstance);\n    }\n\n    uint32_t numInstances = uint32_t(instances.size());\n\n    if (!m_instanceDescs.GetBuffer() || m_instanceDescs.GetNumElements() != numInstances)\n    {\n        nvrhi::BufferDesc instanceDescsDesc =\n        {\n            .byteSize = numInstances * sizeof(instances[0]),\n            .structStride = sizeof(instances[0]),\n            .debugName = \"TLAS InstanceDescs\",\n            .canHaveUAVs = true,\n            .isAccelStructBuildInput = true,\n            .initialState = nvrhi::ResourceStates::AccelStructBuildInput,\n            .keepInitialState = true,\n        };\n\n        m_instanceDescs.Create(instanceDescsDesc, GetDevice());\n        m_subdInstancesBuffer.Create(numInstances, \"SubdInstances\", GetDevice());\n    }\n\n    if (m_subdInstancesBuffer.GetNumElements() > subdInstances.size())\n    {\n        // Clear existing elements with default entries\n        subdInstances.resize(m_subdInstancesBuffer.GetNumElements());\n    }\n\n    m_instanceDescs.Upload(instances, commandList);\n    m_subdInstancesBuffer.Upload(subdInstances, commandList);\n\n    // Patch the instance desc buffer with BLAS pointers\n    nvrhi::IBuffer* blasAddresses = m_sceneAccels->blasPtrsBuffer;\n    if (blasAddresses != nullptr)\n    {\n        FillInstanceDescs(commandList, m_instanceDescs, blasAddresses, numInstances);\n    }\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"TLAS Update\");\n    commandList->buildTopLevelAccelStructFromBuffer(m_topLevelAS, m_instanceDescs, 0u, numInstances);\n}\n\nvoid RTXMGRenderer::DumpPixelDebugBuffers(nvrhi::ICommandList* commandList)\n{\n#if ENABLE_SHADER_DEBUG\n    {\n        log::info(\"Raytracing Pixel Debug: %d, %d\", m_params.debugPixel.x, m_params.debugPixel.y);\n        auto debugOutput = m_pixelDebugBuffer.Download(commandList);\n        uint numElements = debugOutput.front().payloadType;\n        vectorlog::Log(debugOutput, ShaderDebugElement::OutputLambda, vectorlog::FormatOptions{ .wrap = false, .header = false, .elementIndex = false, .startIndex = 1, .count = numElements });\n    }\n    \n    {\n        log::info(\"Motion Vector Pixel Debug: %d, %d\", m_params.debugPixel.x, m_params.debugPixel.y);\n        auto debugOutput = m_motionVectorsPixelDebugBuffer.Download(commandList);\n        uint numElements = debugOutput.front().payloadType;\n        vectorlog::Log(debugOutput, ShaderDebugElement::OutputLambda, vectorlog::FormatOptions{ .wrap = false, .header = false, .elementIndex = false, .startIndex = 1, .count = numElements });\n    }\n#endif\n}\n"
  },
  {
    "path": "demo/rtxmg_renderer.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <donut/core/math/math.h>\n#include <donut/engine/BindingCache.h>\n#include <donut/engine/DescriptorTableManager.h>\n#include <donut/engine/SceneGraph.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/engine/TextureCache.h>\n#include <donut/engine/View.h>\n#include <nvrhi/nvrhi.h>\n\n#include \"render_params.h\"\n#include \"blit_params.h\"\n#include \"motion_vectors_params.h\"\n#include \"render_targets.h\"\n\n#include \"rtxmg/cluster_builder/cluster_accel_builder.h\"\n#include \"envmap/preprocess_envmap.h\"\n#include \"envmap/scan_system.h\"\n#include \"rtxmg/hiz/zbuffer.h\"\n#include \"rtxmg/scene/camera.h\"\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/utils/shader_debug.h\"\n\nusing namespace donut::engine;\nusing namespace donut::math;\n\ntypedef vector<unsigned short, 2> uint16_t2;\ntypedef vector<unsigned short, 4> uint16_t4;\n\nclass RTXMGScene;\nclass Camera;\n\n#define cStablePlaneCount (3u)\n\nclass RTXMGRenderer\n{\npublic:\n    struct Options\n    {\n        RenderParams& params;\n        nvrhi::IDevice* device;\n    };\n\n    enum class OutputTexture : uint32_t\n    {\n        DlssOutputColor,\n        Accumulation,\n        Depth,\n        Normals,\n        Albedo,\n        Specular,\n        SpecularHitT,\n        Roughness,\n        MotionVectors,\n#if ENABLE_DUMP_FLOAT\n        Debug1,\n        Debug2,\n        Debug3,\n        Debug4,\n#endif\n        Count\n    };\n\n    enum class Output : uint32_t\n    {\n        DlssOutputColor,\n        Accumulation,\n        Depth,\n        Normals,\n        Albedo,\n        Specular,\n        SpecularHitT,\n        Roughness,\n        MotionVectors,\n#if ENABLE_DUMP_FLOAT\n        Debug1,\n        Debug2,\n        Debug3,\n        Debug4,\n#endif\n        OutputTextureCount,\n        InstanceId = OutputTextureCount,\n        SurfaceIndex,\n        SurfaceUv,\n        Texcoord,\n        HiZ,\n        Count\n    };\n\n    // Output is a superset of OutputTexture since we need to be able to visualize both textures/buffers\n    static_assert(uint32_t(Output::OutputTextureCount) == uint32_t(OutputTexture::Count));\n\n    RTXMGRenderer(Options const& opts);\n    ~RTXMGRenderer();\n\n    void UpdateAccelerationStructures(const TessellatorConfig& tessConfig,\n        ClusterStatistics& buildStats,\n        uint32_t frameIndex,\n        nvrhi::ICommandList* commandList);\n    void ReloadShaders();\n\n    void CreateOutputs(nvrhi::ICommandList* commandList);\n    void Launch(nvrhi::ICommandList* commandList, uint32_t frameIndex,\n        std::shared_ptr<Light> light);\n    void BlitFramebuffer(nvrhi::ICommandList* commandList, nvrhi::IFramebuffer* framebuffer);\n    void DlssUpscale(nvrhi::ICommandList* commandList, uint32_t frameIndex);\n\n    void ResetSubframes();\n    void ResetDenoiser() { m_resetDenoiser = true; }\n    void SetRenderCamera(Camera& camera, bool isCameraCut);\n\n    void SetShadingMode(ShadingMode shadingMode)\n    {\n        m_shadingMode = shadingMode;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n    ShadingMode GetShadingMode() const { return m_shadingMode; }\n    ShadingMode GetEffectiveShadingMode() const { return m_showMicroTriangles ? ShadingMode::PRIMARY_RAYS : m_shadingMode; }\n\n    void SetColorMode(ColorMode colorMode)\n    {\n        m_colorMode = colorMode;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n    ColorMode GetColorMode() const { return m_colorMode; }\n\n    void SetOutputIndex(Output output)\n    {\n        m_outputIndex = output;\n    }\n    Output GetOutputIndex() const { return m_outputIndex; }\n\n    const char* GetOutputLabel(Output output) const\n    {\n        uint32_t outputIndex = uint32_t(output);\n        if (outputIndex < m_outputTextures.size())\n        {\n            auto& textureHandle = m_outputTextures[outputIndex];\n            return textureHandle.Get() ? textureHandle->getDesc().debugName.c_str() : \"\";\n        }\n        else\n        {\n            const char* label = \"\";\n            switch (output)\n            {\n            case Output::InstanceId:\n                label = \"Instance Id\";\n                break;\n            case Output::SurfaceIndex:\n                label = \"Surface Index\";\n                break;\n            case Output::SurfaceUv:\n                label = \"Surface UV\";\n                break;\n            case Output::Texcoord:\n                label = \"Texcoord UV\";\n                break;\n            case Output::HiZ:\n                label = \"HiZ Buffer\";\n                break;\n            default:\n                assert(false);\n                label = \"Unknown\";\n                break;\n            }\n            return label;\n        }\n    }\n\n    nvrhi::TextureHandle GetOutputTexture(Output output) const\n    {\n        return m_outputTextures[uint32_t(output)];\n    }\n\n    void SetSPP(int spp)\n    {\n        m_params.spp = spp;\n        ResetSubframes();\n    }\n    int GetSPP() const { return m_params.spp; }\n\n    void SetMissColor(float3 missColor)\n    {\n        m_params.missColor = missColor;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n    float3 GetMissColor() const { return m_params.missColor; }\n\n    void SetWireframe(bool wireframe)\n    {\n        m_params.enableWireframe = wireframe;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n    bool GetWireframe() const { return m_params.enableWireframe; }\n\n    void SetShowMicroTriangles(bool showMicroTriangles) { m_showMicroTriangles = showMicroTriangles; }\n    bool GetShowMicroTriangles() const { return m_showMicroTriangles; }\n\n    void SetWireframeThickness(float thickness)\n    {\n        m_params.wireframeThickness = thickness;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n    float GetWireframeThickness() const { return m_params.wireframeThickness; }\n\n    int GetPTMaxBounces() const { return m_params.ptMaxBounces; }\n    void SetPTMaxBounces(int maxBounces)\n    {\n        m_params.ptMaxBounces = maxBounces;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n\n    float GetFireflyMaxIntensity() const { return m_params.fireflyMaxIntensity; }\n    void SetFireflyMaxIntensity(float fireflyFilterMaxIntensity)\n    {\n        m_params.fireflyMaxIntensity = fireflyFilterMaxIntensity;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n\n    float GetRoughnessOverride() const { return m_params.roughnessOverride; }\n    void  SetRoughnessOverride(float roughness)\n    {\n        m_params.roughnessOverride = roughness;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n\n    float GetExposure() const\n    {\n        return m_exposure;\n    }\n    void SetExposure(float exposure)\n    {\n        m_exposure = exposure;\n        ResetSubframes();\n    }\n\n    TonemapOperator GetTonemapOperator() const { return m_tonemapOperator; }\n    void SetTonemapOperator(TonemapOperator tonemapOperator)\n    {\n        m_tonemapOperator = tonemapOperator;\n    }\n\n    MvecDisplacement GetMVecDisplacement() const { return m_mvecDisplacement; }\n    void SetMvecDisplacement(MvecDisplacement mvecDisplacement)\n    {\n        m_mvecDisplacement = mvecDisplacement;\n    }\n\n    float GetEnvMapAzimuth() const { return m_environmentMapAzimuth; }\n    void  SetEnvMapAzimuth(float azimuth)\n    {\n        m_environmentMapAzimuth = azimuth;\n        UpdateEnvMapTransform();\n        ResetSubframes();\n    }\n    float GetEnvMapElevation() const { return m_environmentMapElevation; }\n    void  SetEnvMapElevation(float elevation)\n    {\n        m_environmentMapElevation = elevation;\n        UpdateEnvMapTransform();\n        ResetSubframes();\n    }\n    float GetEnvMapIntensity() const { return m_params.envmapIntensity; }\n    void  SetEnvMapIntensity(float intensity)\n    {\n        m_params.envmapIntensity = intensity;\n        ResetSubframes();\n    }\n\n    float GetDenoiserSeparator() const { return m_denoiserSeparator; }\n    void  SetDenoiserSeparator(float separator)\n    {\n        m_denoiserSeparator = separator;\n    }\n\n    bool GetEnableEnvmapHeatmap() const { return m_params.enableEnvmapHeatmap; }\n    void  SetEnableEnvmapHeatmap(bool enableEnvmapHeatmap)\n    {\n        m_params.enableEnvmapHeatmap = enableEnvmapHeatmap;\n        ResetSubframes();\n        ResetDenoiser();\n    }\n\n    void SetTimeView(bool timeView);\n    bool GetTimeView() const { return m_params.enableTimeView; }\n\n    void SetDisplayZBuffer(bool displayZBuffer)\n    {\n        m_displayZBuffer = displayZBuffer;\n        m_outputIndex = displayZBuffer ? Output::HiZ : Output::Accumulation;\n    }\n    bool GetDisplayZBuffer() const { return m_displayZBuffer; }\n\n    int2& GetDebugPixel() { return m_params.debugPixel; }\n    \n    void SetDebugSurfaceIndex(int surfaceIndex)\n    {\n        m_params.debugSurfaceIndex = surfaceIndex;\n        ResetSubframes(); // Reset accumulation when debug surface changes\n    }\n    int GetDebugSurfaceIndex() const { return m_params.debugSurfaceIndex; }\n\n    void SceneFinishedLoading(std::shared_ptr<RTXMGScene> scene);\n\n    std::shared_ptr<TextureCache> GetTextureCache() const\n    {\n        return m_textureCache;\n    }\n\n    std::shared_ptr<DescriptorTableManager> GetDescriptorTable() const\n    {\n        return m_descriptorTable;\n    }\n\n    std::shared_ptr<ShaderFactory> GetShaderFactory() const\n    {\n        return m_shaderFactory;\n    }\n\n    std::shared_ptr<CommonRenderPasses> GetCommonPasses() const\n    {\n        return m_commonPasses;\n    }\n\n    void DumpPixelDebugBuffers(nvrhi::ICommandList* commandList);\n\n    void SetRenderSize(int2 renderSize, int2 displaySize);\n    ZBuffer* GetZBuffer() { return m_zbuffer.get(); }\n    const ZBuffer* GetZBuffer() const { return m_zbuffer.get(); }\n\n    nvrhi::rt::AccelStructHandle GetTopLevelAS() const { return m_topLevelAS; }\n    std::unique_ptr<ClusterAccels>& GetSceneAccels() { return m_sceneAccels; }\n    std::unique_ptr<ClusterAccelBuilder>& GetAccelBuilder() { return m_clusterAccelBuilder; }\n\n    void SetEnvMap(const std::string& filePath, nvrhi::ICommandList* commandList);\n    std::shared_ptr<LoadedTexture> GetEnvMap() const { return m_envMap; }\n    void ClearEnvMap() { m_envMap = nullptr; }\nprivate:\n\n    nvrhi::IDevice* GetDevice() const { return m_options.device; }\n\n    void FillInstanceDescs(nvrhi::ICommandList* commandList, nvrhi::IBuffer* outInstanceDescs, nvrhi::IBuffer* blasAddresses, uint32_t numInstances);\n\n    void CreateAccelStructs();\n\n    void UpdateEnvMapTransform();\n    void UpdateEnvMapSampling(nvrhi::ICommandList* commandList);\n\n    void ComputeMotionVectors(nvrhi::ICommandList* commandList);\nprivate:\n    \n\n    Options m_options;\n    RenderParams& m_params;\n    ShadingMode m_shadingMode = ShadingMode::PT;\n    ColorMode m_colorMode = ColorMode::BASE_COLOR;\n    TonemapOperator m_tonemapOperator = TonemapOperator::Aces;\n    float m_exposure = 1.0f;\n\n    PreprocessEnvMapShaders m_preprocessEnvMapShaders;\n    PreprocessEnvMapResources m_preprocessEnvMapResources;\n    std::shared_ptr<LoadedTexture> m_envMap;\n    ScanSystem m_scanSystem;\n    float m_environmentMapAzimuth = 0.f;\n    float m_environmentMapElevation = 0.f;\n\n    Camera m_camera;\n    Camera m_previousCamera;\n\n    int2 m_renderSize = int2(0, 0);\n    int2 m_displaySize = int2(0, 0);\n\n    // Ray tracing permutation support\n    class RayTracingPermutation\n    {\n    public:\n        enum BitIndices : uint32_t\n        {\n            VertexNormals = 0,\n            Count\n        };\n        static constexpr size_t kCount = 1u << BitIndices::Count;\n        uint32_t index() const { return m_bits; }\n\n        RayTracingPermutation(bool enableVertexNormals)\n            : m_bits((enableVertexNormals ? (1u << BitIndices::VertexNormals) : 0u))\n        {\n        }\n\n        bool isVertexNormalsEnabled() const { return (m_bits & (1u << BitIndices::VertexNormals)) != 0; }\n\n    private:\n        uint32_t m_bits = 0;\n    };\n\n    std::array<nvrhi::rt::PipelineHandle, RayTracingPermutation::kCount> m_rayPipelines = {};\n    std::array<nvrhi::rt::ShaderTableHandle, RayTracingPermutation::kCount> m_shaderTables = {};\n    nvrhi::BindingLayoutHandle m_bindingLayout;\n    nvrhi::BindingSetHandle m_bindingSet;\n    nvrhi::BindingLayoutHandle m_bindlessLayout;\n\n    nvrhi::BufferHandle m_dummyBuffer;\n\n    nvrhi::ComputePipelineHandle m_blitPipeline;\n    nvrhi::BindingLayoutHandle m_blitBL;\n    nvrhi::BufferHandle m_blitParamsBuffer;\n\n    std::unique_ptr<ClusterAccelBuilder> m_clusterAccelBuilder;\n    RTXMGBuffer<nvrhi::rt::InstanceDesc> m_instanceDescs;\n    nvrhi::rt::AccelStructHandle m_topLevelAS;\n    std::unique_ptr<ClusterAccels> m_sceneAccels;\n\n    nvrhi::BufferHandle m_fillInstanceDescsParams;\n    nvrhi::BindingLayoutHandle m_fillInstanceDescsBL;\n    nvrhi::ComputePipelineHandle m_fillInstanceDescsPSO;\n\n    nvrhi::BufferHandle m_lightingConstantsBuffer;\n    nvrhi::BufferHandle m_renderParamsBuffer;\n\n    std::shared_ptr<DescriptorTableManager> m_descriptorTable;\n\n    // Render Textures (RenderSize)\n    std::array<nvrhi::TextureHandle, size_t(OutputTexture::Count)> m_outputTextures;\n    Output m_outputIndex = Output::Accumulation;\n    nvrhi::BufferHandle m_hitResultBuffer;\n\n    // Display Textures (DisplayRes)\n    nvrhi::TextureHandle m_dlssOutputColorTexture; // Upscaled color output.\n    nvrhi::TextureHandle m_displayTexture; // Can be eliminated when blit is converted to PS\n\n    float m_denoiserSeparator = 0.0f;\n    bool m_resetDenoiser = false;\n    bool m_showMicroTriangles = false;\n\n    // Motion Vectors\n    MvecDisplacement m_mvecDisplacement = MvecDisplacement::FromSubdEval;\n    RTXMGBuffer<SubdInstance> m_subdInstancesBuffer;\n    nvrhi::BindingLayoutHandle m_motionVectorsBL;\n    nvrhi::ComputePipelineHandle m_motionVectorsPSO[size_t(MvecDisplacement::Count)];\n\n    // Debug Buffers\n#if ENABLE_SHADER_DEBUG\n    RTXMGBuffer<ShaderDebugElement> m_pixelDebugBuffer;\n    RTXMGBuffer<ShaderDebugElement> m_motionVectorsPixelDebugBuffer;\n#endif\n    nvrhi::BufferHandle m_timeViewBuffer;\n\n    std::unique_ptr<BindingCache> m_bindingCache;\n    std::shared_ptr<TextureCache> m_textureCache;\n    std::shared_ptr<RTXMGScene> m_scene;\n\n\n\n    std::shared_ptr<ShaderFactory> m_shaderFactory;\n    std::shared_ptr<CommonRenderPasses> m_commonPasses;\n\n    PlanarView m_view;\n    PlanarView m_viewPrevious;\n\n    bool m_needsRebind = true;\n    bool m_needsEnvMapUpdate = false;\n    bool m_displayZBuffer = false;\n    bool m_enableVertexNormals = false;\n    std::unique_ptr<ZBuffer> m_zbuffer;\n};\n"
  },
  {
    "path": "demo/shaders/blit.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#include \"blit_params.h\"\n\n#include \"gbuffer.h\"\n#include \"utils.hlsli\"\n#include <donut/shaders/binding_helpers.hlsli>\n\nConstantBuffer<BlitParams> g_Params : register(b0);\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<float4> g_Output : register(u0);\nTexture2D<float4> g_Input : register(t0);\nTexture2D<float4> g_InputSplitScreen : register(t1);\n\nStructuredBuffer<HitResult> g_HitResult : register(t3);\n\nSamplerState g_Sampler : register(s0);\n\ninline float3 expose(float3 input)\n{\n    return input * g_Params.m_exposure;\n}\n\ninline float3 computeSRGB(float3 c)\n{\n    // reference: https://www.color.org/chardata/rgb/srgb.xalter\n    float  invGamma = 1.0f / 2.4f;\n    float3 powed = float3(pow(c.x, invGamma), pow(c.y, invGamma), pow(c.z, invGamma));\n    return float3(\n        c.x < 0.0031308f ? 12.92f * c.x : 1.055f * powed.x - 0.055f,\n        c.y < 0.0031308f ? 12.92f * c.y : 1.055f * powed.y - 0.055f,\n        c.z < 0.0031308f ? 12.92f * c.z : 1.055f * powed.z - 0.055f);\n}\n\ninline float3 computeACES(float3 c)\n{\n    // reference : https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl\n    // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT\n    //float3x3 m1 = { 0.59719f, 0.35458f, 0.04823f,\n    //    0.07600f, 0.90834f, 0.01566f,\n    //    0.02840f, 0.13383f, 0.83777f };\n\n    float3x3 m1 = { 0.59719f, 0.07600f, 0.02840f,\n        0.35458f, 0.90834f, 0.13383f,\n        0.04823f, 0.01566f, 0.83777f };\n\n\n    // ODT_SAT => XYZ => D60_2_D65 => sRGB\n    //float3x3 m2 = { 1.60475f, -0.53108f, -0.07367f,\n    //    -0.10208f, 1.10813f, -0.00605f,\n    //    -0.00327f, -0.07276f, 1.07602f };\n\n    float3x3 m2 = { 1.60475f, -0.10208f, -0.00327f,\n        -0.53108f, 1.10813f, -0.07276f,\n        -0.07367f, -0.00605f, 1.07602f };\n\n    float3 v = mul(c, m1);\n    float3 a = v * (v + 0.0245786f) - 0.000090537f;\n    float3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;\n\n    c = clamp(mul(a / b, m2), 0.0f, 1.0f);\n    return c;\n}\n\ninline float3 uncharted2_partial(float3 x)\n{\n    float A = 0.15f; float B = 0.50f; float C = 0.10f;\n    float D = 0.20f; float E = 0.02f; float F = 0.30f;\n    return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;\n}\n\ninline float3 computeUncharted2(float3 c)\n{\n    // reference : https://64.github.io/tonemapping/\n    const float exposureBias = 2.0f;\n    float3 curr = uncharted2_partial(c * exposureBias);\n\n    const float3 W = { 11.2f, 11.2f, 11.2f };\n    float3 whiteScale = float3(1.f, 1.f, 1.f) / uncharted2_partial(W);\n\n    c = clamp(curr * whiteScale, 0.f, 1.f);\n\n    return c;\n}\n\n[numthreads(32, 32, 1)]\nvoid main(uint3 threadIdx : SV_DispatchThreadID)\n{\n    uint2 pixelPos = uint2(threadIdx.xy);\n    uint outputWidth, outputHeight;\n    uint inputWidth, inputHeight;\n\n    g_Input.GetDimensions(inputWidth, inputHeight);\n    g_Output.GetDimensions(outputWidth, outputHeight);\n\n    if (pixelPos.x >= outputWidth || pixelPos.y >= outputHeight)\n        return;\n\n    float2 uv = (float2(pixelPos) + 0.5) / float2(outputWidth, outputHeight);\n\n    uint2 inputPos = uint2(uv * float2(inputWidth, inputHeight));\n    uint hitResultIndex = inputPos.x + inputPos.y * inputWidth;\n\n    float3 input;\n\n    if (uv.x < g_Params.m_separator)\n    {\n        switch (g_Params.m_blitDecodeMode)\n        {\n        case BlitDecodeMode::SingleChannel:\n            input = g_Input.SampleLevel(g_Sampler, uv, 0).rrr;\n            break;\n        case BlitDecodeMode::Depth:\n            input = (g_Input[inputPos].rrr - g_Params.m_zNear) / (g_Params.m_zFar - g_Params.m_zNear);\n            break;\n        case BlitDecodeMode::MotionVectors:\n            // input = float3(abs(g_Input[inputPos].xy / float2(inputWidth, inputHeight)), 0.0f);\n            input = float3(abs(g_Input[inputPos].xy), 0.0f);\n            break;\n        case BlitDecodeMode::Normals:\n            input = float3(g_Input[inputPos].xyz * 0.5 + 0.5);\n            break;\n        case BlitDecodeMode::InstanceId:\n        {\n            uint instanceId = g_HitResult[hitResultIndex].instanceId;\n            input = instanceId != kInvalidInstanceId ? UintToColor(instanceId) : float3(0, 0, 0);\n            break;\n        }\n        case BlitDecodeMode::SurfaceIndex:\n        {\n            uint surfaceIndex = g_HitResult[hitResultIndex].surfaceIndex;\n            input = surfaceIndex != kInvalidSurfaceIndex ? UintToColor(surfaceIndex) : float3(0, 0, 0);\n            break;\n        }\n        case BlitDecodeMode::SurfaceUv:\n            input = float3(g_HitResult[hitResultIndex].surfaceUV, 0);\n            break;\n        case BlitDecodeMode::Texcoord:\n            input = float3(frac(g_HitResult[hitResultIndex].texcoord), 0);\n            break;\n        case BlitDecodeMode::None:\n            input = g_Input.SampleLevel(g_Sampler, uv, 0).xyz;\n            break;\n        }\n    }\n    else\n    {\n        input = g_InputSplitScreen.SampleLevel(g_Sampler, uv, 0).xyz;\n    }\n\n    input = expose(input);\n    \n    float3 output;\n    switch (g_Params.m_tonemapOperator)\n    {\n    case TonemapOperator::Srgb:\n        output = computeSRGB(input);\n        break;\n    case TonemapOperator::Aces:\n        output = computeSRGB(computeACES(input));\n        break;\n    case TonemapOperator::Hable:\n        output = computeSRGB(computeUncharted2(input));\n        break;\n    default:\n        output = input;\n        break;\n    }\n\n    float verticalLine = saturate(1.0 - abs(uv.x - g_Params.m_separator) * outputWidth / 3.5);\n    verticalLine = saturate(verticalLine / 0.5);\n    verticalLine *= float(g_Params.m_separator != 0.0);\n\n    const float3 nvColor = float3(118.0, 185.0, 0.0) / 255.0;\n    output = lerp(output, nvColor * verticalLine, verticalLine);\n\n    g_Output[pixelPos] = float4(output, 1.f);\n}"
  },
  {
    "path": "demo/shaders/brdf.hlsli",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef RTXMG_BRDF_HLSLI // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RTXMG_BRDF_HLSLI\n\n#include <donut/shaders/lighting.hlsli>\n#include \"utils.hlsli\"\n\n#define MIN_DIELECTRICS_F0     0.04f\n#define INV_MIN_DIELECTRICS_F0 (1.f / MIN_DIELECTRICS_F0);\n\n// Attenuates F90 for very low F0 values\n// Source: \"An efficient and Physically Plausible Real-Time Shading Model\" in ShaderX7 by Schuler\n// Also see section \"Overbright highlights\" in Hoffman's 2010 \"Crafting Physically Motivated Shading Models for Game Development\" for discussion\n// IMPORTANT: Note that when F0 is calculated using metalness, it's value is never less than MIN_DIELECTRICS_F0, and therefore,\n// this adjustment has no effect. To be effective, F0 must be authored separately, or calculated in different way. See main text for discussion.\nfloat ShadowedF90(float3 F0)\n{\n    // This scalar value is somewhat arbitrary, Schuler used 60 in his article. In here, we derive it from MIN_DIELECTRICS_F0 so\n    // that it takes effect for any reflectance lower than least reflective dielectrics\n    //const float t = 60.0f;\n    const float t = INV_MIN_DIELECTRICS_F0;\n    return min(1.0f, t * Luminance(F0));\n}\n\n\ntemplate <typename T>\ninline T Sqr(T v) {\n    return v * v;\n}\n\ntemplate <typename T>\ninline float LengthSquared( T n )\n{\n    return dot( n, n );\n}\n\ninline float AbsDot( float3 v1, float3 v2 )\n{\n    return clamp(abs( dot( v1, v2 ) ), .00001f, 1.f);\n}\n\ninline float CosTheta( float3 w )\n{\n    return w.z;\n}\ninline float Cos2Theta( float3 w )\n{\n    return w.z * w.z;\n}\ninline float AbsCosTheta( float3 w )\n{\n    return clamp(abs( w.z ), .00001f, 1.f);\n}\ninline float Sin2Theta( float3 w )\n{\n    return max( 0.00001f, 1.0f - Cos2Theta( w ) );\n}\ninline float SinTheta( float3 w )\n{\n    return sqrt( Sin2Theta( w ) );\n}\ninline float TanTheta( float3 w )\n{\n    return SinTheta( w ) / CosTheta( w );\n}\ninline float Tan2Theta( float3 w )\n{\n    return Sin2Theta( w ) / Cos2Theta( w );\n}\ninline float CosPhi( float3 w )\n{\n    const float sinTheta = SinTheta( w );\n    return ( sinTheta == 0.0f ) ? 1.0f : clamp( w.x / sinTheta, -1.0f, 1.0f );\n}\ninline float SinPhi( float3 w )\n{\n    const float sinTheta = SinTheta( w );\n    return ( sinTheta == 0.0f ) ? 0.0f : clamp( w.y / sinTheta, -1.0f, 1.0f );\n}\ninline float Cos2Phi( float3 w )\n{\n    return CosPhi( w ) * CosPhi( w );\n}\ninline float Sin2Phi( float3 w )\n{\n    return SinPhi( w ) * SinPhi( w );\n}\ninline float CosDPhi( float3 wa, float3 wb )\n{\n    return clamp( ( wa.x * wb.x + wa.y * wb.y ) / sqrt( ( wa.x * wa.x + wa.y * wa.y ) * ( wb.x * wb.x + wb.y * wb.y ) ),\n                  -1.0f, 1.0f );\n}\n\ninline bool SameHemisphere( float3 a, float3 b )\n{\n    return a.z * b.z > 0.00001f;\n}\n\ninline float3 Reflect( float3 wo, float3 n )\n{\n    return -1.0f * wo + 2.0f * dot( wo, n ) * n;\n}\n\n\n// Schlick's approximation to Fresnel term\n// f90 should be 1.0, except for the trick used by Schuler (see 'ShadowedF90' function)\ninline float3 EvalFresnelSchlick( float3 f0, float f90, float NdotS )\n{\n    return f0 + ( f90 - f0 ) * pow( 1.0f - NdotS, 5.0f );\n}\n\ninline float3 EvalFresnel( float3 f0, float f90, float NdotS )\n{\n    // Default is Schlick's approximation\n    return EvalFresnelSchlick( f0, f90, NdotS );\n}\n\ninline float3 BaseColorToDiffuseReflectance(float3 baseColor, float metalness)\n{\n    return baseColor * (1.0f - metalness);\n}\n\nfloat3 BaseColorToSpecularF0(float3 baseColor, float metalness)\n{\n    return lerp(float3(MIN_DIELECTRICS_F0, MIN_DIELECTRICS_F0, MIN_DIELECTRICS_F0), baseColor, metalness);\n}\n\nclass TrowbridgeReitzDistribution\n{\n    float alphaX, alphaY;\n    inline float D(float3 wm)\n    {\n        float tan2Theta = Tan2Theta(wm);\n        if (isinf(tan2Theta))\n            return 0.f;\n        float cos4Theta = Sqr(Cos2Theta(wm));\n        if (cos4Theta < 1e-16f)\n            return 0.f;\n        float e = tan2Theta * (Sqr(CosPhi(wm) / alphaX) + Sqr(SinPhi(wm) / alphaY));\n        return 1.f / (M_PIf * alphaX * alphaY * cos4Theta * Sqr(1.f + e));\n    }\n\n    bool EffectivelySmooth() { return max(alphaX, alphaY) < 1e-3f; }\n\n    float G1(float3 w) { return 1.f / (1.f + Lambda(w)); }\n \n    float Lambda( float3 w )\n    {\n        float tan2Theta = Tan2Theta( w );\n        if( isinf( tan2Theta ) )\n            return 0;\n        float alpha2 = Sqr( CosPhi( w ) * alphaX ) + Sqr( SinPhi( w ) * alphaY );\n        return ( sqrt( 1.f + alpha2 * tan2Theta ) - 1.f ) / 2.f;\n    }\n\n    float G(float3 wo, float3 wi) { return 1.f / (1.f + Lambda(wo) + Lambda(wi)); }\n    \n    float D(float3 w, float3 wm)\n    {\n        return G1(w) / AbsCosTheta(w) * D(wm) * AbsDot(w, wm);\n    }\n    \n\n    float Pdf(float3 w, float3 wm)\n    {\n        return D(w, wm);\n    }\n\n    float3 Sample_wh(float3 w, float2 u) {\n        // Transform w to hemispherical configuration\n        float3 wh = normalize(float3(alphaX * w.x, alphaY * w.y, w.z));\n        if (wh.z < 0.f)\n            wh = -wh;\n\n\t    float lensq = wh.x * wh.x + wh.y * wh.y;\n        // Find orthonormal basis for visible normal sampling\n        float3  T1    = lensq > 0.f ? float3( -wh.y, wh.x, 0.f ) * rsqrt( lensq ) : float3( 1.f, 0.f, 0.f );\n        float3  T2    = cross( wh, T1 );\n\n        float r   = sqrt( u.x );\n        float phi = TWO_PI * u.y;\n        float t1  = r * cos( phi );\n        float t2  = r * sin( phi );\n        float s   = 0.5f * ( 1.0f + wh.z );\n        t2        = ( 1.0f - s ) * sqrt( 1.0f - t1 * t1 ) + s * t2;\n        // Section 4.3: reprojection onto hemisphere\n        float3 nh = t1 * T1 + t2 * T2 + sqrt( max( 0.0f, 1.0f - t1 * t1 - t2 * t2 ) ) * wh;\n        return normalize( float3(alphaX * nh.x, alphaY * nh.y, max(0.00001f, nh.z)));\n    }\n\n\n    float RoughnessToAlpha(float roughness) { return Sqr(roughness); }\n\n    void Regularize() {\n        if (alphaX < 0.3f)\n            alphaX = clamp(2.f * alphaX, 0.1f, 0.3f);\n        if (alphaY < 0.3f)\n            alphaY = clamp(2.f * alphaY, 0.1f, 0.3f);\n    }\n};\n\nTrowbridgeReitzDistribution MakeTrowbridgeReitzDistribution(float ax, float ay)\n{\n    TrowbridgeReitzDistribution ret;\n    ret.alphaX = ax*ax;\n    ret.alphaY = ay*ay;\n    \n    if( !ret.EffectivelySmooth() )\n    {\n        // If one direction has some roughness, then the other can't\n        // have zero (or very low) roughness; the computation of |e| in\n        // D() blows up in that case.\n        ret.alphaX = max( ret.alphaX, 1e-4f );\n        ret.alphaY = max( ret.alphaY, 1e-4f );\n    }\n    return ret;\n}\n\nclass DiffuseReflection\n{\n    float3 m_albedo;\n    \n    float3 Eval( const float3 wo, const float3 wi ) \n    {\n        return !SameHemisphere( wo, wi ) ? 0.f : m_albedo * M_1_PIf;\n    }\n    float Pdf( const float3 wo, const float3 wi )\n    {\n        return SameHemisphere( wo, wi ) ? AbsCosTheta( wi ) * M_1_PIf : 0.0f;\n    }\n\n    float3 Sample_f( const float3 wo, out float3 wi, const float2 u, out float pdf )\n    {\n        // Cosine-sample the hemisphere, flipping the direction if necessary\n        // default left handed since we are in bxdf coordinate system\n        wi  = CosineSampleHemisphere( u );\n        pdf = Pdf( wo, wi );\n        return Eval( wo, wi );\n    }\n};\n\n\nDiffuseReflection MakeDiffuseReflection(float3 baseColor, float metalness)\n{\n    DiffuseReflection ret;\n    if (metalness >= 0.0)\n    {\n        ret.m_albedo = BaseColorToDiffuseReflectance(baseColor, metalness);\n    }\n    else\n    {\n        ret.m_albedo = baseColor;\n    }\n    return ret;\n}\n\nclass MicrofacetReflection\n{\n    float3 m_f0;\n    float m_f90;\n    TrowbridgeReitzDistribution m_distribution;\n    \n    float3 SchlickFresnel( float cosTheta )\n    {\n        return EvalFresnel( m_f0, m_f90, cosTheta );\n    }\n\n    float3 Eval( float3 wo, float3 wi )\n    {\n        if( !SameHemisphere( wo, wi ) )\n        {\n            return 0.f;\n        }\n        const float cosThetaO = AbsCosTheta( wo );\n        const float cosThetaI = AbsCosTheta( wi );\n        // Handle degenerate cases for microfacet reflection\n        if( cosThetaI <= 0.0f || cosThetaO <= 0.0f )\n        {\n            return 0.f;\n        }\n        float3 wh = normalize( wi + wo );\n        wh *= sign( wh.z );\n        const float3 F = SchlickFresnel( dot( wo, wh ) );\n        const float3 result =\n            F * m_distribution.D( wh ) * m_distribution.G( wo, wi ) / ( 4.0f * cosThetaI * cosThetaO );\n        return result;\n    }\n\n    float Pdf( float3 wo, float3 wi )\n    {\n        if( !SameHemisphere( wo, wi ) )\n        {\n            return 0.f;\n        }\n        const float cosThetaO = AbsCosTheta( wo );\n        const float cosThetaI = AbsCosTheta( wi );\n        // Handle degenerate cases for microfacet reflection\n        if( cosThetaI <= 0.0f || cosThetaO <= 0.0f )\n        {\n            return 0.f;\n        }\n        const float3 wh = normalize( wo + wi );\n        return m_distribution.Pdf( wo, wh ) / ( 4.0f * dot( wo, wh ) );\n    }\n\n    float3 Sample_f( float3 wo, out float3 wi, float2 u, out float pdf )\n    {\n        const float3 wh = m_distribution.Sample_wh( wo, u );\n        wi              = Reflect( wo, wh );\n        if( !SameHemisphere( wo, wi ) )\n        {\n            pdf = 0.f;\n            return 0.f;\n        }\n        float cosTheta_o = AbsCosTheta(wo), cosTheta_i = AbsCosTheta(wi);\n        if( cosTheta_i <= 0 || cosTheta_o <= 0 )\n        {\n            pdf = 0.f;\n            return 0.f;\n        }\n        pdf = m_distribution.Pdf( wo, wh ) / ( 4.0f * dot( wo, wh ) );\n        const float3 F = SchlickFresnel( dot( wo, wh ) );\n        const float D = m_distribution.D( wh );\n        const float G = m_distribution.G(wo, wi);\n        const float3 result =\n            F * D * G / ( 4.0f * cosTheta_i * cosTheta_o );\n        return result;\n    }\n};\n\nMicrofacetReflection MakeMicrofacetReflection(float3 baseColor, float3 f0, float metalness, float roughness)\n{\n    MicrofacetReflection ret;\n    ret.m_f0 = metalness >= 0.0 ? BaseColorToSpecularF0(baseColor, metalness) : f0;\n    ret.m_f90 = ShadowedF90(ret.m_f0);\n    ret.m_distribution = MakeTrowbridgeReitzDistribution(roughness, roughness);\n    return ret;\n}\n\nclass FresnelBlend\n{\n    DiffuseReflection m_diffuse;\n    MicrofacetReflection m_microfacet;\n    \n    float Pdf(float3 wo, float3 wi)\n    {\n        if( !SameHemisphere( wo, wi ) )\n            return 0.f;\n        const float3 wh       = normalize( wo + wi );\n        const float  F        = getSpecularProbability( dot( wi, wh ) );\n        const float  diffuse  = m_diffuse.Pdf( wo, wi );\n        const float  specular = m_microfacet.Pdf( wo, wi );\n        return ( 1.f - F ) * diffuse + F * specular;\n    }\n    \n    float getSpecularProbability(float cosTheta)\n    {                  \n        const float3 specularF0 = Luminance(m_microfacet.m_f0);\n        float diffuseReflectance = Luminance(m_diffuse.m_albedo);\n        float Fresnel = saturate(Luminance(EvalFresnel(specularF0, m_microfacet.m_f90, max(0.0f, cosTheta))));\n        // Approximate relative contribution of BRDFs using the Fresnel term\n        float specular = Fresnel;\n        float diffuse = diffuseReflectance * (1.0f - Fresnel);\n\n        // Return probability of selecting specular BRDF over diffuse BRDF\n        float p = (specular / max(0.0001f, (specular + diffuse)));\n\n        // Clamp probability to avoid undersampling of less prominent BRDF\n        return clamp(p, 0.1f, 0.9f);\n    }\n\n    float3 Eval( float3 wo, float3 wi )\n    {\n        if( !SameHemisphere( wo, wi ) )\n            return 0.f;\n        const float3 wh = normalize( wo + wi );\n        const float3 F        = m_microfacet.SchlickFresnel( dot( wi, wh ) );\n        const float3 diffuse = m_diffuse.Eval(wo,wi);\n        const float3 specular = m_microfacet.Eval(wo,wi);\n\n        return ( float3( 1.f, 1.f, 1.f ) - F ) * diffuse + specular;\n    }\n    \n    float3 Sample_f( float3 wo, out float3 wi, float2 u, out float pdf, inout uint32_t seed )\n    {\n        const float specularPdf = getSpecularProbability( CosTheta( wo ) );\n        float3      weight      = 1.f;\n        \n        if( Rnd( seed ) < specularPdf )\n        {\n            weight *= m_microfacet.Sample_f( wo, wi, u, pdf ) / specularPdf;\n        }\n        else\n        {\n            weight *= m_diffuse.Sample_f( wo, wi, u, pdf ) / ( 1.f - specularPdf );\n        }\n        return weight;\n    }\n    \n    float3 Sample_f_forceDiffuse(float3 wo, out float3 wi, float2 u, out float pdf, inout uint32_t seed)\n    {\n        const float specularPdf = getSpecularProbability(CosTheta(wo));\n        return m_diffuse.Sample_f(wo, wi, u, pdf) / (1.f - specularPdf);\n    }\n\n    float3 Sample_f_forceSpecular(float3 wo, out float3 wi, float2 u, out float pdf, inout uint32_t seed)\n    {\n        const float specularPdf = getSpecularProbability(CosTheta(wo));\n        return m_microfacet.Sample_f(wo, wi, u, pdf) / specularPdf;\n    }\n\n};\n\nFresnelBlend MakeFresnelBlend(float3 baseColor, float3 f0, float metalness, float roughness)\n{\n    FresnelBlend ret;\n    ret.m_diffuse = MakeDiffuseReflection(baseColor, metalness);\n    ret.m_microfacet = MakeMicrofacetReflection(baseColor, f0, metalness, roughness);\n    return ret;\n}\n\nfloat3 BRDFEval(MaterialSample material, float3 gN, float3 N, float3 V, float3 L, out float pdf )\n{\n    if( dot( gN, V ) < 0.f || dot( gN, L ) < 0.f )\n    {\n        pdf = 0.f;\n        return 0;\n    }\n    \n    FresnelBlend brdf = MakeFresnelBlend(material.baseColor, material.specularF0, material.metalness, material.roughness);\n\n    const Onb    onb = MakeOnb( N );\n    const float3 wo     = onb.ToLocal( V );\n    const float3 wi     = onb.ToLocal( L );\n    pdf                 = brdf.Pdf( wo, wi );\n    const float3 weight = brdf.Eval( wo, wi ) * AbsCosTheta( wi );\n    if (isnan(pdf) || any(isnan(weight)))\n    {\n        pdf = 0.f;\n        return 0;\n    }\n    return weight;\n}\n\n\nfloat3 BRDFSample(MaterialSample material, float3 gN, float3 N, float3 V, out float3 L, out float pdf, inout uint32_t seed)\n{\n    if (dot(gN, V) < 0.f || dot(gN, L) < 0.f)\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    pdf = 0.f;\n    \n    FresnelBlend brdf = MakeFresnelBlend(material.baseColor, material.specularF0, material.metalness, material.roughness);\n\n    const Onb onb = MakeOnb(N);\n    const float3 wo = onb.ToLocal(V);\n    \n    float3 wi = 0.f;\n    const float2 u = float2(Rnd(seed), Rnd(seed));\n    const float3 weight = brdf.Sample_f(wo, wi, u, pdf, seed) * AbsCosTheta(wi) / pdf;\n    if (isnan(pdf) || any(isnan(weight)))\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    L = onb.ToWorld(wi);\n    if (dot(L, gN) < 0.f)\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    return weight;\n}\n\n\nfloat3 BRDFSampleDiffuse(MaterialSample material, float3 gN, float3 N, float3 V, out float3 L, out float pdf, inout uint32_t seed)\n{\n    if (dot(gN, V) < 0.f || dot(gN, L) < 0.f)\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    pdf = 0.f;\n    \n    FresnelBlend brdf = MakeFresnelBlend(material.baseColor, material.specularF0, material.metalness, material.roughness);\n\n    const Onb onb = MakeOnb(N);\n    const float3 wo = onb.ToLocal(V);\n    \n    float3 wi = 0.f;\n    const float2 u = float2(Rnd(seed), Rnd(seed));\n    const float3 weight = brdf.Sample_f_forceDiffuse(wo, wi, u, pdf, seed) * AbsCosTheta(wi) / pdf;\n    if (isnan(pdf) || any(isnan(weight)))\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    L = onb.ToWorld(wi);\n    if (dot(L, gN) < 0.f)\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    return weight;\n}\n\n\nfloat3 BRDFSampleSpecular(MaterialSample material, float3 gN, float3 N, float3 V, out float3 L, out float pdf, inout uint32_t seed)\n{\n    if (dot(gN, V) < 0.f || dot(gN, L) < 0.f)\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    pdf = 0.f;\n    \n    FresnelBlend brdf = MakeFresnelBlend(material.baseColor, material.specularF0, material.metalness, material.roughness);\n\n    const Onb onb = MakeOnb(N);\n    const float3 wo = onb.ToLocal(V);\n    \n    float3 wi = 0.f;\n    const float2 u = float2(Rnd(seed), Rnd(seed));\n    const float3 weight = brdf.Sample_f_forceSpecular(wo, wi, u, pdf, seed) * AbsCosTheta(wi) / pdf;\n    if (isnan(pdf) || any(isnan(weight)))\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    L = onb.ToWorld(wi);\n    if (dot(L, gN) < 0.f)\n    {\n        pdf = 0.f;\n        return 0.f;\n    }\n    return weight;\n}\n\n// Return approximated preintegrated specular term which assumes that light is aligned with normal.\n// This provides the denoiser's expected input for the specular buffer.\n//\n// From [Ray Tracing Gems, Chapter 32]\n// const float3 vIBLSpecularTerm = EnvBRDFApprox(specularColor, roughness * roughness, dot(N, V));\nfloat3 BRDFEnvApprox(FresnelBlend brdf, float3 N, float3 V)\n{ \n    // specularColor is the reflectance from a direction parallel to the normal.\n    float3 specularColor = brdf.m_microfacet.m_f0;\n        \n    // alpha is the square of the linear roughness in the GGX model.\n    // This approximation only supports isotropic alpha, use max alpha.\n    float alpha = max(brdf.m_microfacet.m_distribution.alphaX, brdf.m_microfacet.m_distribution.alphaY);\n        \n    float NoV = abs(dot(N, V));\n    float4 X;\n    X.x = 1.f;\n    X.y = NoV;\n    X.z = NoV * NoV;\n    X.w = NoV * X.z;\n \n    float4 Y;\n    Y.x = 1.f;\n    Y.y = alpha;\n    Y.z = alpha * alpha;\n    Y.w = alpha * Y.z;\n \n    float2x2 M1 = float2x2(0.99044f, -1.28514f, 1.29678f, -0.755907f);\n    float3x3 M2 = float3x3(1.f, 2.92338f, 59.4188f, 20.3225f, -27.0302f, 222.592f, 121.563f, 626.13f, 316.627f);\n \n    float2x2 M3 = float2x2(0.0365463f, 3.32707, 9.0632f, -9.04756);\n    float3x3 M4 = float3x3(1.f, 3.59685f, -1.36772f, 9.04401f, -16.3174f, 9.22949f, 5.56589f, 19.7886f, -20.2123f);\n \n    float bias = dot(mul(M1, X.xy), Y.xy) * rcp(dot(mul(M2, X.xyw), Y.xyw));\n    float scale = dot(mul(M3, X.xy), Y.xy) * rcp(dot(mul(M4, X.xzw), Y.xyw));\n \n    return mad(specularColor, max(0, scale), max(0, bias));\n}\n\n#endif // RTXMG_BRDF_HLSLI"
  },
  {
    "path": "demo/shaders/color.hlsli",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef COLOR_HLSLI // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define COLOR_HLSLI \n\ninline\nuint32_t ToRGBe9995(float3 Power)\n{\n    // All three numbers are packed into a shared exponent: e, which is the largest exponent + 1\n    // Each element is assigned a signed mantissa in the range of [0,511]\n    // x_i = 2^e * m_i / 512\n    // Assumes no negatives\n    const float MaxPower = max(Power.x, max(Power.y, Power.z));\n    int UnbiasedExp2Int = (int)(asuint(MaxPower) >> 23) - 127 + 1;\n    UnbiasedExp2Int = clamp(UnbiasedExp2Int, -16, 15);\n    // Invert the exponent\n    const float InverseExp2AndScale = abs(asfloat(((uint32_t)(127 + 9 - UnbiasedExp2Int)) << 23));\n    const float3 pi = Power * InverseExp2AndScale;\n    uint3  Fractional = uint3((uint32_t)pi.x, (uint32_t)pi.y, (uint32_t)pi.z);\n\n    Fractional = min(Fractional, uint3(511, 511, 511));  // Prevents 512 which can occur due to round\n    uint32_t Packed = ((uint32_t)UnbiasedExp2Int) << 27;\n    Packed |= Fractional.z << 18;\n    Packed |= Fractional.y << 9;\n    Packed |= Fractional.x;\n    return Packed;\n}\n\ninline\nfloat3 FromRGBe9995(uint32_t i)\n{\n    // No negatives\n    uint3 Fractional = 0;\n    int PackedInt = (int)i;\n    Fractional.x = PackedInt & 511;\n    PackedInt >>= 9;\n    Fractional.y = PackedInt & 511;\n    PackedInt >>= 9;\n    Fractional.z = PackedInt & 511;\n    PackedInt >>= 9;\n    const int UnbiasedExp2Int = PackedInt;    // After all the integer shifts, exponent is in 2s complement\n    float SharedScaledPower = abs(asfloat((uint32_t)(UnbiasedExp2Int - 9 + 127) << 23));\n    return SharedScaledPower * float3((float)Fractional.x, (float)Fractional.y, (float)Fractional.z);\n}\n\n#endif // COLOR_HLSLI "
  },
  {
    "path": "demo/shaders/lerp_keyframes.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#include \"lerp_keyframes_params.h\"\n\nStructuredBuffer<float3> kf0 : register(t0);\nStructuredBuffer<float3> kf1 : register(t1);\n\nRWStructuredBuffer<float3> dst : register(u0);\n\nConstantBuffer<LerpKeyFramesParams> g_lerpParams: register(b0);\n\n[numthreads(32, 1, 1)]\nvoid main(uint3 threadIdx : SV_DispatchThreadID)\n{\n    const uint32_t vertexIndex = threadIdx.x;\n    if (vertexIndex >= g_lerpParams.numVertices)\n        return;\n    const float3 v0 = kf0[vertexIndex];\n    const float3 v1 = kf1[vertexIndex];\n\n    dst[vertexIndex] = lerp(v0, v1, g_lerpParams.animTime);\n}\n"
  },
  {
    "path": "demo/shaders/motion_vectors.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n#pragma pack_matrix(row_major)\n\n#include \"rtxmg/utils/shader_debug.h\"\n\n#include <donut/shaders/bindless.h>\n#include <donut/shaders/binding_helpers.hlsli>\n\n#include \"render_params.h\"\n#include \"motion_vectors_params.h\"\n#include \"gbuffer.h\"\n\n#include \"rtxmg/cluster_builder/displacement.hlsli\"\n#include \"rtxmg/subdivision/subdivision_eval.hlsli\"\n\n// MVEC_DISPLACEMENT\n#define MVEC_DISPLACEMENT_FROM_SUBD_EVAL 0\n#define MVEC_DISPLACEMENT_FROM_MATERIAL 1\n\n#ifndef MVEC_DISPLACEMENT\n#error \"Must define MVEC_DISPLACEMENT\"\n#endif\n\nConstantBuffer<RenderParams>        g_RenderParams          : register(b0);\n\nTexture2D<DepthFormat>              t_Depth                 : register(t0);\nStructuredBuffer<HitResult>         t_HitResult             : register(t1);\nStructuredBuffer<SubdInstance>      t_SubdInstances         : register(t2); // indexed via instancID, but values will be null.\nStructuredBuffer<InstanceData>      t_InstanceData          : register(t3);\nStructuredBuffer<GeometryData>      t_GeometryData          : register(t4);\nStructuredBuffer<MaterialConstants> t_MaterialConstants     : register(t5);\n\n\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<float2>                 u_MotionVectors         : register(u0);\n\n#if ENABLE_SHADER_DEBUG\nRWStructuredBuffer<ShaderDebugElement> u_PixelDebug          : register(u1);\n#endif\n\n\n\nSamplerState                        s_DisplacementSampler : register(s0);\n\n\nstatic DynamicSubdivisionEvaluatorHLSL MakeDynamicSubdivisionEvaluator(SubdInstance subdInstance, uint32_t surfaceIndex)\n{\n    DynamicSubdivisionEvaluatorHLSL result;\n\n    result.m_plans = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.plansBindlessIndex)];\n    result.m_stencilMatrix = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.stencilMatrixBindlessIndex)];\n    result.m_subpatchTrees = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.subpatchTreesBindlessIndex)];\n    result.m_vertexPatchPointIndices = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.patchPointIndicesBindlessIndex)];\n    result.m_surfaceDescriptors = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.vertexSurfaceDescriptorBindlessIndex)];\n    result.m_vertexControlPointIndices = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.vertexControlPointIndicesBindlessIndex)];\n    result.m_vertexControlPoints = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.positionsBindlessIndex)];\n    result.m_vertexControlPointsPrev = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.positionsPrevBindlessIndex)];\n\n    result.m_surfaceIndex = surfaceIndex;\n    result.m_isolationLevel = uint16_t(g_RenderParams.isolationLevel);\n    return result;\n}\n\nfloat3 TransformPoint(float3 p, const float3x4 mat)\n{\n    return mul(mat, float4(p, 1.0f)).xyz;\n}\n\n[numthreads(kMotionVectorsNumThreadsX, kMotionVectorsNumThreadsY, 1)]\nvoid main(uint3 threadIdx : SV_DispatchThreadID)\n{\n    uint2 idx = threadIdx.xy;\n    if (any(idx >= uint2(g_RenderParams.camera.dims)))\n        return;\n\n    SHADER_DEBUG_INIT(u_PixelDebug, g_RenderParams.debugPixel, idx);\n\n    const HitResult hit = t_HitResult[idx.y * g_RenderParams.camera.dims.x + idx.x];\n\n    const float2 curPixel = g_RenderParams.jitter + float2(idx) + 0.5f;\n\n    // Check for miss\n    if (hit.instanceId == ~uint32_t(0))\n    {\n        // Re-project env map direction\n        const float3 vw = g_RenderParams.camera.unprojectPixelToWorldDirection(curPixel);\n        const float2 prevPixel = g_RenderParams.prevCamera.projectWorldDirectionToPixel(vw);\n        u_MotionVectors[idx] = prevPixel - curPixel;\n        return;\n    }\n\n    const float depth = t_Depth[idx];\n    const float3 Pw = g_RenderParams.camera.unprojectPixelToWorld_lineardepth(curPixel, depth);\n    float3 PdispW;\n\n    // Check for non-subd geometry\n    if (hit.surfaceIndex == ~uint32_t(0))\n    {\n        //  No deformation, only camera motion\n        float2 prevPixel = g_RenderParams.prevCamera.projectWorldToPixel(Pw);\n        u_MotionVectors[idx] = prevPixel - curPixel;\n        return;\n    }\n\n    InstanceData instanceData = t_InstanceData[hit.instanceId];\n\n    float2 prevPixel = 0.0f;\n    SubdInstance subdInstance = t_SubdInstances[hit.instanceId];\n    if (subdInstance.positionsPrevBindlessIndex != kInvalidBindlessIndex)\n    {\n        DynamicSubdivisionEvaluatorHLSL subd = MakeDynamicSubdivisionEvaluator(subdInstance, hit.surfaceIndex);\n\n        if (MVEC_DISPLACEMENT == MVEC_DISPLACEMENT_FROM_MATERIAL)\n        {\n            // Resample displacement from texture and apply to prev frame limit surface\n            // If tess rates vary then there can be a mismatch with the current frame hit point.\n            LimitFrame limitPrev = subd.EvaluatePrev(hit.surfaceUV);\n\n            float3 displacementVec = 0.f;\n\n            StructuredBuffer<uint16_t> surfaceToGeometryIndex = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.surfaceToGeometryIndexBindlessIndex)];\n            uint32_t geometryIndex = surfaceToGeometryIndex[hit.surfaceIndex] + instanceData.firstGeometryIndex;\n            GeometryData geometry = t_GeometryData[geometryIndex];\n            MaterialConstants material = t_MaterialConstants[geometry.materialIndex];\n\n            float displacementScale = 0.f;\n            int displacementTexIndex = -1;\n            GetDisplacement(material, g_RenderParams.globalDisplacementScale, displacementTexIndex, displacementScale);\n            if (displacementTexIndex >= 0)\n            {\n                Texture2D<float> displacementTex = ResourceDescriptorHeap[NonUniformResourceIndex(displacementTexIndex)];\n\n                float displacement = displacementTex.SampleLevel(s_DisplacementSampler, hit.texcoord, 0) * displacementScale;\n                float3 normal = normalize(cross(limitPrev.deriv1, limitPrev.deriv2));\n                displacementVec = displacement * normal;\n            }\n\n            PdispW = TransformPoint(limitPrev.p + displacementVec, subdInstance.prevLocalToWorld);\n            prevPixel = g_RenderParams.prevCamera.projectWorldToPixel(PdispW);\n        }\n        else\n        {\n            // Compute displacement using the delta between gbuffer hit point and subd limit point\n            // Expensive since it re-evalutes limit surface again, but compensates for tess rates\n            LimitFrame limit, limitPrev;\n            subd.Evaluate(limit, limitPrev, hit.surfaceUV);\n\n            float3 displacementVec = TransformPoint(Pw, subdInstance.worldToLocal) - limit.p;\n\n            PdispW = TransformPoint(limitPrev.p + displacementVec, subdInstance.prevLocalToWorld);\n            prevPixel = g_RenderParams.prevCamera.projectWorldToPixel(PdispW);\n        }\n    }\n    else\n    {\n        // No deformation, only camera motion\n        PdispW = Pw;\n        prevPixel = g_RenderParams.prevCamera.projectWorldToPixel(Pw);\n    }\n\n    u_MotionVectors[idx] = prevPixel - curPixel;\n}"
  },
  {
    "path": "demo/shaders/rtxmg_demo_path_tracer.hlsl",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma pack_matrix(row_major)\n\n#include \"rtxmg/utils/shader_debug.h\"\n\n#include \"render_params.h\"\n#include \"lighting_cb.h\"\n#include <donut/shaders/bindless.h>\n#include <donut/shaders/lighting.hlsli>\n#include <donut/shaders/packing.hlsli>\n#include <donut/shaders/scene_material.hlsli>\n#include <donut/shaders/surface.hlsli>\n#include <donut/shaders/utils.hlsli>\n#include <donut/shaders/binding_helpers.hlsli>\n\n#include \"rtxmg/cluster_builder/cluster.h\"\n#include \"rtxmg/hiz/hiz_buffer_constants.h\"\n#include \"rtxmg/utils/constants.h\"\n\n#include \"ray_payload.h\"\n#include \"color.hlsli\"\n#include \"gbuffer.h\"\n#include \"brdf.hlsli\"\n#include \"utils.hlsli\"\n#include \"envmap/shaders/envmap.hlsli\"\n\n#if defined(TARGET_D3D12)\n#define CONCAT(a,b) a##b\n#define CONCAT_UAV(x) CONCAT(u,x)\n#define NV_SHADER_EXTN_SLOT CONCAT_UAV(RTXMG_NVAPI_SHADER_EXT_SLOT)\n#define NV_SHADER_EXTN_REGISTER_SPACE space0\n#include \"nvHLSLExtns.h\"\n\nuint32_t GetClusterID()\n{\n    return NvRtGetClusterID();\n}\n\n#elif defined(TARGET_VULKAN)\n\n// Note that `vk::RayTracingPipelineClusterAccelerationStructureCreateInfoNV::allowClusterAccelerationStructures` must\n// be set to `true` to make this valid.\n[[vk::ext_extension(\"SPV_NV_cluster_acceleration_structure\")]]\n[[vk::ext_capability(5437)]]\n[[vk::ext_builtin_input(5436)]]\nstatic int const g_ClusterIDNV_;\n\nuint32_t GetClusterID()\n{\n    return (uint32_t)g_ClusterIDNV_;\n}\n\n#endif\n\nConstantBuffer<LightingConstants> g_Const : register(b0);\nConstantBuffer<RenderParams> g_RenderParams : register(b1);\n\nRWTexture2D<float4> u_Accum     : register(u0);\n\n// GBuffer\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<DepthFormat>    u_Depth         : register(u1);\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<NormalFormat>   u_Normal        : register(u2);\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<AlbedoFormat>   u_Albedo        : register(u3);\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<SpecularFormat> u_Specular      : register(u4);\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<SpecularHitTFormat> u_SpecularHitT  : register(u5);\nVK_IMAGE_FORMAT_UNKNOWN RWTexture2D<RoughnessFormat>    u_Roughness     : register(u6);\n\nRWStructuredBuffer<HitResult>   u_HitResult     : register(u7);\n\n#if ENABLE_DUMP_FLOAT\nRWTexture2D<float4> u_DebugTex1 : register(u8);\nRWTexture2D<float4> u_DebugTex2 : register(u9);\nRWTexture2D<float4> u_DebugTex3 : register(u10);\nRWTexture2D<float4> u_DebugTex4 : register(u11);\n#endif\n\n#if ENABLE_SHADER_DEBUG\nRWStructuredBuffer<ShaderDebugElement> u_PixelDebug : register(u12);\n#endif\n\n#ifndef TARGET_VULKAN\nRWBuffer<uint32_t>  u_TimeviewBuffer : register(u13);\n#endif\n\nRaytracingAccelerationStructure SceneBVH : register(t0);\nStructuredBuffer<InstanceData> t_InstanceData : register(t1);\nStructuredBuffer<GeometryData> t_GeometryData : register(t2);\nStructuredBuffer<MaterialConstants> t_MaterialConstants : register(t3);\nTexture2D<float4> t_EnvMap : register(t4);\nStructuredBuffer<float> t_EnvMapConditionalCDF: register(t5);\nStructuredBuffer<float> t_EnvMapMarginalCDF : register(t6);\nStructuredBuffer<float> t_EnvMapConditionalFunc: register(t7);\nStructuredBuffer<float> t_EnvMapMarginalFunc : register(t8);\nStructuredBuffer<ClusterShadingData> t_ClusterShadingData : register(t9);\nStructuredBuffer<float3> t_ClusterVertexPositions : register(t10);\n#if VERTEX_NORMALS\nStructuredBuffer<float3> t_ClusterVertexNormals : register(t11);\n#endif\nStructuredBuffer<SubdInstance> t_SubdInstances : register(t12);\n\nSamplerState s_MaterialSampler : register(s0);\n\n#include \"self_intersection_avoidance.hlsli\"\n\n// Cluster look up\nuint16_t2 ClusterGetEdgeSize(uint32_t clusterId)\n{\n    ClusterShadingData clusterShadingData = t_ClusterShadingData[clusterId];\n    return uint16_t2(clusterShadingData.m_clusterSizeX, clusterShadingData.m_clusterSizeY);\n}\n\nuint3 ClusterGetVertexIndices(uint32_t primId)\n{\n    const uint32_t      clusterId = GetClusterID();\n    const uint16_t      triID = (uint16_t)primId;\n\n    // vertex quad ordering: \n    // 23\n    // 01\n    // triangle ordering: left edge first -- 032+013 (diagonal:03) or 012+132 (diagonal:12)\n    // 21 .5    or   2. 54\n    // 0. 34    or   01 .3\n    // vx,vy are row-major vertex indices in range [0..sx][0..sy] sx,sy are cluster edge m_size\n    // if vx,vy are the lower left corner vtx idxs, then diagonal:03 == ((vx & 1) == (vy & 1))\n\n    uint16_t2 clusterEdgeSize = ClusterGetEdgeSize(clusterId);\n\n    const uint16_t qs = clusterEdgeSize.x;      // quad stride\n    const uint16_t vs = clusterEdgeSize.x + 1;  // vert stride\n    const uint16_t qid = triID >> 1;             // quad id\n    const uint16_t qx = qid % qs;               // quad x\n    const uint16_t qy = qid / qs;               // quad y\n    const uint16_t vid = qy * vs + qx;           // lower-left vertex id\n    const bool    diag03 = ((qx & 1) == (qy & 1));       // is diag 0-3 (true) or 1-2 (false)\n\n    const uint16_t df = uint16_t(diag03) << 1 | uint16_t(triID & 1);\n\n    uint3 indices;\n    switch (df)\n    {\n    case 0b00: indices = uint3(vid, vid + 1, vid + vs); break;\n    case 0b01: indices = uint3(vid + 1, vid + 1 + vs, vid + vs); break;\n    case 0b10: indices = uint3(vid, vid + 1 + vs, vid + vs); break;\n    case 0b11: indices = uint3(vid, vid + 1, vid + 1 + vs); break;\n    }\n\n    return indices;\n}\n\ninline uint16_t2 Index2D(uint32_t indexLinear, uint16_t lineStride)\n{\n    return uint16_t2(uint16_t(indexLinear % lineStride), uint16_t(indexLinear / lineStride));\n}\n\n// Given a cluster triangle id, find the uv coordinates in the parametric surface\n// that generated the triangle's three corners.\n//\ninline void GetSurfaceUV(out float2 uvs[3], ClusterShadingData clusterShadingData, uint primId)\n{\n    const uint3    uMajorVtxIDs = ClusterGetVertexIndices(primId);\n\n    const uint16_t2 clusterSize = uint16_t2(clusterShadingData.m_clusterSizeX, clusterShadingData.m_clusterSizeY);\n    const uint16_t2 clusterOffset = clusterShadingData.m_clusterOffset;\n    const uint16_t4 edgeSegments = clusterShadingData.m_edgeSegments;\n\n    const GridSampler sampler = { edgeSegments };\n\n    // offset local i,j index to surface index\n    uint16_t2 vertexIndex2d = Index2D(uMajorVtxIDs.x, clusterSize.x + 1) + clusterOffset;\n    uvs[0] = sampler.UV(vertexIndex2d, (ClusterPattern)g_RenderParams.clusterPattern);\n    vertexIndex2d = Index2D(uMajorVtxIDs.y, clusterSize.x + 1) + clusterOffset;\n    uvs[1] = sampler.UV(vertexIndex2d, (ClusterPattern)g_RenderParams.clusterPattern);\n    vertexIndex2d = Index2D(uMajorVtxIDs.z, clusterSize.x + 1) + clusterOffset;\n    uvs[2] = sampler.UV(vertexIndex2d, (ClusterPattern)g_RenderParams.clusterPattern);\n}\n\nstruct IntersectionRecord\n{\n    float3 p;             // world space intersection point\n    float3 n;             // world space shading normal\n    float3 gn;            // world space geometry normal\n    float2 texcoord;      // user-assigned texcoord from base mesh\n    float3 barycentrics;  // barycentrics\n    float3 distToEdge;\n    float  hitT;\n\n    // Gbuffer output needed for motion vecs\n    uint32_t surfaceIndex;\n    float2   surfaceUV;\n\n    MaterialSample ms;\n};\n\nvoid SetupPrimaryRay(uint2 pixelPosition, float2 subPixelJitter, out float3 rayOrigin, out float3 rayDirection)\n{\n    float2 d = ((float2(pixelPosition) + 0.5f + subPixelJitter) *\n        g_RenderParams.camera.dimsInv) *\n        2.f -\n        1.f;\n\n    d *= float2(1, -1);\n\n    RayDesc ray;\n    rayOrigin = g_RenderParams.eye;\n    rayDirection = normalize(d.x * g_RenderParams.U + d.y * g_RenderParams.V +\n        g_RenderParams.W);\n}\n\nRayDesc SetupShadowRay(float3 surfacePos, float3 L)\n{\n    RayDesc ray;\n    ray.Origin = surfacePos - WorldRayDirection() * 0.001;\n    ray.Direction = L;\n    ray.TMin = 0;\n    ray.TMax = 1.#INF;\n    return ray;\n}\n\n[shader(\"miss\")] void ShadowMiss(inout ShadowRayPayload payload : SV_RayPayload)\n{\n    payload.missed = true;\n}\n\nbool IsOccluded(float3 worldPos, float3 towardsLight)\n{\n    ShadowRayPayload shadowPayload = (ShadowRayPayload)0;\n    shadowPayload.missed = false;\n\n    RayDesc shadowRay = SetupShadowRay(worldPos, towardsLight);\n\n    TraceRay(SceneBVH,\n        RAY_FLAG_NONE,\n        0xFF,\n        1, // shadow hit group\n        0,\n        1, // shadow miss shader\n        shadowRay, shadowPayload);\n\n    return !shadowPayload.missed;\n}\n\nenum GeometryAttributes\n{\n    GeomAttr_Position = 0x01,\n    GeomAttr_TexCoord = 0x02,\n    GeomAttr_Normal = 0x04,\n    GeomAttr_Tangents = 0x08,\n\n    GeomAttr_All = 0x0F\n};\n\nstruct GeometrySample\n{\n    InstanceData instance;\n    MaterialConstants material;\n\n    float3 vertexPositions[3];\n    float3 vertexNormals[3];\n    float2 vertexTexcoords[3];\n\n    float3 barycentrics;\n    float2 texcoord;\n    float3x4 objectToWorld;\n    float3x4 worldToObject;\n\n    uint geometryIndex;\n    uint clusterId;\n    uint surfaceIndex;\n    float2 surfaceUV;\n};\n\nGeometrySample\nGetGeometryFromHit(RayPayload payload)\n{\n    GeometrySample gs = (GeometrySample)0;\n\n    InstanceData instance = t_InstanceData[payload.instanceID];\n\n    uint geometryIndex = instance.firstGeometryIndex + payload.geometryIndex;\n\n    GeometryData geometry = t_GeometryData[geometryIndex];\n\n    gs.instance = instance;\n    gs.geometryIndex = geometryIndex;\n    gs.objectToWorld = instance.transform;\n    gs.material = t_MaterialConstants[geometry.materialIndex];\n    gs.clusterId = ~0u;\n\n    float3x3 w2oRotation = transpose((float3x3)gs.objectToWorld);\n    float3 w2oTranslation = -mul(w2oRotation, float3(gs.objectToWorld[0][3], gs.objectToWorld[1][3], gs.objectToWorld[2][3]));\n    gs.worldToObject = float3x4(\n        w2oRotation[0], w2oTranslation.x,\n        w2oRotation[1], w2oTranslation.y,\n        w2oRotation[2], w2oTranslation.z);\n\n    gs.barycentrics.yz = payload.barycentrics;\n    gs.barycentrics.x = 1.0 - (gs.barycentrics.y + gs.barycentrics.z);\n\n\n    // Look up cluster geometry data\n\n    uint32_t clusterId = GetClusterID();\n    gs.clusterId = clusterId;\n    ClusterShadingData clusterShadingData = t_ClusterShadingData[clusterId];\n\n    uint3 localVtxIndices = ClusterGetVertexIndices(payload.primitiveIndex);\n    uint3 globalVtxIndices = localVtxIndices + clusterShadingData.m_vertexOffset;\n\n    // Load vertex positions.\n    gs.vertexPositions[0] = t_ClusterVertexPositions[globalVtxIndices[0]];\n    gs.vertexPositions[1] = t_ClusterVertexPositions[globalVtxIndices[1]];\n    gs.vertexPositions[2] = t_ClusterVertexPositions[globalVtxIndices[2]];\n\n#if VERTEX_NORMALS\n    // Load vertex normals.\n    gs.vertexNormals[0] = t_ClusterVertexNormals[globalVtxIndices[0]];\n    gs.vertexNormals[1] = t_ClusterVertexNormals[globalVtxIndices[1]];\n    gs.vertexNormals[2] = t_ClusterVertexNormals[globalVtxIndices[2]];\n#endif\n\n\n    // Use this for a stable cluster ID\n    uint linearClusterOffset = (clusterShadingData.m_clusterOffset.y * clusterShadingData.m_clusterSizeX) + clusterShadingData.m_clusterOffset.x;\n    SHADER_DEBUG(uint2(clusterShadingData.m_surfaceId, linearClusterOffset));\n    SHADER_DEBUG(localVtxIndices);\n\n    // Texcoords\n    // Bilinear texcoords\n    float2 uvs[3];\n    GetSurfaceUV(uvs, clusterShadingData, payload.primitiveIndex);\n    float2 uv = gs.barycentrics.x * uvs[0] + gs.barycentrics.y * uvs[1] + gs.barycentrics.z * uvs[2];\n\n    gs.surfaceUV = uv;\n    gs.surfaceIndex = clusterShadingData.m_surfaceId;\n\n    // bilerp from 4 corner attributes\n    const float u = uv.x;\n    const float v = uv.y;\n\n    gs.texcoord = clusterShadingData.m_texcoords[0] * (1.0f - u) * (1.0f - v)\n        + clusterShadingData.m_texcoords[1] * u * (1.0f - v)\n        + clusterShadingData.m_texcoords[2] * u * v\n        + clusterShadingData.m_texcoords[3] * (1.0f - u) * v;\n\n    return gs;\n}\n\n// Blinking function that returns true for \"on\" state based on subframe index\nbool GetBlinkState(uint subFrameIndex, uint blinkPeriod = 60)\n{\n    return (subFrameIndex / blinkPeriod) % 2 == 0;\n}\n\nvoid GetClipPoints(out float3 outClipPoints[3], in GeometrySample gs)\n{\n    float3 worldSpacePositions[3];\n    worldSpacePositions[0] =\n        mul(gs.instance.transform, float4(gs.vertexPositions[0], 1.0)).xyz;\n    worldSpacePositions[1] =\n        mul(gs.instance.transform, float4(gs.vertexPositions[1], 1.0)).xyz;\n    worldSpacePositions[2] =\n        mul(gs.instance.transform, float4(gs.vertexPositions[2], 1.0)).xyz;\n\n    float4 projectedPoints[3];\n    projectedPoints[0] = mul(g_RenderParams.viewProjectionMatrix,\n        float4(worldSpacePositions[0], 1.0));\n    projectedPoints[1] = mul(g_RenderParams.viewProjectionMatrix,\n        float4(worldSpacePositions[1], 1.0));\n    projectedPoints[2] = mul(g_RenderParams.viewProjectionMatrix,\n        float4(worldSpacePositions[2], 1.0));\n\n    outClipPoints[0] = projectedPoints[0].xyz / projectedPoints[0].w;\n    outClipPoints[1] = projectedPoints[1].xyz / projectedPoints[1].w;\n    outClipPoints[2] = projectedPoints[2].xyz / projectedPoints[2].w;\n}\n\nMaterialSample RTXMG_EvaluateSceneMaterial(GeometrySample gs, float3 normal, MaterialTextureSample textures)\n{\n    MaterialSample result = DefaultMaterialSample();\n    result.roughness = 1;\n\n    ColorMode colorMode = g_RenderParams.colorMode;\n\n    if (colorMode == ColorMode::COLOR_BY_NORMAL)\n    {\n        result.baseColor = 0.5f * (float3(1, 1, 1) + normal);\n        result.diffuseAlbedo = 0.5f * (float3(1, 1, 1) + normal);\n    }\n    else if (colorMode == ColorMode::COLOR_BY_TOPOLOGY)\n    {\n        uint32_t clusterId = GetClusterID();\n        uint32_t surfaceId = t_ClusterShadingData[clusterId].m_surfaceId;\n\n        SubdInstance subdInstance = t_SubdInstances[InstanceID()];\n        StructuredBuffer<uint16_t> topologyQuality = ResourceDescriptorHeap[NonUniformResourceIndex(subdInstance.topologyQualityBindlessIndex)];\n        uint16_t surfaceValue = topologyQuality[surfaceId];\n\n        float value = float(surfaceValue) / 255.f;\n\n        result.baseColor = lerp(float3(0.f, 1.f, 0.f), float3(1.f, 0.f, 0.f), value);\n    }\n    else if (colorMode == ColorMode::COLOR_BY_TEXCOORD)\n    {\n        result.baseColor = float3(frac(gs.texcoord), 0);\n        result.diffuseAlbedo = float3(frac(gs.texcoord), 0);\n    }\n    else if (colorMode == ColorMode::COLOR_BY_MATERIAL)\n    {\n        float3 hashedColor = UintToColor(gs.material.materialID);\n        result.baseColor = hashedColor;\n        result.diffuseAlbedo = hashedColor;\n    }\n    else if (colorMode == ColorMode::COLOR_BY_GEOMETRY_INDEX)\n    {\n        float3 hashedColor = UintToColor(gs.geometryIndex);\n        result.baseColor = hashedColor;\n        result.diffuseAlbedo = hashedColor;\n    }\n    else if (colorMode == ColorMode::BASE_COLOR)\n    {\n        if (g_RenderParams.shadingMode == ShadingMode::AO)\n        {\n            result.baseColor = 0.8;\n        }\n        else\n        {\n            result.baseColor = lerp(gs.material.baseOrDiffuseColor.rgb, textures.baseOrDiffuse.rgb, textures.baseOrDiffuse.a);\n\n            result.roughness = gs.material.roughness;\n            if (g_RenderParams.roughnessOverride > 0.f)\n            {\n                result.roughness = g_RenderParams.roughnessOverride;\n            }\n            else if (gs.material.metalRoughOrSpecularTextureIndex >= 0)\n            {\n                result.roughness = textures.metalRoughOrSpecular.r;\n            }\n            result.roughness = max(result.roughness, 1e-4f);\n\n            result.metalness = gs.material.metalness;\n            result.hasMetalRoughParams = true;\n            // Compute the BRDF inputs for the metal-rough model\n            // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metal-brdf-and-dielectric-brdf\n            if (g_RenderParams.shadingMode == ShadingMode::PT)\n            {\n                // the metalness map is arriving as \"emissive\" to re-use donut materials\n                // and specular maps arrive in the occlusion texture.\n                if (gs.material.occlusionTextureIndex >= 0)\n                {\n                    result.metalness = -1.f;\n                    result.specularF0 = textures.occlusion.rgb;\n                }\n                else if (gs.material.emissiveTextureIndex >= 0)\n                {\n                    result.metalness = textures.emissive.r;\n                }\n            }\n            else\n            {\n                // direct rays will use the built in donut model.\n                result.diffuseAlbedo = lerp(result.baseColor * (1.0 - c_DielectricSpecular), 0.0, result.metalness);\n                result.specularF0 = lerp(c_DielectricSpecular, result.baseColor.rgb, result.metalness);\n            }\n        }\n    }\n    else if (colorMode == ColorMode::COLOR_BY_CLUSTER_ID)\n    {\n        ClusterShadingData clusterShadingData = t_ClusterShadingData[gs.clusterId];\n        uint linearClusterOffset = (clusterShadingData.m_clusterOffset.y * clusterShadingData.m_clusterSizeX) + clusterShadingData.m_clusterOffset.x;\n\n        uint hash = 0;\n        hash = MurmurAdd(hash, clusterShadingData.m_surfaceId);\n        hash = MurmurAdd(hash, linearClusterOffset);\n        float3 hashedColor = UintToColor(hash);\n        result.baseColor = hashedColor;\n        result.diffuseAlbedo = hashedColor;\n    }\n    else if (colorMode == ColorMode::COLOR_BY_MICROTRI_ID)\n    {\n        uint primitiveIndex = PrimitiveIndex();\n\n        ClusterShadingData clusterShadingData = t_ClusterShadingData[gs.clusterId];\n        uint linearClusterOffset = (clusterShadingData.m_clusterOffset.y * clusterShadingData.m_clusterSizeX) + clusterShadingData.m_clusterOffset.x;\n\n        uint hash = 0;\n        hash = MurmurAdd(hash, clusterShadingData.m_surfaceId);\n        hash = MurmurAdd(hash, linearClusterOffset);\n        hash = MurmurAdd(hash, primitiveIndex);\n        float3 hashedColor = UintToColor(hash);\n        result.baseColor = hashedColor;\n        result.diffuseAlbedo = hashedColor;\n    }\n    else if (colorMode == ColorMode::COLOR_BY_SURFACE_INDEX)\n    {\n        if (g_RenderParams.debugSurfaceIndex >= 0)\n        {\n            // Highlighting mode enabled\n            if (gs.surfaceIndex == (uint)g_RenderParams.debugSurfaceIndex)\n            {\n                // This is the highlighted surface - blink between red and dark gray\n                bool blinkOn = GetBlinkState(g_RenderParams.subFrameIndex);\n                float3 highlightColor = blinkOn ? float3(1.0, 0.0, 0.0) : float3(0.1, 0.1, 0.1);\n                result.baseColor = highlightColor;\n                result.diffuseAlbedo = highlightColor;\n            }\n            else\n            {\n                // All other surfaces are dark gray\n                float3 darkGray = float3(0.1, 0.1, 0.1);\n                result.baseColor = darkGray;\n                result.diffuseAlbedo = darkGray;\n            }\n        }\n        else\n        {\n            // Normal hashed color scheme when no debug surface is selected\n            float3 hashedColor = UintToColor(gs.surfaceIndex);\n            result.baseColor = hashedColor;\n            result.diffuseAlbedo = hashedColor;\n        }\n    }\n    else if (colorMode == ColorMode::COLOR_BY_CLUSTER_UV)\n    {\n        result.baseColor = float3(gs.surfaceUV, 0);\n        result.diffuseAlbedo = float3(gs.surfaceUV, 0);\n    }\n    else if (colorMode == ColorMode::COLOR_BY_MICROTRI_AREA)\n    {\n        float3 clipPoints[3];\n        GetClipPoints(clipPoints, gs);\n\n        const float uTriScreenArea = .5f * length(cross(clipPoints[0] - clipPoints[1], clipPoints[2] - clipPoints[0]));\n        const float uTriAreaInPixels = g_RenderParams.camera.dims.x * g_RenderParams.camera.dims.y * uTriScreenArea / 4.f;  // the area of the screen is 4.f since the screen vertices range from [-1, 1]\n\n        const float normUTriAreaInPixels = Remap(clamp(uTriAreaInPixels, 0.f, 2.f), 0.f, 2.f, 1.0f, 0.0f);\n        result.baseColor = Temperature(normUTriAreaInPixels);\n        result.diffuseAlbedo = result.baseColor;\n    }\n\n    result.occlusion = 1.0;\n\n    // if you need to highlight a particular surface\n    if (gs.surfaceIndex == DEBUG_SURFACE)\n    {\n        result.baseColor = float3(1, 0, 0);\n    }\n\n    return result;\n}\n\nMaterialTextureSample RTXMG_DefaultMaterialTextures()\n{\n    MaterialTextureSample values;\n    values.baseOrDiffuse = float4(1.0, 1.0, 1.0, 0.0); // fully transparent texture\n    values.metalRoughOrSpecular = float4(0.8, 0, 0, 0);\n    values.emissive = float4(0.0, 0.0, 0.0, 0.0); // no metal default\n    return values;\n}\n\nMaterialSample\nSampleGeometryMaterial(GeometrySample gs,\n    float3 normal,\n    float2 texGradX,\n    float2 texGradY,\n    float mipLevel, // <-- Use a compile time constant for mipLevel, < 0 for aniso filtering\n    SamplerState materialSampler)\n{\n    MaterialTextureSample textures = RTXMG_DefaultMaterialTextures();\n\n    if ((gs.material.baseOrDiffuseTextureIndex >= 0) &&\n        (gs.material.flags & MaterialFlags_UseBaseOrDiffuseTexture) != 0)\n    {\n        Texture2D<float4> diffuseTexture = ResourceDescriptorHeap[NonUniformResourceIndex(\n            gs.material.baseOrDiffuseTextureIndex)];\n\n        if (mipLevel >= 0)\n            textures.baseOrDiffuse =\n            diffuseTexture.SampleLevel(materialSampler, gs.texcoord, mipLevel);\n        else\n        {\n            textures.baseOrDiffuse = diffuseTexture.SampleGrad(\n                materialSampler, gs.texcoord, texGradX, texGradY);\n        }\n    }\n\n    if ((gs.material.metalRoughOrSpecularTextureIndex >= 0) &&\n        (gs.material.flags & MaterialFlags_UseMetalRoughOrSpecularTexture) != 0)\n    {\n        Texture2D<float4> specularTexture = ResourceDescriptorHeap[NonUniformResourceIndex(\n            gs.material.metalRoughOrSpecularTextureIndex)];\n\n        if (mipLevel >= 0)\n            textures.metalRoughOrSpecular =\n            specularTexture.SampleLevel(materialSampler, gs.texcoord, mipLevel);\n        else\n            textures.metalRoughOrSpecular = specularTexture.SampleGrad(\n                materialSampler, gs.texcoord, texGradX, texGradY);\n    }\n\n    if ((gs.material.emissiveTextureIndex >= 0) &&\n        (gs.material.flags & MaterialFlags_UseEmissiveTexture) != 0)\n    {\n        Texture2D<float4> emissiveTexture = ResourceDescriptorHeap[NonUniformResourceIndex(\n            gs.material.emissiveTextureIndex)];\n\n        if (mipLevel >= 0)\n            textures.emissive =\n            emissiveTexture.SampleLevel(materialSampler, gs.texcoord, mipLevel);\n        else\n            textures.emissive = emissiveTexture.SampleGrad(\n                materialSampler, gs.texcoord, texGradX, texGradY);\n    }\n    if ((gs.material.occlusionTextureIndex >= 0) &&\n        (gs.material.flags & MaterialFlags_UseOcclusionTexture) != 0)\n    {\n        Texture2D<float4> occlusionTexture = ResourceDescriptorHeap[NonUniformResourceIndex(\n            gs.material.occlusionTextureIndex)];\n\n        if (mipLevel >= 0)\n            textures.occlusion =\n            occlusionTexture.SampleLevel(materialSampler, gs.texcoord, mipLevel);\n        else\n            textures.occlusion = occlusionTexture.SampleGrad(\n                materialSampler, gs.texcoord, texGradX, texGradY);\n    }\n\n    return RTXMG_EvaluateSceneMaterial(gs, normal, textures);\n}\n\nMaterialSample GetMaterialSample(GeometrySample gs, float3 normal)\n{\n    uint2 pixelPosition = DispatchRaysIndex().xy;\n\n    float2 noJitter = float2(0.f, 0.f);\n\n    float3 ray0Origin, ray0Direction;\n    float3 rayXOrigin, rayXDirection;\n    float3 rayYOrigin, rayYDirection;\n\n    SetupPrimaryRay(pixelPosition, noJitter, ray0Origin, ray0Direction);\n    SetupPrimaryRay(pixelPosition + uint2(1, 0), noJitter, rayXOrigin, rayXDirection);\n    SetupPrimaryRay(pixelPosition + uint2(0, 1), noJitter, rayYOrigin, rayYDirection);\n    float3 worldSpacePositions[3];\n    worldSpacePositions[0] =\n        mul(gs.instance.transform, float4(gs.vertexPositions[0], 1.0)).xyz;\n    worldSpacePositions[1] =\n        mul(gs.instance.transform, float4(gs.vertexPositions[1], 1.0)).xyz;\n    worldSpacePositions[2] =\n        mul(gs.instance.transform, float4(gs.vertexPositions[2], 1.0)).xyz;\n    float3 bary_0 = computeRayIntersectionBarycentrics(\n        worldSpacePositions, ray0Origin, ray0Direction);\n    float3 bary_x = computeRayIntersectionBarycentrics(\n        worldSpacePositions, rayXOrigin, rayXDirection);\n    float3 bary_y = computeRayIntersectionBarycentrics(\n        worldSpacePositions, rayYOrigin, rayYDirection);\n    float2 texCoord0 = interpolate(gs.vertexTexcoords, bary_0);\n    float2 texCoordX = interpolate(gs.vertexTexcoords, bary_x);\n    float2 texCoordY = interpolate(gs.vertexTexcoords, bary_y);\n    float2 texGradX = texCoordX - texCoord0;\n    float2 texGradY = texCoordY - texCoord0;\n\n    MaterialSample ms = SampleGeometryMaterial(gs, normal, texGradX, texGradY, -1, s_MaterialSampler);\n\n    return ms;\n}\n\nIntersectionRecord GetIntersectionRecord(RayPayload payload)\n{\n    IntersectionRecord ir = (IntersectionRecord)0;\n    ir.hitT = RayTCurrent();\n\n    GeometrySample gs = GetGeometryFromHit(payload);\n\n    float3 objP, objN, wldP;\n    float wldOffset;\n\n    SafeSpawnPoint(objP, wldP, objN, ir.gn, wldOffset,\n        gs.vertexPositions[0], gs.vertexPositions[1], gs.vertexPositions[2],\n        gs.barycentrics.yz, gs.objectToWorld, gs.worldToObject);\n\n    if (dot(ir.gn, WorldRayDirection()) > 0.0f)\n    {\n        ir.gn = -ir.gn;\n    }\n\n    ir.p = SafeSpawnPoint(wldP, ir.gn, wldOffset);\n\n    ir.n = ir.gn;\n    \n#if VERTEX_NORMALS\n    // Use interpolated vertex normals when available\n    float3 objInterpolatedNormal = gs.barycentrics.x * gs.vertexNormals[0] + \n                                   gs.barycentrics.y * gs.vertexNormals[1] + \n                                   gs.barycentrics.z * gs.vertexNormals[2];\n    \n    // Transform to world space and normalize\n    float3 worldInterpolatedNormal = normalize(mul((float3x3)gs.objectToWorld, objInterpolatedNormal));\n    \n    // Handle front-facing (ensure normal faces towards camera)\n    if (dot(worldInterpolatedNormal, WorldRayDirection()) > 0.0f)\n    {\n        worldInterpolatedNormal = -worldInterpolatedNormal;\n    }\n    \n    ir.n = worldInterpolatedNormal;\n#endif\n    \n    ir.texcoord = gs.texcoord;\n    ir.barycentrics = gs.barycentrics;\n    ir.surfaceIndex = gs.surfaceIndex;\n    ir.surfaceUV = gs.surfaceUV;\n\n    ir.ms = GetMaterialSample(gs, ir.n);\n    ir.ms.geometryNormal = ir.gn;\n    ir.ms.shadingNormal = ir.n;\n\n    if (g_RenderParams.enableWireframe)\n    {\n        float3 clipPoints[3];\n        GetClipPoints(clipPoints, gs);\n\n        float3 e_01 = clipPoints[0] - clipPoints[1];\n        float3 e_12 = clipPoints[1] - clipPoints[2];\n        float3 e_20 = clipPoints[2] - clipPoints[0];\n\n        float area = .5f * length(cross(e_01, e_20));\n\n        ir.distToEdge = 2.f * area * gs.barycentrics;\n\n        ir.distToEdge.x /= length(e_12);\n        ir.distToEdge.y /= length(e_20);\n        ir.distToEdge.z /= length(e_01);\n    }\n    return ir;\n}\n\nfloat3 ShadeSurface(IntersectionRecord ir)\n{\n    float3 diffuseTerm = 0, specularTerm = 0;\n\n    if (!IsOccluded(ir.p, -g_Const.light.direction))\n    {\n        ShadeSurface(g_Const.light, ir.ms, ir.p, WorldRayDirection(), diffuseTerm,\n                       specularTerm);\n    }\n\n    return (diffuseTerm + specularTerm +\n        ir.ms.diffuseAlbedo * g_Const.ambientColor.rgb);\n}\n\nstruct Attributes\n{\n    float2 uv;\n};\n\n[shader(\"miss\")] void Miss(inout RayPayload payload\n    : SV_RayPayload)\n{\n    bool hasEnvMap = g_RenderParams.hasEnvironmentMap;\n\n    float3 d = WorldRayDirection();\n    float2 u = convertDirToTexCoords(d, g_RenderParams.envmapRotation);\n\n    float3 lightContribution;\n    if (g_RenderParams.shadingMode == ShadingMode::PT)\n    {\n        if (hasEnvMap)\n        {\n            lightContribution = envMapEvaluate(u, t_EnvMap, g_RenderParams.envmapIntensity, s_MaterialSampler);\n        }\n        else\n        {\n            lightContribution = ((float3(d.y, d.y, d.y) + 1.f) / 2.f) * g_RenderParams.missColor;\n        }\n    }\n    else\n    {\n        lightContribution = g_RenderParams.missColor;\n    }\n\n    if (g_RenderParams.shadingMode == ShadingMode::PT)\n    {\n        float brdfPdf = payload.pdf;\n        uint bounce = payload.bounce;\n\n        if (bounce > 0)\n        {\n            // Calculate pdf if texture environment map is present and when not present,\n            // uniformly sample the hemisphere for the gradient environment map\n            const float lightPdf = hasEnvMap ? envMapPdf(u, t_EnvMap, t_EnvMapConditionalFunc, t_EnvMapMarginalCDF, s_MaterialSampler) : 1.f / (4.f * M_PIf);\n            const float misWeight = PowerHeuristic(1.f, brdfPdf, 1.f, lightPdf);\n            const float3 pathWeight = FromRGBe9995(payload.pathWeight);\n            lightContribution *= pathWeight * misWeight;\n        }\n        else\n        {\n            if (g_RenderParams.enableEnvmapHeatmap)\n            {\n                float pdf = hasEnvMap ? envMapPdf(u, t_EnvMap, t_EnvMapConditionalFunc, t_EnvMapMarginalCDF, s_MaterialSampler) : 0.0f;\n                lightContribution = hasEnvMap ? Temperature(pdf) : Temperature((d.y + 1.f) / 2.f);\n            }\n        }\n        payload.pathContribution = ToRGBe9995(lightContribution);\n    }\n    else\n    {\n        payload.pathWeight = ToRGBe9995(lightContribution);\n    }\n    payload.hitT = 1.#INF;\n    payload.instanceID = ~0u;\n\n    bool clearGBuffer = g_RenderParams.denoiserMode != DenoiserMode::None;\n    if (clearGBuffer)\n    {\n        // Write no hit\n        uint2 dispatchDims = DispatchRaysDimensions().xy;\n        uint2 dispatchPixel = DispatchRaysIndex().xy;\n        uint dispatchIndex = dispatchPixel.x + dispatchDims.x * dispatchPixel.y;\n\n        if (g_RenderParams.shadingMode != ShadingMode::PT || payload.bounce == 0)\n        {\n            u_HitResult[dispatchIndex] = DefaultHitResult();\n\n            // Clear gbuffer\n            // using linear depth\n            u_Depth[dispatchPixel] = g_RenderParams.zFar;\n            u_Normal[dispatchPixel] = float4(0.0f, 0.0f, 0.0f, 0.0f);\n            u_Albedo[dispatchPixel] = float4(0.0f, 0.0f, 0.0f, 0.0f);\n            u_Specular[dispatchPixel] = float4(0.0f, 0.0f, 0.0f, 0.0f);\n            u_Roughness[dispatchPixel] = 0.0f;\n        }\n\n        // If PT mode, then if we miss on bounce 0 or 1, then clear to zFar\n        // If non-PT mode, then only bounce 0, clear to ZFar\n        if (payload.bounce <= 1)\n        {\n            u_SpecularHitT[dispatchPixel] = g_RenderParams.zFar;\n        }\n    }\n}\n\nfloat WireframeWeight(IntersectionRecord ir)\n{\n    float thickness = g_RenderParams.wireframeThickness * 1e-4f;\n    float smoothness = 1e-7f;\n    float3 b = ir.barycentrics * ir.distToEdge;\n\n    float minBary = min(min(b.x, b.y), b.z);\n    return smoothstep(thickness, thickness + smoothness, minBary);\n}\n\nfloat3 AOSample(const float3 normal, const float2 u)\n{\n    const Onb onb = MakeOnb(normal);\n    float3    dir = CosineSampleHemisphere(u);\n    dir = onb.ToWorld(dir);\n    return normalize(dir);\n}\n\nfloat3 SampleDirect(MaterialSample material, float3 p, float3 gN, float3 N, float3 V, inout uint32_t seed)\n{\n    float2 u = float2(Rnd(seed), Rnd(seed));\n\n    float lightPdf = 1.f;\n    float3 envMapColor;\n    float3 L = envMapImportanceSample(u, g_RenderParams.envmapRotationInv,\n        lightPdf, envMapColor, t_EnvMap, g_RenderParams.envmapIntensity, t_EnvMapConditionalFunc, t_EnvMapMarginalFunc, t_EnvMapConditionalCDF, t_EnvMapMarginalCDF, s_MaterialSampler);\n    float3 lightContribution = 0.f;\n    if (IsOccluded(p, L))\n    {\n        return lightContribution;\n    }\n    lightContribution = envMapColor;\n    float brdfPdf = 1.f;\n    const float3 brdfWeight = BRDFEval(material, gN, N, V, L, brdfPdf);\n    const float misWeight = PowerHeuristic(1.f, lightPdf, 1.f, brdfPdf);\n    // brdfWeight includes scaling by dot( N, L )\n    float3 result = lightContribution * brdfWeight * misWeight / lightPdf;\n    return result;\n}\n\n[shader(\"closesthit\")]void ClosestHit(inout RayPayload payload\n    : SV_RayPayload, in Attributes attrib\n    : SV_IntersectionAttributes)\n{\n    SHADER_DEBUG_INIT(u_PixelDebug, g_RenderParams.debugPixel, DispatchRaysIndex().xy);\n\n    payload.instanceID = InstanceID();\n    payload.primitiveIndex = PrimitiveIndex();\n    payload.geometryIndex = GeometryIndex();\n    payload.barycentrics = attrib.uv;\n    payload.hitT = RayTCurrent();\n\n    SHADER_DEBUG(uint4(payload.instanceID, payload.primitiveIndex, payload.geometryIndex, GetClusterID()));\n    SHADER_DEBUG(float3(payload.barycentrics, payload.hitT));\n\n    IntersectionRecord ir = GetIntersectionRecord(payload);\n\n    float3 pathWeight = 1.f;\n\n    float wfWeight = g_RenderParams.enableWireframe ? WireframeWeight(ir) : 1.0f;\n\n    if (g_RenderParams.denoiserMode != DenoiserMode::None)\n    {\n        uint2 dispatchDims = DispatchRaysDimensions().xy;\n        uint2 dispatchPixel = DispatchRaysIndex().xy;\n        uint dispatchIndex = dispatchPixel.x + dispatchDims.x * dispatchPixel.y;\n\n        const bool writePrimarySurfaceGBuffer = (g_RenderParams.shadingMode != ShadingMode::PT || payload.bounce == 0);\n\n        if (writePrimarySurfaceGBuffer)\n        {\n            HitResult hitResult;\n            hitResult.instanceId = payload.instanceID;\n            hitResult.surfaceIndex = ir.surfaceIndex;\n            hitResult.surfaceUV = ir.surfaceUV;\n            hitResult.texcoord = ir.texcoord;\n            u_HitResult[dispatchIndex] = hitResult;\n\n            float depth = dot(normalize(g_RenderParams.W), ir.p - g_RenderParams.eye);\n            u_Depth[dispatchPixel] = depth;\n            u_Normal[dispatchPixel] = float4(ir.n, 0.f);\n\n            float3 V = -WorldRayDirection();\n            FresnelBlend brdf = MakeFresnelBlend(ir.ms.baseColor, ir.ms.specularF0, ir.ms.metalness, ir.ms.roughness);\n            u_Albedo[dispatchPixel] = float4(brdf.m_diffuse.m_albedo * wfWeight, 1.0f);\n            float3 spec = BRDFEnvApprox(brdf, ir.n, V);\n            u_Specular[dispatchPixel] = float4(spec * wfWeight, 1.0f);\n            u_Roughness[dispatchPixel] = ir.ms.roughness;\n        }\n\n        if (g_RenderParams.shadingMode != ShadingMode::PT)\n        {\n            // Non-PT mode clear to far Z\n            u_SpecularHitT[dispatchPixel] = g_RenderParams.zFar;\n        }\n        else if (payload.bounce == 1)\n        {\n            // We want to write the hit distance from primary surface to specular hit\n            u_SpecularHitT[dispatchPixel] = ir.hitT;\n        }\n    }\n\n    if (wfWeight == 0.f)\n    {\n        payload.pathWeight = 0;\n        return;\n    }\n\n\n    if (g_RenderParams.shadingMode == ShadingMode::PRIMARY_RAYS)\n    {\n        pathWeight = ir.ms.baseColor;\n        payload.pathWeight = ToRGBe9995(pathWeight);\n    }\n    else if (g_RenderParams.shadingMode == ShadingMode::AO)\n    {\n        uint spp = g_RenderParams.denoiserMode != DenoiserMode::None ? 1 : g_RenderParams.spp;\n\n        uint32_t seed = payload.seed;\n        float2 subPixelJitter = RandomStrat(payload.multipurposeField, sqrt(spp), seed);\n        float3 L = AOSample(ir.n, subPixelJitter);\n        bool occluded = IsOccluded(ir.p, L);\n        pathWeight *= ir.ms.baseColor * (occluded ? 0.f : 1.f);\n        payload.pathWeight = ToRGBe9995(pathWeight);\n        payload.seed = seed;\n    }\n    else if (g_RenderParams.shadingMode == ShadingMode::PT)\n    {\n        uint32_t seed = payload.seed;\n        float3 V = -WorldRayDirection();\n\n        float3 pw = FromRGBe9995(payload.pathWeight);\n        pathWeight *= pw;\n        float3 lightContribution = 0;\n        float samplePdf = payload.pdf;\n\n        if (g_RenderParams.hasEnvironmentMap)\n        {\n            float3 directLighting = SampleDirect(ir.ms, ir.p, ir.gn, ir.n, V, seed);\n            lightContribution = pathWeight * directLighting;\n        }\n\n        if (payload.bounce < g_RenderParams.ptMaxBounces - 1)\n        {\n            // No need to do this on the final hit, since we won't trace \n            // another ray anyway\n            float3 L = 0;\n\n            float3 indirectWeight;\n\n            indirectWeight = BRDFSample(ir.ms, ir.gn, ir.n, V, L, samplePdf, seed);\n            pathWeight *= indirectWeight;\n\n            payload.pathWeight = ToRGBe9995(pathWeight);\n            payload.multipurposeField = PackNormalizedVector(L);\n            payload.rayOrigin = ir.p;\n\n            payload.pdf = samplePdf;\n            payload.seed = seed;\n        }\n        payload.pathContribution = ToRGBe9995(lightContribution);\n    }\n}\n\nvoid TraceRadiancePT(uint           bounce,\n                     inout uint     seed,\n                     const uint32_t subPixelIndex,\n                     inout float3   rayOrigin,\n                     inout float3   rayDirection,\n                     inout float3   pathWeight,\n                     inout float3   pathContribution,\n                     inout float    pdf,\n                     out float      hitT)\n{\n    RayPayload payload = (RayPayload)0;\n    payload.instanceID = ~0u;\n    payload.pathWeight = ToRGBe9995(pathWeight);\n    payload.bounce = bounce;\n    payload.multipurposeField = subPixelIndex;\n    payload.pathContribution = ToRGBe9995(pathContribution);\n    payload.pdf = pdf;\n    payload.seed = seed;\n\n    RayDesc ray;\n    ray.Origin = rayOrigin;\n    ray.Direction = rayDirection;\n    ray.TMin = 0;\n    ray.TMax = 1.#INF;\n\n    TraceRay(SceneBVH,\n        RAY_FLAG_NONE,\n        0xFF,\n        0, // which hit group to use\n        0,\n        0, // which miss shader to use (0 = regular, 1 = shadow)\n        ray, payload);\n\n    pathWeight = FromRGBe9995(payload.pathWeight);\n    hitT = payload.hitT;\n    seed = payload.seed;\n    rayDirection = UnpackNormalizedVector(payload.multipurposeField); // multipurposeField gets re-used for normal\n    rayOrigin = payload.rayOrigin;\n    float3 pc = FromRGBe9995(payload.pathContribution);\n    if (g_RenderParams.denoiserMode == DenoiserMode::None)\n    {\n        pathContribution += bounce > 0 ? FireflyFiltering(pc, g_RenderParams.fireflyMaxIntensity) : pc;\n    }\n    else\n    {\n        pathContribution += pc;\n    }\n    pdf = payload.pdf;\n}\n\nvoid TraceRadiancePR(float3 rayOrigin, float3 rayDirection, out float3 pathWeight, out float hitT)\n{\n    RayPayload payload = (RayPayload)0;\n    payload.instanceID = ~0u;\n\n    RayDesc ray;\n    ray.Origin = rayOrigin;\n    ray.Direction = rayDirection;\n    ray.TMin = 0;\n    ray.TMax = 1.#INF;\n\n    TraceRay(SceneBVH,\n        RAY_FLAG_NONE,\n        0xFF,\n        0, // which hit group to use\n        0,\n        0, // which miss shader to use (0 = regular, 1 = shadow)\n        ray, payload);\n\n    pathWeight = FromRGBe9995(payload.pathWeight);\n    hitT = payload.hitT;\n}\n\nvoid TraceRadianceAO(inout uint32_t seed,\n                    const uint32_t         subPixelIndex,\n                    float3                 rayOrigin,\n                    float3                 rayDirection,\n                    out float3 pathWeight,\n                    out float hitT)\n{\n    RayPayload payload = (RayPayload)0;\n    payload.instanceID = ~0u;\n    payload.multipurposeField = subPixelIndex;\n    payload.seed = seed;\n\n    RayDesc ray;\n    ray.Origin = rayOrigin;\n    ray.Direction = rayDirection;\n    ray.TMin = 0;\n    ray.TMax = 1.#INF;\n\n    TraceRay(SceneBVH,\n               RAY_FLAG_NONE,\n               0xFF,\n               0, // which hit group to use\n               0,\n               0, // which miss shader to use (0 = regular, 1 = shadow)\n               ray, payload);\n\n    pathWeight = FromRGBe9995(payload.pathWeight);\n    hitT = payload.hitT;\n    seed = payload.seed;\n}\n\nuint TimeDiff(uint startTime, uint endTime)\n{\n    // Account for (at most one) overflow\n    return endTime >= startTime ? (endTime - startTime) : (~0u - (startTime - endTime));\n}\n\n[shader(\"raygeneration\")]void RayGen()\n{\n    SHADER_DEBUG_INIT(u_PixelDebug, g_RenderParams.debugPixel, DispatchRaysIndex().xy);\n\n#if defined(TARGET_D3D12)\n    uint startTime = NvGetSpecial(NV_SPECIALOP_GLOBAL_TIMER_LO);\n#endif\n    DUMP_FLOAT4(1, float4(0, 0, 0, 1)); // Clear debug buffer\n    DUMP_FLOAT4(2, float4(0, 0, 0, 1)); // Clear debug buffer\n    DUMP_FLOAT4(3, float4(0, 0, 0, 1)); // Clear debug buffer\n    DUMP_FLOAT4(4, float4(0, 0, 0, 1)); // Clear debug buffer\n\n    uint2 pixelPosition = DispatchRaysIndex().xy;\n    uint imageIndex = GetImageIndex();\n\n    float3 result = 0;\n    float3 diffuseResult = 0;\n    float3 specularResult = 0;\n    unsigned int seed = TEA(16, imageIndex, g_RenderParams.subFrameIndex);\n\n    float3 rayOrigin, rayDirection;\n\n    float hitT = 1.#INF;\n\n    uint spp = g_RenderParams.denoiserMode != DenoiserMode::None ? 1 : g_RenderParams.spp;\n\n    const uint32_t shflIdxRndOffset = Rnd(seed) * spp;\n    const int strataCount = sqrt(spp);\n\n    for (int i = 0; i < spp; i++)\n    {\n        float2 subPixelJitter = g_RenderParams.denoiserMode != DenoiserMode::None ? g_RenderParams.jitter :\n            (RandomStrat(i, strataCount, seed) - 0.5f);\n        int subpixelIndex2nd = (i + shflIdxRndOffset) % spp;\n\n        SetupPrimaryRay(pixelPosition, subPixelJitter, rayOrigin, rayDirection);\n\n        float3 pathWeight = 1.f;\n\n        if (g_RenderParams.shadingMode == ShadingMode::PT)\n        {\n            float3 pathContribution = 0.f;\n            float pdf = 0.f;\n\n            for (uint32_t bounce = 0; bounce < g_RenderParams.ptMaxBounces; bounce++)\n            {\n                TraceRadiancePT(bounce, seed, subpixelIndex2nd,\n                    rayOrigin, rayDirection, pathWeight, pathContribution, pdf, hitT);\n\n                if (isinf(hitT) || !any(pathWeight))\n                {\n                    break;\n                }\n\n                if (g_RenderParams.denoiserMode == DenoiserMode::None)\n                {\n                    // Russian Roulette \n                    if (bounce > 1)\n                    {\n                        float rrProbability = min(0.95f, Luminance(pathWeight));\n                        if (rrProbability < Rnd(seed))\n                            break;\n                        else\n                            pathWeight /= rrProbability;\n                    }\n                }\n            }\n            result += pathContribution;\n        }\n        else if (g_RenderParams.shadingMode == ShadingMode::AO)\n        {\n            TraceRadianceAO(seed, subpixelIndex2nd, rayOrigin, rayDirection, pathWeight, hitT);\n            result += pathWeight;\n        }\n        else\n        {\n            TraceRadiancePR(rayOrigin, rayDirection, pathWeight, hitT);\n            result += pathWeight;\n        }\n    }\n    result /= spp;\n\n    float4 accumVal;\n    if (g_RenderParams.denoiserMode != DenoiserMode::None)\n    {\n        accumVal = float4(result, 1.f);\n    }\n    else\n    {\n        accumVal = u_Accum[pixelPosition];\n        accumVal = lerp(accumVal, float4(result, 1.f), 1.f / float(g_RenderParams.subFrameIndex + 1));\n    }\n\n#if defined(TARGET_D3D12)\n    if (g_RenderParams.enableTimeView)\n    {\n        uint endTime = NvGetSpecial(NV_SPECIALOP_GLOBAL_TIMER_LO);\n        uint deltaTime = TimeDiff(startTime, endTime);\n\n        if (g_RenderParams.subFrameIndex == 0)\n        {\n            if (pixelPosition.x == 0 && pixelPosition.y == 0)\n            {\n                u_TimeviewBuffer[0] = 0xffffffff;\n                u_TimeviewBuffer[1] = 0;\n            }\n        }\n        else if (g_RenderParams.subFrameIndex == 1)\n        {\n            int orig;\n            InterlockedMin(u_TimeviewBuffer[0], deltaTime, orig);\n            InterlockedMax(u_TimeviewBuffer[1], deltaTime, orig);\n        }\n        else\n        {\n            uint minValue = u_TimeviewBuffer[0];\n            uint maxValue = u_TimeviewBuffer[1];\n\n            float3 result = Temperature(((float)deltaTime) / ((float)maxValue - minValue));\n            accumVal = u_Accum[pixelPosition];\n            accumVal = lerp(accumVal, float4(result, 1.f), 1.f / float(g_RenderParams.subFrameIndex - 1));\n        }\n    }\n#endif\n\n    u_Accum[pixelPosition] = accumVal;\n}\n"
  },
  {
    "path": "demo/shaders/self_intersection_avoidance.hlsli",
    "content": "﻿//\n// SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n// SPDX-License-Identifier: BSD-3-Clause\n// \n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n// \n// 1. Redistributions of source code must retain the above copyright notice, this\n// list of conditions and the following disclaimer.\n// \n// 2. Redistributions in binary form must reproduce the above copyright notice,\n// this list of conditions and the following disclaimer in the documentation\n// and/or other materials provided with the distribution.\n// \n// 3. Neither the name of the copyright holder nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n// \n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n// \n\n// Compute the object and world space position and normal corresponding to a triangle hit point.\n// Compute a safe spawn point offset along the normal in world space to prevent self intersection of secondary rays.\nvoid SafeSpawnPoint(\n    out float3     outObjPosition, // position in object space\n    out float3     outWldPosition, // position in world space    \n    out float3     outObjNormal,   // unit length surface normal in object space\n    out float3     outWldNormal,   // unit length surface normal in world space\n    out float      outWldOffset,   // safe offset for spawn position in world space\n    const float3   v0,             // spawn triangle vertex 0 in object space\n    const float3   v1,             // spawn triangle vertex 1 in object space\n    const float3   v2,             // spawn triangle vertex 2 in object space\n    const float2   bary,           // spawn barycentrics\n    const float3x4 o2w,            // spawn instance object-to-world transformation\n    const float3x4 w2o )           // spawn instance world-to-object transformation\n{\n    precise float3 edge1 = v1 - v0;\n    precise float3 edge2 = v2 - v0;\n\n    // interpolate triangle using barycentrics.\n    // add in base vertex last to reduce object space error.\n    precise float3 objPosition = v0 + mad( bary.x, edge1, mul( bary.y, edge2 ) );\n    float3 objNormal = cross( edge1, edge2 );\n\n    // transform object space position.\n    // add in translation last to reduce world space error.\n    precise float3 wldPosition;\n    wldPosition.x = o2w._m03 +\n        mad( o2w._m00, objPosition.x,\n            mad( o2w._m01, objPosition.y,\n                mul( o2w._m02, objPosition.z ) ) );\n    wldPosition.y = o2w._m13 +\n        mad( o2w._m10, objPosition.x,\n            mad( o2w._m11, objPosition.y,\n                mul( o2w._m12, objPosition.z ) ) );\n    wldPosition.z = o2w._m23 +\n        mad( o2w._m20, objPosition.x,\n            mad( o2w._m21, objPosition.y,\n                mul( o2w._m22, objPosition.z ) ) );\n\n    // transform normal to world - space using\n    // inverse transpose matrix\n    float3 wldNormal = mul( transpose( ( float3x3 )w2o ), objNormal );\n\n    // normalize world space normal\n    const float wldScale = rsqrt( dot( wldNormal, wldNormal ) );\n    wldNormal = mul( wldScale, wldNormal );\n\n    const float c0 = 5.9604644775390625E-8f;\n    const float c1 = 1.788139769587360206060111522674560546875E-7f;\n\n    const float3 extent3 = abs( edge1 ) + abs( edge2 ) + abs( edge1 - edge2 );\n    const float  extent = max( max( extent3.x, extent3.y ), extent3.z );\n\n    // bound object space error due to reconstruction and intersection\n    float3 objErr = mad( c0, abs( v0 ), mul( c1, extent ) );\n\n    // bound world space error due to object to world transform\n    const float c2 = 1.19209317972490680404007434844970703125E-7f;\n    float3 wldErr = mad( c1, mul( abs( ( float3x3 )o2w ), abs( objPosition ) ), mul( c2, abs( transpose( o2w )[3] ) ) );\n\n    // bound object space error due to world to object transform\n    objErr = mad( c2, mul( abs( w2o ), float4( abs( wldPosition ), 1 ) ), objErr );\n\n    // compute world space self intersection avoidance offset\n    float wldOffset = dot( wldErr, abs( wldNormal ) );\n    float objOffset = dot( objErr, abs( objNormal ) );\n\n    wldOffset = mad( wldScale, objOffset, wldOffset );\n\n    // output safe front and back spawn points\n    outObjPosition = objPosition;\n    outWldPosition = wldPosition;\n    outObjNormal = normalize( objNormal );\n    outWldNormal = wldNormal;\n    outWldOffset = wldOffset;\n}\n\n// Offset the world-space position along the world-space normal by the safe offset to obtain the safe spawn point.\nfloat3 SafeSpawnPoint(\n    const float3 position,\n    const float3 normal,\n    const float  offset )\n{\n    precise float3 p = mad( offset, normal, position );\n    return p;\n}\n"
  },
  {
    "path": "demo/shaders/utils.hlsli",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef RTXMG_UTILS_HLSLI // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RTXMG_UTILS_HLSLI\n\n#include \"rtxmg/utils/constants.h\"\n\n#if ENABLE_DUMP_FLOAT\n#define DUMP_FLOAT(idx, x)  { u_DebugTex##idx[DispatchRaysIndex().xy] = float4(x, 0, 0, 1); }\n#define DUMP_FLOAT2(idx, x) { u_DebugTex##idx[DispatchRaysIndex().xy] = float4(x, 0, 1); }\n#define DUMP_FLOAT3(idx, x) { u_DebugTex##idx[DispatchRaysIndex().xy] = float4(x, 1); }\n#define DUMP_FLOAT4(idx, x) { u_DebugTex##idx[DispatchRaysIndex().xy] = x; }\n#else\n#define DUMP_FLOAT(idx, x)  \n#define DUMP_FLOAT2(idx, x)\n#define DUMP_FLOAT3(idx, x)\n#define DUMP_FLOAT4(idx, x)\n#endif\n\n#define DEBUG_ARGS \\\n    RWBuffer<float4> u_Debug1, RWTexture2D<float4> u_DebugTex1, \\\n    RWBuffer<float4> u_Debug2, RWTexture2D<float4> u_DebugTex2, \\\n    RWBuffer<float4> u_Debug3, RWTexture2D<float4> u_DebugTex3, \\\n    RWBuffer<float4> u_Debug4, RWTexture2D<float4> u_DebugTex4\n#define DEBUG_PARAMS \\\n    u_Debug1, u_DebugTex1, \\\n    u_Debug2, u_DebugTex2, \\\n    u_Debug3, u_DebugTex3, \\\n    u_Debug4, u_DebugTex4\n\n// Ported from Omniverse Kit Renderer.\n// Clamps the contribution of ray in order to prevent fireflies\ninline\nfloat3 FireflyFiltering(float3 contribution, float fireflyMaxIntensity)\n{\n    if (fireflyMaxIntensity > 0.0f)\n    {\n        // !important! don't use CIE luminance functions here\n        // reason: Our fake spectral dispersion creates negative color components and the spectral sampling is tweaked\n        // such as that the expected value is (1, 1, 1) in sRGB. If now the firefly filter clamps those components\n        // unevenly (in particular it doesn't clamp values where the negative red minimizes the CIE luminance) it\n        // results in color shifts. So we use the average of the absolute value here, that seems to behave better in\n        // expectation.\n        float magicNumber = 1e-5f;\n        float3 absValue = float3(abs(contribution.x), abs(contribution.y), abs(contribution.z));\n        float averageRGB = (absValue.x + absValue.y + absValue.z) * (1.0f / 3.0f);\n        float clampedLuminance = min(averageRGB, fireflyMaxIntensity);\n        contribution = (averageRGB > magicNumber) ? (contribution * (clampedLuminance / averageRGB)) : contribution;\n    }\n    contribution.x = max(0.f, contribution.x);\n    contribution.y = max(0.f, contribution.y);\n    contribution.z = max(0.f, contribution.z);\n    return contribution;\n}\n\ninline float ModPositive(float dividend, float divisor)\n{\n    float result = fmod(dividend, divisor);\n    if (result < 0)\n        result += divisor;\n    return result;\n}\n\ninline float3 HSVToRGB(float3 c)\n{\n    if (c.y == 0.0f)\n        return float3(c.zzz);\n\n    float h = ModPositive(c.x, 360.0f) / 60.0f;\n    int i = int(floor(h));\n    \n    float f = h - i;\n    float p = c.z * (1.0f - c.y);\n    float q = c.z * (1.0f - c.y * f);\n    float t = c.z * (1.0f - c.y * (1.0f - f));\n\n    switch (i)\n    {\n        case 0:\n            return float3(c.z, t, p);\n        case 1:\n            return float3(q, c.z, p);\n        case 2:\n            return float3(p, c.z, t);\n        case 3:\n            return float3(p, q, c.z);\n        case 4:\n            return float3(t, p, c.z);\n        case 5:\n            return float3(c.z, p, q);\n        default:\n            return float3(0.0, 0.0f, 0.f);\n    }\n}\n\ninline uint JenkinsHash(uint a)\n{\n    // http://burtleburtle.net/bob/hash/integer.html\n    a = (a + 0x7ed55d16) + (a << 12);\n    a = (a ^ 0xc761c23c) ^ (a >> 19);\n    a = (a + 0x165667b1) + (a << 5);\n    a = (a + 0xd3a2646c) ^ (a << 9);\n    a = (a + 0xfd7046c5) + (a << 3);\n    a = (a ^ 0xb55a4f09) ^ (a >> 16);\n    return a;\n}\n\ninline uint32_t MurmurAdd(uint32_t hash, uint32_t element)\n{\n    element *= 0xcc9e2d51;\n    element = (element << 15) | (element >> (32 - 15));\n    element *= 0x1b873593;\n    hash ^= element;\n    hash = (hash << 13) | (hash >> (32 - 13));\n    hash = hash * 5 + 0xe6546b64;\n    return hash;\n}\n\ninline uint32_t MurmurMix(uint32_t hash)\n{\n    hash ^= hash >> 16;\n    hash *= 0x85ebca6b;\n    hash ^= hash >> 13;\n    hash *= 0xc2b2ae35;\n    hash ^= hash >> 16;\n    return hash;\n}\n\ninline float3 UintToColor(uint x)\n{\n    uint xHashed = JenkinsHash(x);\n    float hue = (float(xHashed & 0xffff) / float(0xffffu)) * 360.0f;\n    float sat = lerp(0.7f, 1.0f, saturate(float((xHashed >> 16) & 0xff) / float(0xffu)));\n    float value = lerp(0.5f, 1.0f, saturate(float((xHashed >> 24) & 0xff) / float(0xffu)));\n    \n    return HSVToRGB(float3(hue, sat, value));\n}\n\nfloat Luminance(float3 rgb)\n{\n    return dot(rgb, float3(0.2126f, 0.7152f, 0.0722f));\n}\n\ninline float SafeLength(float3 v)\n{\n    const float3 n = float3(abs(v.x), abs(v.y), abs(v.z));\n    // avoid overflow by dividing by the max component\n    const float m = max(n.x, max(n.y, n.z));\n    const float x = n.x / m;\n    const float y = n.y / m;\n    const float z = n.z / m;\n    // scale back by the max component\n    const float len = m * (sqrt(x * x + y * y + z * z));\n    return len;\n}\n\ninline float3 SafeNormalize(float3 n)\n{\n    // avoid division by 0 by adding numeric_limits::min\n    const float len = SafeLength(n) + FLT_MIN;\n    return n * (1.f / len);\n}\n\ninline float2 ConcentricSampleDisk(float2 u)\n{\n    // Map uniform random numbers to $[-1,1]^2$\n    const float2 uOffset = 2.f * u - float2(1.f, 1.f);\n\n    // Handle degeneracy at the origin\n    if (uOffset.x == 0.f && uOffset.y == 0.f)\n        return float2(0.f, 0.f);\n\n    // Apply concentric mapping to point\n    float theta, r;\n    if (abs(uOffset.x) > abs(uOffset.y))\n    {\n        r = uOffset.x;\n        theta = PI_OVER_4 * (uOffset.y / uOffset.x);\n    }\n    else\n    {\n        r = uOffset.y;\n        theta = PI_OVER_2 - PI_OVER_4 * (uOffset.x / uOffset.y);\n    }\n    return r * float2(cos(theta), sin(theta));\n}\n\ninline float3 CosineSampleHemisphere(float2 u)\n{\n    const float2 d = ConcentricSampleDisk(u);\n    const float  z = sqrt(max(0.0f, 1.f - d.x * d.x - d.y * d.y));\n    return float3(d.x, d.y, z);\n}\n\ninline float CosineHemispherePdf(float cosTheta)\n{\n    return cosTheta * M_1_PIf;\n}\n\ninline float3 UniformSampleHemisphere(float2 u)\n{\n    const float z = u.x;\n    const float r = sqrt(max(0.f, 1.f - z * z));\n    const float phi = TWO_PI * u.y;\n    return float3(r * cos(phi), r * sin(phi), z);\n}\n\ninline float UniformHemiSpherePdf()\n{\n    return INV_2PI;\n}\n\ninline float PowerHeuristic(int nf, float fPdf, int ng, float gPdf)\n{\n    float f = nf * fPdf, g = ng * gPdf;\n    return (f * f) / (f * f + g * g);\n}\n\ninline uint32_t FloatToUInt(\nfloat _V, float _Scale)\n{\n    return (uint32_t)floor(_V * _Scale + 0.5f);\n}\n\n// this is a generalized lerp; map a value in [a, b] to the range [c, d] without clamping\ninline float Remap(float x, float a, float b, float c, float d)\n{\n    return c + (d - c) * (x - a) / (b - a);\n}\n\n\n/**\n* Octahedral normal vector encoding.\n* @param N must be a unit vector.\n* @return An octahedral vector on the [-1, +1] square.\n*/\n\ninline float2 UnitVectorToOctahedron(float3 N)\n{\n    const float d = dot(float3(1.f, 1.f, 1.f), float3(abs(N.x), abs(N.y), abs(N.z)));\n    N.x /= d;\n    N.y /= d;\n    if (N.z <= 0.f)\n    {\n        float2 signs;\n        signs.x = N.x >= 0.f ? 1.f : -1.f;\n        signs.y = N.y >= 0.f ? 1.f : -1.f;\n\n        const float2 k = (float2(1.f, 1.f) - float2(abs(N.y), abs(N.x))) * signs;\n\n        N.x = k.x;\n        N.y = k.y;\n    }\n    return float2(N.x, N.y);\n}\n/**\n* Octahedral normal vector decoding.\n* @param Oct An octahedral vector as returned from UnitVectorToOctahedron, on the [-1, +1] square.\n* @return Returns a unit vector.\n*/\ninline float3 OctahedronToUnitVector(float2 Oct)\n{\n    float3 N = float3(Oct.x, Oct.y, 1.f - dot(float2(1.f, 1.f), float2(abs(Oct.x), abs(Oct.y))));\n    if (N.z < 0)\n    {\n        float2 signs;\n        signs.x = N.x >= 0.f ? 1.f : -1.f;\n        signs.y = N.y >= 0.f ? 1.f : -1.f;\n\n        const float2 k = (float2(1.f, 1.f) - float2(abs(N.y), abs(N.x))) * signs;\n\n        N.x = k.x;\n        N.y = k.y;\n    }\n    return normalize(N);\n}\n\ninline uint32_t PackNormalizedVector(float3 x)\n{\n    float2 XY = UnitVectorToOctahedron(x);\n\n    XY = XY * float2( .5f, .5f ) + float2( .5f, .5f );\n\n    uint32_t X = FloatToUInt( saturate( XY.x ), ( 1 << 16 ) - 1 );\n    uint32_t Y = FloatToUInt( saturate( XY.y ), ( 1 << 16 ) - 1 );\n\n    uint32_t PackedOutput = X;\n    PackedOutput |= Y << 16;\n    return PackedOutput;\n}\n\ninline float3 UnpackNormalizedVector(uint32_t PackedInput)\n{\n    uint32_t  X = PackedInput & ((1 << 16) - 1);\n    uint32_t  Y = PackedInput >> 16;\n    float2 XY = float2(0.f, 0.f);\n    XY.x = (float) X / ((1 << 16) - 1);\n    XY.y = (float) Y / ((1 << 16) - 1);\n    XY = XY * float2(2.f, 2.f) + float2(-1.f, -1.f);\n    return OctahedronToUnitVector(XY);\n}\n\n\ninline float3 Temperature(const float t)\n{\n    const float b = t < 0.25f ? smoothstep(-0.25f, 0.25f, t) : 1.0f - smoothstep(0.25f, 0.5f, t);\n    const float g = t < 0.5f ? smoothstep(0.0f, 0.5f, t) : (t < 0.75f ? 1.0f : 1.0f - smoothstep(0.75f, 1.0f, t));\n    const float r = smoothstep(0.5f, 0.75f, t);\n    return float3(r, g, b);\n}\n\n\ninline int GetImageIndex()\n{\n    return DispatchRaysIndex().y * DispatchRaysDimensions().x + DispatchRaysIndex().x;\n}\n\nunsigned int TEA(unsigned int N, unsigned int val0, unsigned int val1)\n{\n    unsigned int v0 = val0;\n    unsigned int v1 = val1;\n    unsigned int s0 = 0;\n\n    for (unsigned int n = 0; n < N; n++)\n    {\n        s0 += 0x9e3779b9;\n        v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4);\n        v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e);\n    }\n\n    return v0;\n}\n// Generate random unsigned int in [0, 2^24)\nunsigned int LCG(inout unsigned int prev)\n{\n    const unsigned int LCG_A = 1664525u;\n    const unsigned int LCG_C = 1013904223u;\n    prev = (LCG_A * prev + LCG_C);\n    return prev & 0x00FFFFFF;\n}\n\n// Generate random float in [0, 1)\nfloat Rnd(inout unsigned int prev)\n{\n    return ((float) LCG(prev) / (float) 0x01000000);\n}\n\nfloat2 RandomStrat(const int sampleOffset, const int strataCount,\n    inout unsigned int prev)\n{\n    const int sy = sampleOffset / strataCount;\n    const int sx = sampleOffset - sy * strataCount;\n    const float invStrata = 1.0f / strataCount;\n    return float2((sx + Rnd(prev)) * invStrata, (sy + Rnd(prev)) * invStrata);\n}\n\nstruct Onb\n{\n\n    // transform from the coordinate system represented by ONB\n    float3 ToWorld(float3 v) { return (v.x * m_tangent + v.y * m_binormal + v.z * m_normal); }\n\n    // transform to the coordinate system represented by ONB\n    float3 ToLocal(float3 v)\n    {\n        const float x = dot(v, m_tangent);\n        const float y = dot(v, m_binormal);\n        const float z = dot(v, m_normal);\n        return float3(x, y, z);\n    }\n\n    float3 m_tangent;\n    float3 m_binormal;\n    float3 m_normal;\n};\n\nOnb MakeOnb(float3 normal)\n{\n    Onb ret;\n    ret.m_normal = normal;\n\n    if (abs(normal.x) > abs(normal.z))\n    {\n        ret.m_binormal.x = -normal.y;\n        ret.m_binormal.y = normal.x;\n        ret.m_binormal.z = 0.f;\n    }\n    else\n    {\n        ret.m_binormal.x = 0.f;\n        ret.m_binormal.y = -normal.z;\n        ret.m_binormal.z = normal.y;\n    }\n\n    ret.m_binormal = SafeNormalize(ret.m_binormal);\n    ret.m_tangent = cross(ret.m_binormal, normal);\n\n    return ret;\n}\n\n#endif // RTXMG_UTILS_HLSLI"
  },
  {
    "path": "demo/shaders/zrender.hlsl",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma pack_matrix(row_major)\n\n#include \"zrender_params.h\"\n#include <donut/shaders/bindless.h>\n#include <donut/shaders/packing.hlsli>\n#include <donut/shaders/surface.hlsli>\n#include <donut/shaders/utils.hlsli>\n#include <donut/shaders/binding_helpers.hlsli>\n\nConstantBuffer<ZRenderParams> g_RenderParams : register(b0);\n\nRWTexture2D<float> u_Output    : register(u0);\n\nRaytracingAccelerationStructure SceneBVH : register(t0);\n\nvoid GetRay(uint2 pixelPosition, float2 subpixelJitter, uint2 dims, out float3 rayOrigin, out float3 rayDirection)\n{\n    float2 d = ((float2(pixelPosition) + subpixelJitter) / float2(dims)) * 2.f - 1.f;\n\n    d *= float2(1, -1);\n\n    rayOrigin = g_RenderParams.eye;\n    rayDirection = normalize(d.x * g_RenderParams.U + d.y * g_RenderParams.V + g_RenderParams.W);\n}\n\n[shader(\"miss\")] void Miss(inout ZRayPayload payload\n    : SV_RayPayload)\n{\n    payload.hitT = 1.#INF;\n}\n\n[shader(\"closesthit\")]void ClosestHit(inout ZRayPayload payload\n    : SV_RayPayload, in BuiltInTriangleIntersectionAttributes attrib\n    : SV_IntersectionAttributes)\n{\n    payload.hitT = min(payload.hitT, RayTCurrent());\n}\n\n[shader(\"raygeneration\")]void RayGen()\n{\n    uint2 pixelPosition = DispatchRaysIndex().xy;\n    uint2 dims = DispatchRaysDimensions().xy;\n\n    float2 subpixelJitter = float2(0.5f, 0.5f);\n\n    float3 rayOrigin, rayDirection;\n    GetRay(pixelPosition, subpixelJitter, dims, rayOrigin, rayDirection);\n\n    RayDesc ray = { rayOrigin, 0.f, rayDirection, 1e38 };\n\n    ZRayPayload payload = { 1.#INF };\n\n    // Only instances with the second InstanceMask bit set will be intersected.\n    // This prevents animated objects from being written to the depth buffer.\n    TraceRay(SceneBVH, RAY_FLAG_NONE, 2, 0, 0, 0, ray, payload);\n\n    float hitT = payload.hitT;\n    float3 hitP = rayOrigin + rayDirection * hitT;\n    float depth = (!isinf(hitT)) ? dot(normalize(g_RenderParams.W), hitP - g_RenderParams.eye) : 1.#INF;\n\n    u_Output[pixelPosition] = depth;\n}\n"
  },
  {
    "path": "demo/shaders.cfg",
    "content": "shaders/blit.hlsl -T cs\nshaders/lerp_keyframes.hlsl -T cs\nshaders/motion_vectors.hlsl -T cs --compilerOptionsSPIRV \"-fvk-bind-resource-heap 0 1\" -D MVEC_DISPLACEMENT={MVEC_DISPLACEMENT_FROM_SUBD_EVAL,MVEC_DISPLACEMENT_FROM_MATERIAL}\nshaders/rtxmg_demo_path_tracer.hlsl -T lib --compilerOptionsSPIRV \"-fvk-bind-resource-heap 0 1\" -D VERTEX_NORMALS={0,1}\nshaders/zrender.hlsl -T lib "
  },
  {
    "path": "demo/trackball.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include <GLFW/glfw3.h>\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cmath>\n\n\n#include \"trackball.h\"\n\nenum class KeyboardControls : uint8_t\n{\n    MoveUp = 0,\n    MoveDown,\n    MoveLeft,\n    MoveRight,\n    MoveForward,\n    MoveBackward,\n    RollLeft,\n    RollRight,\n    SpeedUp,\n    SlowDown,\n    OrbitMode,\n    Count\n};\n\nstatic std::array<bool, int(KeyboardControls::Count)> keyboardState = { false };\n\nenum class MouseButtons : uint8_t { Left = 0, Middle, Right, Count };\n\nstatic std::array<bool, int(MouseButtons::Count)> mouseButtonsState = { false };\n\nvoid Trackball::KeyboardUpdate(int key, int code, int action, int mods)\n{\n    static const std::array<std::pair<int, int>, 18> keyboardMap = { {\n        {GLFW_KEY_Q, int(KeyboardControls::MoveDown)},\n        {GLFW_KEY_E, int(KeyboardControls::MoveUp)},\n        {GLFW_KEY_A, int(KeyboardControls::MoveLeft)},\n        {GLFW_KEY_D, int(KeyboardControls::MoveRight)},\n        //        { GLFW_KEY_DOWN,          int(KeyboardControls::MoveDown) },\n        //        { GLFW_KEY_UP,            int(KeyboardControls::MoveUp) },\n        //        { GLFW_KEY_LEFT,          int(KeyboardControls::MoveLeft) },\n        //        { GLFW_KEY_RIGHT,         int(KeyboardControls::MoveRight) },\n        {GLFW_KEY_W, int(KeyboardControls::MoveForward)},\n        {GLFW_KEY_S, int(KeyboardControls::MoveBackward)},\n        {GLFW_KEY_Z, int(KeyboardControls::RollLeft)},\n        {GLFW_KEY_X, int(KeyboardControls::RollRight)},\n        {GLFW_KEY_LEFT_SHIFT, int(KeyboardControls::SpeedUp)},\n        {GLFW_KEY_RIGHT_SHIFT, int(KeyboardControls::SpeedUp)},\n        {GLFW_KEY_LEFT_CONTROL, int(KeyboardControls::SlowDown)},\n        {GLFW_KEY_RIGHT_CONTROL, int(KeyboardControls::SlowDown)},\n        {GLFW_KEY_LEFT_ALT, int(KeyboardControls::OrbitMode)},\n        {GLFW_KEY_RIGHT_ALT, int(KeyboardControls::OrbitMode)},\n    } };\n\n    auto it = std::find_if(\n        keyboardMap.begin(), keyboardMap.end(),\n        [&key](std::pair<int, int> control) { return control.first == key; });\n\n    if (it != keyboardMap.end())\n        keyboardState[it->second] = (action == GLFW_PRESS || action == GLFW_REPEAT);\n}\n\nvoid Trackball::MouseButtonUpdate(int button, int action, int)\n{\n    static const std::array<std::pair<int, int>, 3> mouseButtonsdMap = { {\n        {GLFW_MOUSE_BUTTON_LEFT, int(MouseButtons::Left)},\n        {GLFW_MOUSE_BUTTON_MIDDLE, int(MouseButtons::Middle)},\n        {GLFW_MOUSE_BUTTON_RIGHT, int(MouseButtons::Right)},\n    } };\n\n    assert(action == GLFW_PRESS || action == GLFW_RELEASE);\n\n    auto it = std::find_if(mouseButtonsdMap.begin(), mouseButtonsdMap.end(),\n        [&button](std::pair<int, int> control)\n        {\n            return control.first == button;\n        });\n\n    if (it != mouseButtonsdMap.end())\n    {\n        mouseButtonsState[it->second] = action == GLFW_PRESS;\n        if (action == GLFW_RELEASE)\n            m_performTracking = false;\n    }\n}\n\nvoid Trackball::Animate(float deltaT)\n{\n    if (!m_roamMode)\n        return;\n\n    float moveSpeed = deltaT * m_moveSpeed;\n    float rollSpeed = deltaT * m_rollSpeed;\n\n    if (keyboardState[int(KeyboardControls::SpeedUp)])\n    {\n        moveSpeed *= 3.f;\n        rollSpeed *= 3.f;\n    }\n\n    if (keyboardState[int(KeyboardControls::SlowDown)])\n    {\n        moveSpeed *= .1f;\n        rollSpeed *= .1f;\n    }\n\n    auto const& basis = m_camera->GetBasis();\n\n    if (keyboardState[int(KeyboardControls::MoveForward)])\n        m_camera->Translate(m_camera->GetDirection() * moveSpeed);\n    if (keyboardState[int(KeyboardControls::MoveBackward)])\n        m_camera->Translate(-m_camera->GetDirection() * moveSpeed);\n\n    if (keyboardState[int(KeyboardControls::MoveLeft)])\n        m_camera->Translate(-normalize(basis[0]) * moveSpeed);\n    if (keyboardState[int(KeyboardControls::MoveRight)])\n        m_camera->Translate(normalize(basis[0]) * moveSpeed);\n\n    if (keyboardState[int(KeyboardControls::MoveUp)])\n        m_camera->Translate(normalize(basis[1]) * moveSpeed);\n    if (keyboardState[int(KeyboardControls::MoveDown)])\n        m_camera->Translate(-normalize(basis[1]) * moveSpeed);\n\n    if (keyboardState[int(KeyboardControls::RollLeft)])\n        m_camera->Roll(rollSpeed);\n    if (keyboardState[int(KeyboardControls::RollRight)])\n        m_camera->Roll(-rollSpeed);\n}\n\nvoid Trackball::MouseTrackingUpdate(int2 pos, int2 canvasSize)\n{\n    if (!m_performTracking)\n    {\n        ReinitOrientationFromCamera();\n        m_performTracking = true;\n        return;\n    }\n\n    m_delta = pos - m_prevPos;\n    m_prevPos = pos;\n    m_pos = pos;\n\n    UpdateCamera(pos, canvasSize);\n}\n\nbool Trackball::MouseWheelUpdate(int dir)\n{\n    Zoom(dir);\n    return true;\n}\n\nfloat3 Trackball::GetCameraDirection() const\n{\n    // use lat/long for view definition\n    float3 localDir;\n    localDir.x = cos(m_latitude) * sin(m_longitude);\n    localDir.y = cos(m_latitude) * cos(m_longitude);\n    localDir.z = sin(m_latitude);\n\n    return m_u * localDir.x + m_v * localDir.y + m_w * localDir.z;\n}\n\nvoid Trackball::ApplyGimbalLock()\n{\n    if (!m_gimbalLock)\n    {\n        ReinitOrientationFromCamera();\n        if (m_camera->HasChanged())\n            m_camera->SetUp(m_w);\n    }\n}\n\nstatic inline float3 getUnitVector(int2 pos, int2 canvas)\n{\n    float3 ret;\n    ret.x = 2.0f * float(pos.x) / float(canvas.x) - 1.0f;\n    ret.y = 2.0f * float(pos.y) / float(canvas.y) - 1.0f;\n    ret.z = 0.0f;\n    float length = ret.x * ret.x + ret.y * ret.y;\n    if (length <= 1)\n    {\n        ret.z = sqrt(1.0f - length);\n        length += ret.z * ret.z;\n    }\n    else\n    {\n        float norm = 1.0f / sqrt(length);\n        ret.x *= norm;\n        ret.y *= norm;\n    }\n    return ret;\n}\n\nvoid Trackball::UpdateCamera(int2 pos, int2 canvas)\n{\n    if (m_roamMode == false || keyboardState[int(KeyboardControls::OrbitMode)])\n    {\n        if (mouseButtonsState[int(MouseButtons::Left)])\n        {\n            m_latitude = radians(std::min(89.0f, std::max(-89.0f, degrees(m_latitude) + 0.5f * m_delta.y)));\n            m_longitude = radians(fmod(degrees(m_longitude) - 0.5f * m_delta.x, 360.0f));\n\n            float3 dirWS = GetCameraDirection();\n\n            m_camera->SetEye(m_camera->GetLookat() + dirWS * m_cameraEyeLookatDistance);\n\n            ApplyGimbalLock();\n        }\n        else if (mouseButtonsState[int(MouseButtons::Middle)])\n        {\n            float2 delta = { float(m_delta.x) / float(canvas.x),\n                            float(-m_delta.y) / float(canvas.y) };\n            m_camera->Pan(delta);\n        }\n        else if (mouseButtonsState[int(MouseButtons::Right)])\n        {\n            float factor = float(m_delta.x) / float(canvas.x) +\n                float(m_delta.y) / float(canvas.y);\n            constexpr float const dollySpeed = 2.f;\n            m_camera->Dolly(1.f - dollySpeed * factor);\n        }\n    }\n    else if (m_roamMode == true)\n    {\n        if (mouseButtonsState[int(MouseButtons::Left)])\n        {\n            m_latitude = radians(std::min(\n                89.0f, std::max(-89.0f, degrees(m_latitude) + 0.5f * m_delta.y)));\n            m_longitude =\n                radians(fmod(degrees(m_longitude) - 0.5f * m_delta.x, 360.0f));\n\n            float3 dirWS = GetCameraDirection();\n\n            m_camera->SetLookat(m_camera->GetEye() -\n                dirWS * m_cameraEyeLookatDistance);\n\n            ApplyGimbalLock();\n        }\n    }\n}\n\nvoid Trackball::SetReferenceFrame(const float3& u, const float3& v,\n    const float3& w)\n{\n    m_u = u;\n    m_v = v;\n    m_w = w;\n    float3 dirWS = -m_camera->GetDirection();\n    float3 dirLocal;\n    dirLocal.x = dot(dirWS, u);\n    dirLocal.y = dot(dirWS, v);\n    dirLocal.z = dot(dirWS, w);\n    m_longitude = atan2(dirLocal.x, dirLocal.y);\n    m_latitude = asin(dirLocal.z);\n}\n\nvoid Trackball::Zoom(int direction)\n{\n    float zoom = (direction > 0) ? 1 / m_zoomMultiplier : m_zoomMultiplier;\n    m_cameraEyeLookatDistance *= zoom;\n    const float3& lookat = m_camera->GetLookat();\n    const float3& eye = m_camera->GetEye();\n    m_camera->SetEye(lookat + (eye - lookat) * zoom);\n}\n\nvoid Trackball::ReinitOrientationFromCamera()\n{\n    auto const& basis = m_camera->GetBasis();\n    m_u = normalize(basis[0]);\n    m_v = normalize(basis[1]);\n    m_w = -normalize(basis[2]);\n    std::swap(m_v, m_w);\n    m_latitude = 0.0f;\n    m_longitude = 0.0f;\n    m_cameraEyeLookatDistance =\n        length(m_camera->GetLookat() - m_camera->GetEye());\n}\n"
  },
  {
    "path": "demo/trackball.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <array>\n\n#include <donut/core/math/math.h>\n\n#include \"rtxmg/scene/camera.h\"\n\nusing namespace donut::math;\n\nclass Camera;\n\nclass Trackball\n{\npublic:\n    void KeyboardUpdate(int key, int code, int action, int mods);\n    void MouseButtonUpdate(int button, int action, int mods);\n    void MouseTrackingUpdate(int2 pos, int2 canvasSize);\n    bool MouseWheelUpdate(int dir);\n\n    void Animate(float deltaT);\n\n    void Zoom(int direction);\n\n    float MoveSpeed() const { return m_moveSpeed; }\n    void SetMoveSpeed(float speed) { m_moveSpeed = speed; }\n\n    float RollSpeed() const { return m_rollSpeed; }\n    void SetRollSpeed(float speed) { m_rollSpeed = speed; }\n\n    // Set the camera that will be changed according to user input.\n    // Warning, this also initializes the reference frame of the trackball from\n    // the camera. The reference frame defines the orbit's singularity.\n    inline void SetCamera(Camera* camera)\n    {\n        m_camera = camera;\n        ReinitOrientationFromCamera();\n    }\n    inline const Camera* CurrentCamera() const { return m_camera; }\n\n    // Setting the gimbal lock to 'on' will fix the reference frame (i.e., the\n    // singularity of the trackball). In most cases this is preferred. For free\n    // scene exploration the gimbal lock can be turned off, which causes the\n    // trackball's reference frame to be Update on every camera Update (adopted\n    // from the camera).\n    bool GimbalLock() const { return m_gimbalLock; }\n    void SetGimbalLock(bool val) { m_gimbalLock = val; }\n\n    // Adopts the reference frame from the camera.\n    // Note that the reference frame of the camera usually has a different 'up'\n    // than the 'up' of the camera. Though, typically, it is desired that the\n    // trackball's reference frame aligns with the actual up of the camera.\n    void ReinitOrientationFromCamera();\n\n    // Specify the frame of the orbit that the camera is orbiting around.\n    // The important bit is the 'up' of that frame as this is defines the\n    // singularity. Here, 'up' is the 'w' component. Typically you want the up of\n    // the reference frame to align with the up of the camera. However, to be able\n    // to really freely move around, you can also constantly Update the reference\n    // frame of the trackball. This can be done by calling\n    // reinitOrientationFromCamera(). In most cases it is not required though (set\n    // the frame/up once, leave it as is).\n    void SetReferenceFrame(const float3& u, const float3& v, const float3& w);\n\n    // In 'roam' mode, the mouse moves the camera in first-person mode and the\n    // 'alt' key must be held to move in third-person mode (orbit). 'roam' mode\n    // off disables first person movement (keyboard & mouse), but orbiting no\n    // longer requires holding the 'alt' key.\n    void SetRoamMode(bool roam) { m_roamMode = roam; }\n    bool GetRoamMode() const { return m_roamMode; }\n\nprivate:\n    float3 GetCameraDirection() const;\n\n    void ApplyGimbalLock();\n\n    void UpdateCamera(int2 pos, int2 canvasSize);\n\nprivate:\n    bool m_roamMode = true;\n    bool m_gimbalLock = true;\n    Camera* m_camera = nullptr;\n    float m_cameraEyeLookatDistance = 0.0f;\n    float m_zoomMultiplier = 1.1f;\n    float m_moveSpeed = 1.0f;\n    float m_rollSpeed = 180.f / 5.f;\n\n    float m_latitude = 0.0f;  // in radians\n    float m_longitude = 0.0f; // in radians\n\n    // mouse tracking\n    bool m_performTracking = false;\n    int2 m_pos = { 0, 0 };\n    int2 m_prevPos = { 0, 0 };\n    int2 m_delta = { 0, 0 };\n\n    // trackball computes camera orientation (eye, lookat) using\n    // latitude/longitude with respect to this frame local frame for trackball\n    float3 m_u = { 0.0f, 0.0f, 0.0f };\n    float3 m_v = { 0.0f, 0.0f, 0.0f };\n    float3 m_w = { 0.0f, 0.0f, 0.0f };\n};\n\n#pragma once\n"
  },
  {
    "path": "demo/zrender_params.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef Z_RENDER_PARAMS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define Z_RENDER_PARAMS_H\n\n#ifdef __cplusplus\n#include <donut/core/math/math.h>\nusing namespace donut::math;\n#endif\n\nstruct ZRenderParams\n{\n    float3 eye;\n    float pad;\n\n    float3 U;\n    int pad2;\n\n    float3 V;\n    int pad3;\n\n    float3 W;\n    float pad4;\n};\n\nstruct ZRayPayload\n{\n    float hitT;\n};\n\n\n#endif // Z_RENDER_PARAMS_H\n"
  },
  {
    "path": "demo/zrenderer.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n\n#include \"zrender_params.h\"\n#include \"zrenderer.h\"\n\n\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/core/math/math.h>\n#include <nvrhi/utils.h>\n\n#include <filesystem>\n#include <string>\n#include <sstream>\n\nusing namespace donut;\nusing namespace donut::math;\n\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/profiler/statistics.h\"\n#include \"rtxmg/scene/camera.h\"\n\nZRenderer::ZRenderer(std::shared_ptr<engine::ShaderFactory> shaderFactory)\n    : m_shaderFactory(shaderFactory)\n{\n\n}\n\nZRenderer::~ZRenderer() {}\n\nvoid ZRenderer::BuildPipeline(nvrhi::IDevice* device)\n{\n    nvrhi::BindingLayoutDesc globalBindingLayoutDesc;\n    globalBindingLayoutDesc.visibility = nvrhi::ShaderType::All;\n    globalBindingLayoutDesc.bindings = {\n        nvrhi::BindingLayoutItem::VolatileConstantBuffer(0), // z render constants\n        nvrhi::BindingLayoutItem::RayTracingAccelStruct(0),  // TLAS\n        nvrhi::BindingLayoutItem::Texture_UAV(0),          // output\n    };\n    m_bindingLayout = device->createBindingLayout(globalBindingLayoutDesc);\n\n    m_params =\n        device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n            sizeof(ZRenderParams), \"Z Render Params\",\n            engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_shaderLibrary =\n        m_shaderFactory->CreateShaderLibrary(\"rtxmg_demo/zrender.hlsl\", nullptr);\n\n    if (!m_shaderLibrary)\n    {\n        log::fatal(\"Failed to create z render shader library\");\n    }\n\n    nvrhi::rt::PipelineDesc pipelineDesc;\n    pipelineDesc.globalBindingLayouts = { m_bindingLayout };\n    pipelineDesc.shaders =\n    {\n        {\"\",m_shaderLibrary->getShader(\"RayGen\", nvrhi::ShaderType::RayGeneration),nullptr},\n        {\"\", m_shaderLibrary->getShader(\"Miss\", nvrhi::ShaderType::Miss),nullptr},\n    };\n\n    pipelineDesc.hitGroups = { {\n        \"ZHitGroup\",\n        m_shaderLibrary->getShader(\"ClosestHit\", nvrhi::ShaderType::ClosestHit),\n        nullptr, // anyHit\n        nullptr, // intersectionShader\n        nullptr, // bindingLayout\n        false    // isProceduralPrimitive\n    } };\n\n    pipelineDesc.maxPayloadSize = sizeof(ZRayPayload);\n    pipelineDesc.maxRecursionDepth = 2;\n\n    m_rayPipeline = device->createRayTracingPipeline(pipelineDesc);\n\n    if (!m_rayPipeline)\n    {\n        log::fatal(\"Failed to create Z ray tracing pipeline\");\n    }\n\n    m_shaderTable = m_rayPipeline->createShaderTable();\n\n    if (!m_shaderTable)\n    {\n        log::fatal(\"Failed to create Z shader table\");\n    }\n\n    m_shaderTable->setRayGenerationShader(\"RayGen\");\n    m_shaderTable->addHitGroup(\"ZHitGroup\");\n    m_shaderTable->addMissShader(\"Miss\");\n}\n\nvoid ZRenderer::Render(Camera& camera, nvrhi::rt::AccelStructHandle tlas, \n    nvrhi::ITexture* zbuffer, nvrhi::ICommandList* commandList)\n{\n    if (!m_rayPipeline)\n    {\n        BuildPipeline(commandList->getDevice());\n    }\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"Z Render Pass\");\n    \n    nvrhi::BindingSetDesc bindingSetDesc;\n    bindingSetDesc.bindings = {\n        nvrhi::BindingSetItem::ConstantBuffer(0, m_params),\n        nvrhi::BindingSetItem::RayTracingAccelStruct(0, tlas),\n        nvrhi::BindingSetItem::Texture_UAV(0, zbuffer),\n    };\n\n    m_bindingSet =\n        commandList->getDevice()->createBindingSet(bindingSetDesc, m_bindingLayout);\n\n    ZRenderParams params;\n    auto const& [u, v, w] = camera.GetBasis();\n\n    params.eye = camera.GetEye();\n\n    params.U = u;\n    params.V = v;\n    params.W = w;\n\n    commandList->writeBuffer(m_params, &params, sizeof(params));\n\n    nvrhi::rt::State state;\n    state.shaderTable = m_shaderTable;\n    state.bindings = { m_bindingSet };\n    commandList->setRayTracingState(state);\n\n    nvrhi::rt::DispatchRaysArguments args;\n    args.width = zbuffer->getDesc().width;\n    args.height = zbuffer->getDesc().height;\n\n    stats::frameSamplers.zRenderPassTime.Start(commandList);\n    commandList->dispatchRays(args);\n    stats::frameSamplers.zRenderPassTime.Stop();\n}\n\n"
  },
  {
    "path": "demo/zrenderer.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <donut/core/math/math.h>\n#include <donut/engine/BindingCache.h>\n#include <donut/engine/SceneGraph.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/engine/TextureCache.h>\n#include <donut/engine/View.h>\n#include <nvrhi/nvrhi.h>\n\n#include \"rtxmg/hiz/zbuffer.h\"\n\n#include \"rtxmg/utils/buffer.h\"\n\nusing namespace donut::engine;\nusing namespace donut::math;\n\nclass Camera;\n\nclass ZRenderer\n{\npublic:\n\n    ZRenderer(std::shared_ptr<ShaderFactory> shaderFactory);\n    ~ZRenderer();\n\n    void Render(Camera& camera, nvrhi::rt::AccelStructHandle tlas,\n        nvrhi::ITexture* zbuffer, nvrhi::ICommandList* commandList);\n\nprivate:\n    void BuildPipeline(nvrhi::IDevice* device);\n\n    std::shared_ptr<ShaderFactory> GetShaderFactory() const\n    {\n        return m_shaderFactory;\n    }\n\nprivate:\n    nvrhi::rt::PipelineHandle m_rayPipeline = nullptr;\n    nvrhi::rt::ShaderTableHandle m_shaderTable;\n    nvrhi::BindingLayoutHandle m_bindingLayout;\n    nvrhi::BindingSetHandle m_bindingSet = nullptr;\n\n    nvrhi::BufferHandle m_params;\n\n    nvrhi::ShaderLibraryHandle m_shaderLibrary;\n\n    std::shared_ptr<ShaderFactory> m_shaderFactory;\n};\n"
  },
  {
    "path": "docs/QuickStart.md",
    "content": "# Quick Start Guide\n\nSee [README](../README.md) for cloning & build steps.\n\n## Path-tracer sample\n\nThis sample uses NVAPI cluster template extensions (RTX MG) to handle very large \nupdates to the ray-tracing acceleration structure (BVH).\n\nThis enables real-time path-tracing of subdivision surfaces with displacement \nmapping for the first time.\n(see [OpenSubdiv](https://graphics.pixar.com/opensubdiv/docs/intro.html))\n\n### Getting started\n\nOn startup, the sample application should be displaying the user-interface in\ndefault mode.\nThe interface is self-documenting : hover the mouse over widgets to access\ndetailed tool-tips.\n\n![overview](./images/ui.jpg)\n\n\n### Finding & loading content\n\nOn load, the application scans the `assets` folder located at the root of the\nproject and automatically populates the `scene` pull-down menu with compatible\nassets. Additional asset file can be placed in this folder hierarchy and will\nbe detected on the next launch of the application.\n\n![](./images/scene_loading.png)\n\n> [!TIPS]\n> The application will attempt to locate a folder named ‘assets’; if\n> unsuccessful, the path to the folder will be blank and the ‘Scene’ pull-down\n> menu will remain empty.\n\n### Camera controls\n\nThe camera follows the following conventions:\n\n  | First-person mode  |                                                          |\n  | ------------------ | -------------------------------------------------------- |\n  | WASD               | forward / strafe left / backward / strafe right movement |   \n  | LMB + drag         | orient the view                                          |   \n\n  | Orbit mode         |                                                          |\n  | ------------------ | -------------------------------------------------------- |\n  | alt + LMB          | orbit around origin                                      |   \n  | alt + MMP          | pan                                                      | \n  | mouse wheel        | dolly                                                    |\n\n### Keyboard shortcuts\n\nFor the complete list, refer to the source code in `RTXMGDemoApp::KeyboardUpdate`\n\n|            |                                                               |\n| ---------- | ------------------------------------------------------------- |\n| esc        | terminate application                                         |\n| f          | reset camera                                                  |\n| 1          | cycle shading modes                                           |\n| 2          | cycle color modes forward                                     |\n| /          | toggle dynamic tessellation ('freeze tess camera')            |\n|            |                                                               |\n\n### Profiler\n\nThe sample application tracks a number of run-time performance metrics that can\nbe accessed through the profiler window.\n\nThe profiler window can be toggled from the button next to the FPS counter: \n\n![profiler ctrl](./images/profiler_button.png)\n\nProfiling data is split into the following tabs:\n  * Frame : overall frame performance (ray-trace time, denoiser, ...)\n  * AccelBuilder : granular breakdown of the BVH build\n  * Evaluator : specifics of subdivision surface evaluation\n  * Memory : tessellation & BVH memory usage\n\n\n![profiler UI](./images/profiler_ui.png)\n\n---\n\n## Dynamic tessellation\n\n![tessellation](./images/tessellation.png)\n\nDynamic tessellation generates new topology each frame, which forces a complete\nrebuild of very large sections of the BVH. NVAPI's Mega-Geometry CLAS extensions\nto the ray-tracing acceleration structure build system are crucial to meeting the\nthroughput requirements. However, partitioning large meshes into clusters is only\npart of the solution. Tessellation algorithms generally produce large amounts of\nrepeating topology patterns: it is possible to take advantage of such a structured\noutput.\n\n> [!IMPORTANT]\n> All tessellation in this example is performed with generic compute : access\n> to the fixed-function tessellation hardware is still accessible through the\n> rasterizer's Hull and Domain shaders only.\n> See: [DirectX Graphics Pipeline](https://learn.microsoft.com/en-us/windows/uwp/graphics-concepts/graphics-pipeline)\n\n### Structured & Unstructured Clusters\n\nBecause Catmull-Clark surfaces are exclusively quad-based, we can define a\nfinite working set of rectangular tiles. Each tile in this set is a regular\ngrid of triangles, with the set covering all the combinations of M x N \nmicro-triangle resolutions, up to a maximum of 8 x 8. \n\nBecause the set can be defined with a finite number of re-usable cluster\ntopologies, we refer to these as 'structured clusters', as opposed to\n'unstructured clusters', where no such finite set can be composed (ex. clusters\ngenerated from photogrammetry data). The topology of the clusters itself can be\nentirely arbitrary though, and any pattern can be used (ex: barycentric \ntriangulation instead of quadrangulation). \n\n|  Quad cluster grids  |  Triangle cluster grids  |\n| :---: | :---: |\n| ![tessellation quads](./images/tessellation_quads.png) | ![tessellation triangles](./images/tessellation_triangles.png) |\n\n\n### Cluster Template API\n\nThe Mega-Geometry NVAPI extensions expose a special BVH build path for structured\nclusters, with substantial build performance advantages : the `CLAS` templates.\n\nOn application startup, each tiled grid configuration is passed to the `CLAS`\ntemplate builder (using NVAPI), which generates a uniquely identified\ntemplate specific to the triangle topology of that grid. The `CLAS` template\ncontains no vertex position data, only topology (the index buffer).\n\nEach template represents a partially optimized BVH treelet that can be\ninstantiated at run-time to any `CLAS` of matching topology. This allows the\napplication to amortize enormous amounts of BVH build work, compounding each\ntime a given template is instantiated with new or updated vertex positions.\n\n![bvh build](./images/clas_build.png)\n\nThis template `CLAS` workflow provides build performance comparable with \ntraditional `BVH` refit, but is more flexible because it permits topology\nchanges. It also has lower memory usage and more stable traversal performance\nversus traditional refit, and only clusters that are animated need to be\nrebuilt. It does, however, rely on a good clustering of the mesh by the\napplication, where triangles in a cluster are near each other in space!\n\n---\n\n## Frame Pipeline\n\n![pipeline](./images/pipeline.png)\n\n1. Subdivision mesh animation:   \n   Each frame starts with the interpolation of the subdivision control mesh (if animated).\n   The computation of the limit surface samples is integrated within the tessellation\n   algorithm, but follows the methods described in:\n   [Efficient GPU Rendering of Subdivision Surfaces using Adaptive Quadtrees](https://dl.acm.org/doi/10.1145/2897824.2925874)\n   (ACM TOG, Vol 35, Issue 4).\n\n2. Cluster tiling:\n   Applies heuristics to determine the tessellation edge-rates for each quadrangulated\n   face of the control mesh:\n   - Evaluate the limit surface at the locations of the 4 corners and 4 edge mid-points\n   - Apply displacement, if any\n   - Measure the lengths of the 8 projected segments in screen-space\n   - Check against visibility oracles (frustum, Hierarchical Z-Buffer, ...)\n   - Compute the final tessellation rates for the 4 edges\n   - If necessary, split into cluster tiles (max tile size is 8x8)\n   - Export clusters lists\n\n3. Fill clusters:\n   Computes the vertex positions for each cluster exported from step 2:\n   - Evaluate the limit surface at all the micro-vertex locations in the cluster grid\n   - Use polynomial basis derivatives to compute analytically surface tangents\n     & normals\n   - Apply displacement, if any\n   - Export vertex & texcoord buffers\n\n4. `CLAS` instantiation:\n   Compresses each cluster's data in `CLAS` templates:\n   - Select the cluster's template based on the cluster MxN edge-rate\n   - Fill the CLAS descriptor in GPU memory with the template ID and\n     pointers to the vertex data\n   - Call NVAPI CLAS template 'indirect' build function to launch the\n     build   \n\n5. `BLAS` build:\n    - Collect the pointers of the `CLAS`es instantiated in stage 4\n    - Fill the `BLAS` build descriptor (also in GPU memory)\n    - Call the NVAPI 'indirect' build function to launch the `BLAS`\n      build\n\n6. `TLAS` build:\n    Build the `TLAS` using the same NVAPI 'indirect' pattern.\n\n---\n\n## Appendix\n\nReferences to the file format extensions supported by the sample application.\n\n### OBJ extensions\n\nWe are using an extended version of Autodesk's `OBJ` file format with a “tag” system\nto express all information specific to subdivision surfaces. A Maya export plugin\ncould be made available upon request.\n\n> [!NOTE]\n> This is the same tagging system that is used in\n> [OpenSubdiv](https://graphics.pixar.com/opensubdiv/docs/intro.html)\n\nThe tag syntax is as follows:\n\n```\n t <tag name> <num int args>/<num float args>/<num string args> <args> \n```\n\nSome examples:\n```\n# select vertex boundary interpolation mode VTX_BOUNDARY_EDGE_AND_CORNER\n# 0 : VTX_BOUNDARY_NONE\n# 1 : VTX_BOUNDARY_EDGE_AND_CORNER\n# 2 : VTX_BOUNDARY_EDGE_ONLY\nt interpolateboundary 1/0/0 1\n\n# select face-varying boundary interpolation mode FVAR_LINEAR_ALL\n# 0 : FVAR_LINEAR_NONE\n# 1 : FVAR_LINEAR_CORNERS_ONLY\n# 2 : FVAR_LINEAR_CORNERS_PLUS1\n# 3 : FVAR_LINEAR_CORNERS_PLUS2\n# 4 : FVAR_LINEAR_BOUNDARIES\n# 5 : FVAR_LINEAR_ALL\nt interpolateboundary 1/0/0 5\n\n# tag the edge defined by vertices '1' and '3' with a sharpness of 2.0\nt crease 2/1/0 1 3 2.0\n\n# tag vertex '4' with a sharpness of 2.8\nt corner 1/1/0 4 2.8\n\n# set face '9' as a hole\nt hole 1/0/0 9\n\n# set the crease method to 'chaikin' (default is 'normal')\nt creasemethod 0/0/1 chaikin \n\n```\n\n### MTL extensions\n\nThe `OBJ` parser supports some of the physically based (PBR) materials\n[extensions](https://en.wikipedia.org/wiki/Wavefront_.obj_file#Physically-based_rendering).\nThe path-tracer does not support transparent, transmissive or emissive materials\nthough, so while they are parsed, they will not be used.\n\nSupported tags:\n```\nKd: albedo\nKs: specular\n\nPr: roughness\nPm: metalness\n\nmap_Kd: albedo map\nmap_Ks: specular map\nmap_Pr: roughness map\n\n# optional displacement scale & bias parameters\nmap_Bump -bm <scale> -bb <bias> : displacement map\n```\n\n### UDIM workflows\n\nUDIM texture naming conventions are supported with the `<UDIM>` keyword.\n\n> [!IMPORTANT]\n> Our implementation has a limitation that does not allow UV islands to cross a\n> UDIM tile boundary and will cause the application to throw a fatal error on load.\n\n```\nmap_Kd textures/asset_name_D.<UDIM>.dds\nmap_Ks textures/asset_name_S.<UDIM>.dds\nmap_Bump -bm 0.002000 textures/asset_name_H.<UDIM>.dds\nmap_Pr textures/asset_name_R.<UDIM>.dds\n```\n\n#### JSON scene files\n\nMultiple assets can be combined to create simple scenes with Donut's JSON schema.\n\nScene files should carry the extension ‘.scene.json’ and must be placed under root of\nthe ‘scenes’ folder (all asset paths are relative to the location of this file).\n\nFor historical reasons, the format used by this sample application differs slightly\nfrom Donut's. Please consult the source code for details.\n\n> [!NOTE]\n> The `glTF` file format only supports triangle-based meshes and cannot represent\n> subdivision surfaces without extensions. Because of these limitations, it is\n> not supported in this sample application.\n"
  },
  {
    "path": "extern/CMakeLists.txt",
    "content": "#\n# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.\n# \n# NVIDIA CORPORATION and its licensors retain all intellectual property\n# and proprietary rights in and to this software, related documentation\n# and any modifications thereto. Any use, reproduction, disclosure or\n# distribution of this software and related documentation without an express\n# license agreement from NVIDIA CORPORATION is strictly prohibited.\n\n##########################################################################\n################################ UTIL ####################################\n##########################################################################\nfunction(util_get_shader_profile_from_name FILE_NAME DXC_PROFILE)\n    get_filename_component(EXTENSION ${FILE_NAME} EXT)\n    if (\"${EXTENSION}\" STREQUAL \".cs.hlsl\")\n        set(DXC_PROFILE \"cs\" PARENT_SCOPE)\n    endif()\n    if (\"${EXTENSION}\" STREQUAL \".vs.hlsl\")\n        set(DXC_PROFILE \"vs\" PARENT_SCOPE)\n    endif()\n    if (\"${EXTENSION}\" STREQUAL \".gs.hlsl\")\n        set(DXC_PROFILE \"gs\" PARENT_SCOPE)\n    endif()\n    if (\"${EXTENSION}\" STREQUAL \".ps.hlsl\")\n        set(DXC_PROFILE \"ps\" PARENT_SCOPE)\n    endif()\nendfunction()\n\nfunction(util_generate_shader_config_file OUT_FILE_NAME DIR DEFINES)\n    file(GLOB_RECURSE HLSL_FILES \"${CMAKE_CURRENT_SOURCE_DIR}/${DIR}/*.hlsl\")\n\n    set(out_content \"\")\n    foreach(FILE_NAME ${HLSL_FILES})\n        get_filename_component(NAME_ONLY ${FILE_NAME} NAME)\n        set(DXC_PROFILE \"\")\n        util_get_shader_profile_from_name(${FILE_NAME} DXC_PROFILE)\n        set(out_content \"${out_content}${DIR}/${NAME_ONLY} -T ${DXC_PROFILE} -E main ${DEFINES}\\n\")\n    endforeach()\n\n    file(WRITE ${OUT_FILE_NAME} ${out_content})\nendfunction()\n\n"
  },
  {
    "path": "extern/nrd.cfg",
    "content": "RayTracingDenoiser/Shaders/Source/Clear_Float.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/Clear_Uint.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_ClassifyTiles.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseDirectionalOcclusion_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSh_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecularSh_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_DiffuseSpecular_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Diffuse_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseDirectionalOcclusion_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSh_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecularSh_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_DiffuseSpecular_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Diffuse_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_SpecularSh_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Perf_Specular_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularOcclusion_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_SpecularSh_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_PostBlur_NoTemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Specular_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REBLUR_Validation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REFERENCE_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/REFERENCE_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_ClassifyTiles.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_AntiFirefly.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_Atrous.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_AtrousSmem.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_HistoryClamping.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_AntiFirefly.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_Atrous.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_AtrousSmem.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_HistoryClamping.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecularSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_AntiFirefly.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_Atrous.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_AtrousSmem.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_HistoryClamping.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_DiffuseSpecular_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_AntiFirefly.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_Atrous.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_AtrousSmem.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_HistoryClamping.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Diffuse_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_AntiFirefly.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_Atrous.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_AtrousSmem.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_HistoryClamping.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_SpecularSh_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_AntiFirefly.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_Atrous.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_AtrousSmem.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_HistoryClamping.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_HistoryFix.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_HitDistReconstruction.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_HitDistReconstruction_5x5.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_PrePass.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Specular_TemporalAccumulation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/RELAX_Validation.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_Copy.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_ShadowTranslucency_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_ShadowTranslucency_ClassifyTiles.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_ShadowTranslucency_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_ShadowTranslucency_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_ShadowTranslucency_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_Shadow_Blur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_Shadow_ClassifyTiles.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_Shadow_PostBlur.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_Shadow_SplitScreen.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_Shadow_TemporalStabilization.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\nRayTracingDenoiser/Shaders/Source/SIGMA_SmoothTiles.cs.hlsl -T cs -E main -D NRD_COMPILER_DXC=1 -D NRD_NORMAL_ENCODING=2 -D NRD_ROUGHNESS_ENCODING=1\n"
  },
  {
    "path": "notice.txt",
    "content": "\n\n  RTX MegaGeometry (RTXMG)\n  Copyright (C) 2019, NVIDIA CORPORATION\n  All rights reserved.\n\n  https://github.com/NVIDIA-RTX/RTXMG\n\nNVIDIA, the NVIDIA logo, GeForce, GeForce GTX, GeForce RTX, NVIDIA GameWorks,\nand NVIDIA RTX are trademarks and/or registered trademarks of NVIDIA Corporation\nin the U.S. and/or other countries. Other company and product names may be\ntrademarks of the respective companies with which they are associated.\nNVIDIA's trademarks may be used publicly with permission only from NVIDIA,\nand nothing in this Agreement shall be construed as granting such permission.\nFair use of NVIDIA's trademarks in advertising and promotion of NVIDIA products\nrequires proper acknowledgment;\n\nhttps://www.nvidia.com/content/dam/en-zz/Solutions/about-us/documents/NVIDIA-Trademark-and-Logo-Usage-Guidelines.pdf\n\n\n"
  },
  {
    "path": "rtxmg/CMakeLists.txt",
    "content": "add_library(rtxmg)\ntarget_include_directories(rtxmg PUBLIC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/include\"\n)\n\nadd_subdirectory(cluster_builder)\nadd_subdirectory(hiz)\nadd_subdirectory(profiler)\nadd_subdirectory(scene)\nadd_subdirectory(subdivision)\nadd_subdirectory(utils)\n\ntarget_link_libraries(rtxmg cluster_builder profiler hiz subdivision rtxmg_utils scene envmap)\nset_target_properties(rtxmg PROPERTIES FOLDER \"RTXMG\")\n"
  },
  {
    "path": "rtxmg/cluster_builder/CMakeLists.txt",
    "content": "set(lib cluster_builder)\nset(folder RTXMG)\n\ninclude (\"${DONUT_DIR}/compileshaders.cmake\")\n\nfile(GLOB shaders \"shaders/*\" \"../include/rtxmg/${lib}/*.hlsli\")\nfile(GLOB sources \"*.cpp\" \"../include/rtxmg/${lib}/*.h\" *.cfg)\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include\"\n)\ntarget_link_libraries(${lib} donut_engine subdivision)\nset_target_properties(${lib} PROPERTIES FOLDER ${folder})\n\ndonut_compile_shaders_all_platforms(\n    TARGET ${lib}_shaders\n    CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/shaders.cfg\n    FOLDER ${folder}\n    SOURCES ${shaders}\n    OUTPUT_BASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders/${lib}\n    SHADERMAKE_OPTIONS ${RTXMG_SHADERMAKE_OPTIONS}\n    SHADERMAKE_OPTIONS_SPIRV ${RTXMG_SHADERMAKE_OPTIONS_SPIRV}\n    SHADER_MODEL ${RTXMG_SHADERS_SHADERMODEL}\n    IGNORE_INCLUDES ${RTXMG_SHADERS_IGNORED_INCLUDES}\n    INCLUDES ${RTXMG_SHADERS_INCLUDE_DIR}\n)\nadd_dependencies(${lib} ${lib}_shaders)\n"
  },
  {
    "path": "rtxmg/cluster_builder/cluster_accel_builder.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include <donut/core/log.h>\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/engine/ShaderFactory.h>\n#include <nvrhi/common/misc.h>\n#include <nvrhi/utils.h>\n\n#include <map>\n#include <fstream>\n\n#include \"rtxmg/utils/buffer.h\"\n\n#include \"rtxmg/cluster_builder/cluster_accels.h\"\n#include \"rtxmg/cluster_builder/cluster_accel_builder.h\"\n#include \"rtxmg/cluster_builder/fill_clusters_params.h\"\n#include \"rtxmg/cluster_builder/copy_cluster_offset_params.h\"\n#include \"rtxmg/cluster_builder/fill_blas_from_clas_args_params.h\"\n#include \"rtxmg/cluster_builder/fill_instantiate_template_args_params.h\"\n#include \"rtxmg/cluster_builder/compute_cluster_tiling_params.h\"\n#include \"rtxmg/cluster_builder/tessellation_counters.h\"\n#include \"rtxmg/cluster_builder/tessellator_config.h\"\n#include \"rtxmg/cluster_builder/tessellator_constants.h\"\n\n#include \"rtxmg/hiz/zbuffer.h\"\n\n#include \"rtxmg/profiler/statistics.h\"\n\n#include \"rtxmg/scene/camera.h\"\n#include \"rtxmg/scene/scene.h\"\n\n#include \"rtxmg/subdivision/subdivision_surface.h\"\n#include \"rtxmg/subdivision/topology_map.h\"\n\nusing namespace donut;\nusing namespace nvrhi::rt;\n\nconstexpr uint32_t kNumTemplates = kMaxClusterEdgeSegments * kMaxClusterEdgeSegments;\nconstexpr uint32_t kClusterMaxTriangles = kMaxClusterEdgeSegments * kMaxClusterEdgeSegments * 2;\nconstexpr uint32_t kClusterMaxVertices = (kMaxClusterEdgeSegments + 1) * (kMaxClusterEdgeSegments + 1);\nconstexpr uint32_t kFrameCount = 4;\n\nClusterAccelBuilder::ClusterAccelBuilder(donut::engine::ShaderFactory& shaderFactory,\n    std::shared_ptr<donut::engine::CommonRenderPasses> commonPasses,\n    nvrhi::DescriptorTableHandle descriptorTable,\n    nvrhi::DeviceHandle device)\n    : m_shaderFactory(shaderFactory)\n    , m_descriptorTable(descriptorTable)\n    , m_commonPasses(commonPasses)\n    , m_device(device)\n{\n    m_tessellationCountersBuffer.Create(kFrameCount, \"tesselation counters\", m_device);\n    m_debugBuffer.Create(512, \"ClusterAccelDebug\", m_device);\n        \n    //////////////////////////////////////////////////\n    // Parameter buffers for shaders\n    //////////////////////////////////////////////////\n    m_fillInstantiateTemplateArgsParamsBuffer = m_device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(FillInstantiateTemplateArgsParams), \"FillInstantiateTemplateArgsParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_computeClusterTilingParamsBuffer = m_device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(ComputeClusterTilingParams), \"ComputeClusterTilingParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_fillClustersParamsBuffer = m_device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(FillClustersParams), \"FillClustersParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    m_fillBlasFromClasArgsParamsBuffer = m_device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(FillBlasFromClasArgsParams), \"FillBlasFromClasArgsParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    //////////////////////////////////////////////////\n    // Create common bindless binding layout\n    //////////////////////////////////////////////////\n    nvrhi::BindlessLayoutDesc bindlessLayoutDesc;\n    bindlessLayoutDesc.visibility = nvrhi::ShaderType::All;\n    bindlessLayoutDesc.firstSlot = 0;\n    bindlessLayoutDesc.maxCapacity = 1024;\n    bindlessLayoutDesc.layoutType = nvrhi::BindlessLayoutDesc::LayoutType::MutableSrvUavCbv;\n    m_bindlessBL = m_device->createBindlessLayout(bindlessLayoutDesc);\n}\n\n// Must match shader defines in compute_cluster_tiling.hlsl\ninline char const* toString(TessellatorConfig::AdaptiveTessellationMode mode)\n{\n    switch (mode)\n    {\n    case TessellatorConfig::AdaptiveTessellationMode::UNIFORM: return \"TESS_MODE_UNIFORM\";\n    case TessellatorConfig::AdaptiveTessellationMode::WORLD_SPACE_EDGE_LENGTH: return \"TESS_MODE_WORLD_SPACE_EDGE_LENGTH\";\n    case TessellatorConfig::AdaptiveTessellationMode::SPHERICAL_PROJECTION: return \"TESS_MODE_SPHERICAL_PROJECTION\";\n    default: return \"UNKNOWN\";\n    }\n}\n\ninline char const* toString(TessellatorConfig::VisibilityMode mode)\n{\n    switch (mode)\n    {\n    case TessellatorConfig::VisibilityMode::VIS_SURFACE: return \"VIS_MODE_SURFACE\";\n    case TessellatorConfig::VisibilityMode::VIS_LIMIT_EDGES: return \"VIS_MODE_LIMIT_EDGES\";\n    default: return \"UNKNOWN\";\n    }\n}\n\nconstexpr auto kSurfaceTypeDefines = std::to_array<const char*>(\n{\n    \"SURFACE_TYPE_PUREBSPLINE\",\n    \"SURFACE_TYPE_REGULARBSPLINE\",\n    \"SURFACE_TYPE_LIMIT\",\n    \"SURFACE_TYPE_ALL\"\n});\nstatic_assert(kSurfaceTypeDefines.size() == size_t(ShaderPermutationSurfaceType::Count));\n\ninline char const* toString(ShaderPermutationSurfaceType surfaceType)\n{\n    return kSurfaceTypeDefines[uint32_t(surfaceType)];\n}\n\n\nvoid ClusterAccelBuilder::FillInstantiateTemplateArgs(nvrhi::IBuffer* outArgs, nvrhi::IBuffer* templateAddresses, uint32_t numTemplates, nvrhi::ICommandList* commandList)\n{\n    FillInstantiateTemplateArgsParams params = {};\n    params.numTemplates = numTemplates;\n    params.pad = uint3();\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"ClusterAccelBuilder::FillInstantiateTemplateArgs\");\n    commandList->writeBuffer(m_fillInstantiateTemplateArgsParamsBuffer, &params, sizeof(params));\n\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, templateAddresses))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, outArgs))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_fillInstantiateTemplateArgsParamsBuffer));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(m_device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_fillInstantiateTemplateBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for fill_instantiate_template_args.hlsl\");\n    }\n\n    if (!m_fillInstantiateTemplatePSO)\n    {\n        nvrhi::ShaderHandle shader = m_shaderFactory.CreateShader(\"cluster_builder/fill_instantiate_template_args.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_fillInstantiateTemplateBL);\n\n        m_fillInstantiateTemplatePSO = m_device->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_fillInstantiateTemplatePSO)\n        .addBindingSet(bindingSet);\n    commandList->setComputeState(state);\n    commandList->dispatch(div_ceil(numTemplates, kFillInstantiateTemplateArgsThreads), 1, 1);\n}\n\nvoid ClusterAccelBuilder::FillBlasFromClasArgs(nvrhi::IBuffer* outArgs, nvrhi::IBuffer* clusterOffsets,\n    nvrhi::GpuVirtualAddress clasPtrsBaseAddress, uint32_t numInstances, nvrhi::ICommandList* commandList)\n{\n    FillBlasFromClasArgsParams params = {};\n    params.clasAddressesBaseAddress = clasPtrsBaseAddress;\n    params.numInstances = numInstances;\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"ClusterAccelBuilder::FillBlasFromClasArgs\");\n    commandList->writeBuffer(m_fillBlasFromClasArgsParamsBuffer, &params, sizeof(params));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, clusterOffsets))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, outArgs))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_fillBlasFromClasArgsParamsBuffer));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(m_device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_fillBlasFromClasArgsBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for fill_blas_from_clas_args.hlsl\");\n    }\n\n    if (!m_fillBlasFromClasArgsPSO)\n    {\n        nvrhi::ShaderHandle shader = m_shaderFactory.CreateShader(\"cluster_builder/fill_blas_from_clas_args.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_fillBlasFromClasArgsBL);\n\n        m_fillBlasFromClasArgsPSO = m_device->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_fillBlasFromClasArgsPSO)\n        .addBindingSet(bindingSet);\n    commandList->setComputeState(state);\n    commandList->dispatch(div_ceil(numInstances, kFillBlasFromClasArgsThreads), 1, 1);\n}\n\nstatic TemplateGrids GenerateTemplateGrids()\n{\n    TemplateGrids result;\n\n    // Offsets per template\n    result.descs.resize(kNumTemplates);\n    result.indices.reserve(kNumTemplates * kClusterMaxTriangles * 3);\n    result.vertices.reserve(kNumTemplates * kClusterMaxVertices * 3);\n\n    // Generate cluster topologies for 11x11 grid\n    for (uint32_t i = 0; i < kNumTemplates; i++)\n    {\n        assert(i % kMaxClusterEdgeSegments < std::numeric_limits<TemplateGrids::IndexType>::max());\n        assert(i / kMaxClusterEdgeSegments < std::numeric_limits<TemplateGrids::IndexType>::max());\n\n        TemplateGridDesc& gridDesc = result.descs[i];\n        gridDesc =\n        {\n            .xEdges = i % kMaxClusterEdgeSegments + 1,\n            .yEdges = i / kMaxClusterEdgeSegments + 1,\n            .indexOffset = static_cast<uint32_t>(result.indices.size() * sizeof(result.indices[0])),\n            .vertexOffset = static_cast<uint32_t>(result.vertices.size() * sizeof(result.vertices[0]))\n        };\n\n        // x, y = lower - left vertex of quad\n        // s: 0 is the first triangle, (left vertical edge), 1 is the second triangle (right vertical edge)\n        auto TriIndices = [&gridDesc](uint32_t x, uint32_t y, uint32_t s)->std::array<uint32_t, 3>\n        {\n            uint32_t vs = gridDesc.getXVerts();  // vertex stride  (same as xVerts)\n            uint32_t vid = y * vs + x;          // lower-left vertex id\n            bool diag03 = ((x & 1) == (y & 1)); // is this triangle a 0-3 diagonal (true) or a 1-2 diagonal (false)\n\n            assert(vid + vs + 1 < std::numeric_limits<TemplateGrids::IndexType>::max());\n\n            // Example output for (xEdges = 3, yEdges = 1, x = {0..3}, y = 0)\n            //      4_____5_____6_____7\n            //      |    /|\\    |    /|\n            //      | a / | \\ d | e / |\n            //      |  /  |  \\  |  /  |\n            //      | / b | c \\ | / f |\n            //      |/____|____\\|/____|\n            //      0     1     2     3\n            //\n            //    (x,y,s)\n            // a  (0,0,0) diag03 = (0, 5, 4) \n            // b  (0,0,1) diag03 = (0, 1, 5)\n            // c  (1,0,0)        = (1, 2, 5)\n            // d  (1,0,1)        = (2, 6, 5)\n            // e  (2,0,0) diag03 = (2, 7, 6)\n            // f  (2,0,1) diag03 = (2, 3, 7)\n\n            if (diag03)\n            {\n                if (s == 0) return { vid, vid + 1 + vs, vid + vs };\n                else        return { vid, vid + 1     , vid + 1 + vs };\n            }\n            else\n            {\n                if (s == 0) return { vid    , vid + 1     , vid + vs };\n                else        return { vid + 1u, vid + 1 + vs, vid + vs };\n            }\n        };\n\n        float xScale = 1.0f / gridDesc.xEdges;\n        float yScale = 1.0f / gridDesc.yEdges;\n\n        uint32_t xVerts = gridDesc.getXVerts();\n        uint32_t yVerts = gridDesc.getYVerts();\n\n        for (uint32_t y = 0; y < yVerts; y++)\n        {\n            for (uint32_t x = 0; x < xVerts; x++)\n            {\n                // Add triangles to index buffer\n                if (x < gridDesc.xEdges && y < gridDesc.yEdges)\n                {\n                    for (uint32_t s = 0; s < 2; s++)\n                    {\n                        std::array<uint32_t, 3> triIndices = TriIndices(x, y, s);\n                        std::transform(triIndices.begin(), triIndices.end(), std::back_inserter(result.indices), [](uint32_t e)\n                            {\n                                assert(e < std::numeric_limits<TemplateGrids::IndexType>::max());\n                                return static_cast<TemplateGrids::IndexType>(e);\n                            });\n                    }\n                }\n\n                // Add verts\n                result.vertices.push_back(x * xScale);\n                result.vertices.push_back(y * yScale);\n                result.vertices.push_back(0.0f);\n            }\n        }\n\n        result.maxTriangles = std::max(result.maxTriangles, gridDesc.getNumTriangles());\n        result.totalTriangles += gridDesc.getNumTriangles();\n\n        result.maxVertices = std::max(result.maxVertices, gridDesc.getNumVerts());\n        result.totalVertices += gridDesc.getNumVerts();\n    }\n\n    assert(result.maxVertices == kClusterMaxVertices);\n    assert(result.maxTriangles == kClusterMaxTriangles);\n\n    return result;\n}\n\nnvrhi::BufferHandle ClusterAccelBuilder::GenerateStructuredClusterTemplateArgs(const TemplateGrids &grids, nvrhi::ICommandList* commandList)\n{\n    nvrhi::BufferDesc indexBufferDesc = {\n        .byteSize = grids.indices.size() * sizeof(grids.indices[0]),\n        .structStride = sizeof(grids.indices[0]),\n        .debugName = \"ClusterTemplateIndices\",\n        .isAccelStructBuildInput = true,\n        .initialState = nvrhi::ResourceStates::AccelStructBuildInput,\n        .keepInitialState = true,\n    };\n\n    nvrhi::BufferHandle indexBuffer = CreateBuffer(indexBufferDesc, m_device);\n    if (grids.indices.size() > 0)\n    {\n        // writeBuffer is retains indexBuffer until the frame end\n        commandList->writeBuffer(indexBuffer, grids.indices.data(), grids.indices.size() * sizeof(grids.indices[0]));\n    }\n\n    nvrhi::BufferDesc vertexBufferDesc = {\n        .byteSize = grids.vertices.size() * sizeof(grids.vertices[0]),\n        .debugName = \"ClusterTemplateVertices\",\n        .format = nvrhi::Format::RGB32_FLOAT,\n        .isVertexBuffer = true,\n        .isAccelStructBuildInput = true,\n        .initialState = nvrhi::ResourceStates::AccelStructBuildInput,\n        .keepInitialState = true,\n    };\n    nvrhi::BufferHandle vertexBuffer = CreateBuffer(vertexBufferDesc, m_device);\n    if (grids.vertices.size() > 0)\n    {\n        // writeBuffer is retains vertexBuffer until the frame end\n        commandList->writeBuffer(vertexBuffer, grids.vertices.data(), grids.vertices.size() * sizeof(grids.vertices[0]));\n    }\n\n    nvrhi::GpuVirtualAddress indexBufferAddress = indexBuffer->getGpuVirtualAddress();\n    nvrhi::GpuVirtualAddress vertexBufferAddress = vertexBuffer->getGpuVirtualAddress();\n\n    uint32_t indexFormat = 0;\n    switch (sizeof(TemplateGrids::IndexType))\n    {\n    case 1: indexFormat = (uint32_t)cluster::OperationIndexFormat::IndexFormat8bit; break;\n    case 2: indexFormat = (uint32_t)cluster::OperationIndexFormat::IndexFormat16bit; break;\n    case 4: indexFormat = (uint32_t)cluster::OperationIndexFormat::IndexFormat32bit; break;\n    default: assert(false);\n    }\n\n    std::vector<cluster::IndirectTriangleTemplateArgs> createTemplateArgData(grids.descs.size());\n    for (uint32_t i = 0; i < createTemplateArgData.size(); i++)\n    {\n        const TemplateGridDesc& grid = grids.descs[i];\n\n        // Zero-initialize unused bit fields \n        createTemplateArgData[i] = { };\n        createTemplateArgData[i] = cluster::IndirectTriangleTemplateArgs\n        {\n            .clusterId = 0,\n            .clusterFlags = 0,\n            .triangleCount = grid.getNumTriangles(),\n            .vertexCount = grid.getNumVerts(),\n            .positionTruncateBitCount = 0,\n            .indexFormat = indexFormat,\n            .baseGeometryIndexAndFlags = 0,\n            .indexBufferStride = sizeof(grids.indices[0]),\n            .vertexBufferStride = sizeof(grids.vertices[0]) * 3,\n            .geometryIndexAndFlagsBufferStride = 0,\n            .indexBuffer = indexBufferAddress + grid.indexOffset,\n            .vertexBuffer = vertexBufferAddress + grid.vertexOffset,\n            .geometryIndexAndFlagsBuffer = 0,\n            .instantiationBoundingBoxLimit = 0\n        };\n    }\n\n    nvrhi::BufferDesc clusterTemplateArgsDesc =\n    {\n        .byteSize = createTemplateArgData.size() * sizeof(createTemplateArgData[0]),\n        .structStride = sizeof(createTemplateArgData[0]),\n        .debugName = \"ClusterTemplateArgs\",\n        .isDrawIndirectArgs = true,\n        .isAccelStructBuildInput = true,\n        .initialState = nvrhi::ResourceStates::IndirectArgument,\n        .keepInitialState = true,\n    };\n\n    return CreateAndUploadBuffer(createTemplateArgData, clusterTemplateArgsDesc, commandList);\n}\n\nvoid ClusterAccelBuilder::InitStructuredClusterTemplates(uint32_t maxGeometryCountPerMesh, nvrhi::ICommandList* commandList)\n{\n    // only initialize if maxGeometryCount or quantNBits changes\n    if (m_templateBuffers.dataBuffer.Get() != 0 && \n        m_templateBuffers.maxGeometryCountPerMesh == maxGeometryCountPerMesh &&\n        m_templateBuffers.quantNBits == m_tessellatorConfig.quantNBits)\n        return;\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"InitStructuredClusterTemplates\");\n    m_templateBuffers.maxGeometryCountPerMesh = maxGeometryCountPerMesh;\n    m_templateBuffers.quantNBits = m_tessellatorConfig.quantNBits;\n\n    TemplateGrids grids = GenerateTemplateGrids();\n    \n    // First compute the size of each template so we can build the address buffer\n    // this will also act as the settings for further operations below.\n    cluster::OperationParams operationParams =\n    {\n        .maxArgCount = kNumTemplates,\n        .type = cluster::OperationType::ClasBuildTemplates,\n        .mode = cluster::OperationMode::GetSizes,\n        .flags = cluster::OperationFlags::None,\n        .clas =\n        {\n            .vertexFormat = nvrhi::Format::RGB32_FLOAT,\n            .maxGeometryIndex = maxGeometryCountPerMesh,\n            .maxUniqueGeometryCount = 1,\n            .maxTriangleCount = kClusterMaxTriangles,\n            .maxVertexCount = kClusterMaxVertices,\n            .maxTotalTriangleCount = grids.totalTriangles,\n            .maxTotalVertexCount = grids.totalVertices,\n            .minPositionTruncateBitCount = m_tessellatorConfig.quantNBits,\n        }\n    };\n    cluster::OperationSizeInfo sizeInfo = m_device->getClusterOperationSizeInfo(operationParams);\n        \n    nvrhi::BufferHandle clusterTemplateArgsBuffer = GenerateStructuredClusterTemplateArgs(grids, commandList);\n    \n    RTXMGBuffer<uint32_t> templateSizesBuffer;\n    templateSizesBuffer.Create(kNumTemplates, \"ClusterTemplateSizes\", m_device);\n\n    cluster::OperationDesc templateGetSizesDesc =\n    {\n        .params = operationParams,\n        .scratchSizeInBytes = sizeInfo.scratchSizeInBytes,\n        .inIndirectArgsBuffer = clusterTemplateArgsBuffer,\n        .inIndirectArgsOffsetInBytes = 0,\n        .outSizesBuffer = templateSizesBuffer,\n        .outSizesOffsetInBytes = 0\n    };\n    commandList->executeMultiIndirectClusterOperation(templateGetSizesDesc);\n\n    // readback templateSizes\n    std::vector<uint32_t> templateSizes = templateSizesBuffer.Download(commandList);\n    \n    if (m_tessellatorConfig.enableLogging)\n    {\n        templateSizesBuffer.Log(commandList);\n    }\n\n    size_t totalTemplateSize = 0;\n    for (uint32_t i = 0; i < kNumTemplates; i++)\n    {\n        totalTemplateSize += templateSizes[i];\n    }\n    \n    // Create template data buffer based off of totalSize of all templates\n    nvrhi::BufferDesc destDataDesc = {\n        .byteSize = totalTemplateSize,\n        .debugName = \"ClusterTemplateData\",\n        .canHaveUAVs = true,\n        .isAccelStructStorage = true,\n        .initialState = nvrhi::ResourceStates::AccelStructWrite,\n        .keepInitialState = true,\n    };\n    m_templateBuffers.dataBuffer = CreateBuffer(destDataDesc, m_device);\n\n    // Explicit Destination mode, calculate the address offset for each template to get a tight fit\n    operationParams.type = cluster::OperationType::ClasBuildTemplates;\n    operationParams.mode = cluster::OperationMode::ExplicitDestinations;\n\n    nvrhi::GpuVirtualAddress baseAddress = m_templateBuffers.dataBuffer->getGpuVirtualAddress();\n    std::vector<nvrhi::GpuVirtualAddress> addresses(kNumTemplates);\n    totalTemplateSize = 0;\n    for (size_t i = 0; i < addresses.size(); i++)\n    {\n        addresses[i] = baseAddress + totalTemplateSize;\n        totalTemplateSize += templateSizes[i];\n    }\n\n    m_templateBuffers.addressesBuffer.Create(kNumTemplates, \"ClusterTemplateDestAddressData\", m_device);\n    m_templateBuffers.addressesBuffer.Upload(addresses, commandList);\n    m_templateBuffers.instantiationSizesBuffer.Create(kNumTemplates, \"ClusterTemplateInstantiationSizes\", m_device);\n\n    cluster::OperationDesc createClusterTemplateDesc =\n    {\n        .params = operationParams,\n        .scratchSizeInBytes = sizeInfo.scratchSizeInBytes,\n        .inIndirectArgsBuffer = clusterTemplateArgsBuffer,\n        .inIndirectArgsOffsetInBytes = 0,\n        .inOutAddressesBuffer = m_templateBuffers.addressesBuffer,\n        .inOutAddressesOffsetInBytes = 0,\n        .outSizesBuffer = 0,\n        .outSizesOffsetInBytes = 0,\n        .outAccelerationStructuresBuffer = nullptr,\n        .outAccelerationStructuresOffsetInBytes = 0\n    };\n    commandList->executeMultiIndirectClusterOperation(createClusterTemplateDesc);\n\n    if (m_tessellatorConfig.enableLogging)\n    {\n        m_templateBuffers.addressesBuffer.Log(commandList);\n    }\n\n    // Create and fill out the instantiate args buffer from addressesBuffer\n    nvrhi::BufferDesc instantiateTemplateArgsDesc = \n    {\n        .byteSize = sizeof(cluster::IndirectInstantiateTemplateArgs) * kNumTemplates,\n        .structStride = sizeof(cluster::IndirectInstantiateTemplateArgs),\n        .debugName = \"InstantiateTemplateArgs\",\n        .canHaveUAVs = true,\n        .isDrawIndirectArgs = true,\n        .isAccelStructBuildInput = true,\n        .initialState = nvrhi::ResourceStates::IndirectArgument,\n        .keepInitialState = true,\n    };\n\n    RTXMGBuffer<cluster::IndirectInstantiateTemplateArgs> instantiateTemplateArgsBuffer(instantiateTemplateArgsDesc, m_device);\n    FillInstantiateTemplateArgs(instantiateTemplateArgsBuffer, m_templateBuffers.addressesBuffer, kNumTemplates, commandList);\n\n    if (m_tessellatorConfig.enableLogging)\n    {\n        instantiateTemplateArgsBuffer.Log(commandList, [](std::ostream& ss, auto e)\n            {\n                ss << \"{ct: \" << std::hex << e.clusterTemplate <<\n                    \" | vb: \" << std::hex << e.vertexBuffer.startAddress << \"}\";\n                return true;\n            });\n    }\n\n    // Execute GetSizes mode to fill out destSizes\n    operationParams.type = cluster::OperationType::ClasInstantiateTemplates;\n    operationParams.mode = cluster::OperationMode::GetSizes;\n    \n    cluster::OperationDesc instantiateTemplateGetSizesDesc =\n    {\n        .params = operationParams,\n        .scratchSizeInBytes = sizeInfo.scratchSizeInBytes,\n        .inIndirectArgsBuffer = instantiateTemplateArgsBuffer,\n        .inIndirectArgsOffsetInBytes = 0,\n        .outSizesBuffer = m_templateBuffers.instantiationSizesBuffer,\n        .outSizesOffsetInBytes = 0\n    };\n    commandList->executeMultiIndirectClusterOperation(instantiateTemplateGetSizesDesc);\n\n    m_templateBuffers.instantiationSizes = m_templateBuffers.instantiationSizesBuffer.Download(commandList);\n\n    if (m_tessellatorConfig.enableLogging)\n    {\n        m_templateBuffers.instantiationSizesBuffer.Log(commandList, { .wrap = false });\n    }\n}\n\nvoid ClusterAccelBuilder::BuildStructuredCLASes(ClusterAccels& accels, uint32_t maxGeometryCountPerMesh, \n    const nvrhi::BufferRange& tessCounterRange, nvrhi::ICommandList* commandList)\n{\n    nvrhi::utils::ScopedMarker marker(commandList, \"ClusterAccelBuilder::BuildStructuredCLASes\");\n\n    cluster::OperationParams instantiateClasParams =\n    {\n        .maxArgCount = m_maxClusters,\n        .type = cluster::OperationType::ClasInstantiateTemplates,\n        .mode = cluster::OperationMode::ExplicitDestinations,\n        .flags = cluster::OperationFlags::None,\n        .clas =\n        {\n            .vertexFormat = nvrhi::Format::RGB32_FLOAT,\n            .maxGeometryIndex = maxGeometryCountPerMesh,\n            .maxUniqueGeometryCount = 1,\n            .maxTriangleCount = kClusterMaxTriangles,\n            .maxVertexCount = kClusterMaxVertices,\n            .maxTotalTriangleCount = m_maxClusters * kClusterMaxTriangles,\n            .maxTotalVertexCount = m_maxVertices,\n            .minPositionTruncateBitCount = m_tessellatorConfig.quantNBits,\n        }\n    };\n\n    cluster::OperationSizeInfo sizeInfo = m_device->getClusterOperationSizeInfo(instantiateClasParams);\n    cluster::OperationDesc instantiateClasDesc =\n    {\n        .params = instantiateClasParams,\n        .scratchSizeInBytes = sizeInfo.scratchSizeInBytes,\n        .inIndirectArgCountBuffer = m_tessellationCountersBuffer,\n        .inIndirectArgCountOffsetInBytes = tessCounterRange.byteOffset + kClusterCountByteOffset,\n        .inIndirectArgsBuffer = m_clasIndirectArgDataBuffer,\n        .inIndirectArgsOffsetInBytes = 0,\n        .inOutAddressesBuffer = accels.clasPtrsBuffer,\n        .inOutAddressesOffsetInBytes = 0,\n        .outSizesBuffer = nullptr,\n        .outSizesOffsetInBytes = 0,\n        .outAccelerationStructuresBuffer = nullptr,\n        .outAccelerationStructuresOffsetInBytes = 0\n    };\n\n    commandList->executeMultiIndirectClusterOperation(instantiateClasDesc);\n}\n\nvoid ClusterAccelBuilder::FillInstanceClusters(const RTXMGScene& scene, ClusterAccels& accels, nvrhi::ICommandList* commandList)\n{    \n    const auto& subdMeshes = scene.GetSubdMeshes();\n    const auto& instances = scene.GetSubdMeshInstances();\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"FillInstanceClusters\");\n    stats::clusterAccelSamplers.fillClustersTime.Start(commandList);\n\n    uint32_t surfaceOffset{ 0 };\n    for (uint32_t instanceIndex = 0; instanceIndex < instances.size(); ++instanceIndex)\n    {\n        const auto& instance = instances[instanceIndex];\n        assert(instance.meshInstance.get());\n        const auto& donutMeshInfo = instance.meshInstance->GetMesh();\n        assert(donutMeshInfo.get());\n        uint32_t firstGeometryIndex = donutMeshInfo->geometries[0]->globalGeometryIndex;\n\n        const auto& subd = *subdMeshes[instance.meshID];\n        \n        const uint32_t surfaceCount = subd.SurfaceCount();\n\n        if (m_tessellatorConfig.debugSurfaceIndex >= 0 &&\n            m_tessellatorConfig.debugClusterIndex >= 0 &&\n            m_tessellatorConfig.debugLaneIndex >= 0)\n        {\n            commandList->clearBufferUInt(m_debugBuffer, 0);\n        }\n\n        FillClustersParams params = {};\n        params.instanceIndex = instanceIndex;\n        params.quantNBits = m_tessellatorConfig.quantNBits;\n        params.isolationLevel = m_tessellatorConfig.isolationLevel;\n        params.globalDisplacementScale = m_tessellatorConfig.displacementScale;\n        params.clusterPattern = uint32_t(m_tessellatorConfig.clusterPattern);\n        params.firstGeometryIndex = firstGeometryIndex;\n        params.debugSurfaceIndex = uint32_t(m_tessellatorConfig.debugSurfaceIndex);\n        params.debugClusterIndex = uint32_t(m_tessellatorConfig.debugClusterIndex);\n        params.debugLaneIndex = uint32_t(m_tessellatorConfig.debugLaneIndex);\n        commandList->writeBuffer(m_fillClustersParamsBuffer, &params, sizeof(FillClustersParams));\n\n        auto bindingSetDesc = nvrhi::BindingSetDesc()\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_gridSamplersBuffer,\n                nvrhi::Format::UNKNOWN,\n                nvrhi::BufferRange(surfaceOffset * sizeof(GridSampler), surfaceCount * sizeof(GridSampler))))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(1, m_clusterOffsetCountsBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(2, m_clustersBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(3, subd.m_positionsBuffer))\n            // Subd\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(4, subd.m_vertexDeviceData.surfaceDescriptors))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(5, subd.m_vertexDeviceData.controlPointIndices))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(6, subd.m_vertexDeviceData.patchPointsOffsets))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(7, subd.GetTopologyMap()->plansBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(8, subd.GetTopologyMap()->subpatchTreesArraysBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(9, subd.GetTopologyMap()->patchPointIndicesArraysBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(10, subd.GetTopologyMap()->stencilMatrixArraysBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(11, subd.m_vertexDeviceData.patchPoints))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(12, scene.GetGeometryBuffer()))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(13, scene.GetMaterialBuffer()))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(14, subd.m_surfaceToGeometryIndexBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(15, subd.m_texcoordDeviceData.surfaceDescriptors))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(16, subd.m_texcoordDeviceData.controlPointIndices))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(17, subd.m_texcoordDeviceData.patchPointsOffsets))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(18, subd.m_texcoordDeviceData.patchPoints))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(19, subd.m_texcoordsBuffer))\n            .addItem(nvrhi::BindingSetItem::Sampler(0, scene.GetDisplacementSampler()))\n\n            // gatherer\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, accels.clusterVertexPositionsBuffer)) \n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(1, accels.clusterShadingDataBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(2, m_debugBuffer))\n            .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(3, accels.clusterVertexNormalsBuffer))\n            .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_fillClustersParamsBuffer));\n\n        nvrhi::BindingSetHandle bindingSet;\n        if (!nvrhi::utils::CreateBindingSetAndLayout(m_device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_fillClustersBL, bindingSet))\n        {\n            log::fatal(\"Failed to create binding set and layout for fill_clusters.hlsl\");\n        }\n\n        auto GetFillClustersPSO = [this](const FillClustersPermutation& shaderPermutation)\n            {\n                if (!m_fillClustersPSOs[shaderPermutation.index()])\n                {\n                    std::vector<donut::engine::ShaderMacro> fillClustersMacros;\n                    fillClustersMacros.push_back(donut::engine::ShaderMacro(\"DISPLACEMENT_MAPS\", shaderPermutation.isDisplacementEnabled() ? \"1\" : \"0\"));\n                    fillClustersMacros.push_back(donut::engine::ShaderMacro(\"VERTEX_NORMALS\", shaderPermutation.isVertexNormalsEnabled() ? \"1\" : \"0\"));\n                    fillClustersMacros.push_back(donut::engine::ShaderMacro(\"SURFACE_TYPE\", toString(shaderPermutation.surfaceType())));\n                    nvrhi::ShaderHandle shader = m_shaderFactory.CreateShader(\"cluster_builder/fill_clusters.hlsl\", \"FillClustersMain\", &fillClustersMacros, nvrhi::ShaderType::Compute);\n\n                    auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n                        .setComputeShader(shader)\n                        .addBindingLayout(m_fillClustersBL)\n                        .addBindingLayout(m_bindlessBL);\n\n                    m_fillClustersPSOs[shaderPermutation.index()] = m_device->createComputePipeline(computePipelineDesc);\n                }\n                return m_fillClustersPSOs[shaderPermutation.index()];\n            };\n        \n        if (!m_fillClustersTexcoordsPSO)\n        {\n            nvrhi::ShaderHandle shader = m_shaderFactory.CreateShader(\"cluster_builder/fill_clusters.hlsl\", \"FillClustersTexcoordsMain\", nullptr, nvrhi::ShaderType::Compute);\n\n            auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n                .setComputeShader(shader)\n                .addBindingLayout(m_fillClustersBL)\n                .addBindingLayout(m_bindlessBL);\n\n            m_fillClustersTexcoordsPSO = m_device->createComputePipeline(computePipelineDesc);\n        }\n\n        auto state = nvrhi::ComputeState()\n            .addBindingSet(bindingSet)\n            .addBindingSet(m_descriptorTable)\n            .setIndirectParams(m_fillClustersDispatchIndirectBuffer);\n\n        if (m_tessellatorConfig.enableMonolithicClusterBuild)\n        {\n            FillClustersPermutation shaderPermutation = { subd.m_hasDisplacementMaterial, m_tessellatorConfig.enableVertexNormals, ShaderPermutationSurfaceType::All };\n            state.setPipeline(GetFillClustersPSO(shaderPermutation));\n            commandList->setComputeState(state);\n            uint32_t dispatchIndirectArgsOffset = (instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::Limit) * uint32_t(m_fillClustersDispatchIndirectBuffer.GetElementBytes());\n            commandList->dispatchIndirect(dispatchIndirectArgsOffset);\n        }\n        else\n        {\n            for (uint32_t i = 0; i <= uint32_t(ShaderPermutationSurfaceType::Limit); i++)\n            {\n                FillClustersPermutation shaderPermutation = { subd.m_hasDisplacementMaterial, m_tessellatorConfig.enableVertexNormals, ShaderPermutationSurfaceType(i) };\n                state.setPipeline(GetFillClustersPSO(shaderPermutation));\n                commandList->setComputeState(state);\n                uint32_t dispatchIndirectArgsOffset = (instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType(i)) * uint32_t(m_fillClustersDispatchIndirectBuffer.GetElementBytes());\n                commandList->dispatchIndirect(dispatchIndirectArgsOffset);\n            }\n        }\n        \n        state.setPipeline(m_fillClustersTexcoordsPSO);\n        commandList->setComputeState(state);\n        uint32_t dispatchIndirectArgsOffset = (instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::All) * uint32_t(m_fillClustersDispatchIndirectBuffer.GetElementBytes());\n        commandList->dispatchIndirect(dispatchIndirectArgsOffset);\n\n        surfaceOffset += surfaceCount;\n\n        if (m_tessellatorConfig.debugSurfaceIndex >= 0 &&\n            m_tessellatorConfig.debugClusterIndex >= 0 &&\n            m_tessellatorConfig.debugLaneIndex >= 0)\n        {\n            donut::log::info(\"Fill Clusters Debug Instance:%d Mesh:%s (Surface:%u Cluster:%u Lane:%u)\", instanceIndex, donutMeshInfo->name.c_str(), m_tessellatorConfig.debugSurfaceIndex,\n                m_tessellatorConfig.debugClusterIndex, m_tessellatorConfig.debugLaneIndex);\n            \n            auto debugOutput = m_debugBuffer.Download(commandList);\n            uint numElements = debugOutput.front().payloadType;\n            vectorlog::Log(debugOutput, ShaderDebugElement::OutputLambda, vectorlog::FormatOptions{ .wrap = false, .header = false, .elementIndex = false, .startIndex = 1, .count = numElements });\n        }\n    }\n\n    stats::clusterAccelSamplers.fillClustersTime.Stop();\n}\n\nvoid ClusterAccelBuilder::ComputeInstanceClusterTiling(ClusterAccels& accels, \n    const RTXMGScene& scene,\n    uint32_t instanceIndex,\n    uint32_t surfaceOffset,\n    uint32_t surfaceCount,\n    const nvrhi::BufferRange& tessCounterRange,\n    nvrhi::ICommandList* commandList)\n{\n\n    const auto& subdMeshes = scene.GetSubdMeshes();\n    const auto& instance = scene.GetSubdMeshInstances()[instanceIndex];\n\n    const SubdivisionSurface& subdivisionSurface = *subdMeshes[instance.meshID];\n\n    assert(instance.meshInstance.get());\n    const auto& donutMeshInfo = instance.meshInstance->GetMesh();\n    assert(donutMeshInfo.get());\n    uint32_t firstGeometryIndex = donutMeshInfo->geometries[0]->globalGeometryIndex;\n    const donut::math::affine3& localToWorld = instance.localToWorld;\n\n    if (m_tessellatorConfig.debugSurfaceIndex >= 0 &&\n        m_tessellatorConfig.debugLaneIndex >= 0)\n    {\n        commandList->clearBufferUInt(m_debugBuffer, 0);\n    }\n\n    ComputeClusterTilingParams params = {};\n    params.debugSurfaceIndex = uint32_t(m_tessellatorConfig.debugSurfaceIndex);\n    params.debugLaneIndex = uint32_t(m_tessellatorConfig.debugLaneIndex);\n    params.matWorldToClip = m_tessellatorConfig.camera->GetProjectionMatrix() * m_tessellatorConfig.camera->GetViewMatrix();\n    affineToColumnMajor(localToWorld, params.localToWorld.m_data); // params.localToWorld;\n    params.viewportSize.x = float(m_tessellatorConfig.viewportSize.x);\n    params.viewportSize.y = float(m_tessellatorConfig.viewportSize.y);\n    params.firstGeometryIndex = firstGeometryIndex;\n    params.isolationLevel = m_tessellatorConfig.isolationLevel;\n    params.coarseTessellationRate = m_tessellatorConfig.coarseTessellationRate;\n    params.fineTessellationRate = m_tessellatorConfig.fineTessellationRate;\n    params.cameraPos = m_tessellatorConfig.camera->GetEye();\n    params.aabb = subdivisionSurface.m_aabb * localToWorld;\n    params.enableBackfaceVisibility = m_tessellatorConfig.enableBackfaceVisibility;\n    params.enableFrustumVisibility = m_tessellatorConfig.enableFrustumVisibility;\n    params.enableHiZVisibility = m_tessellatorConfig.enableHiZVisibility && m_tessellatorConfig.zbuffer != nullptr;\n    params.edgeSegments = m_tessellatorConfig.edgeSegments;\n    params.globalDisplacementScale = m_tessellatorConfig.displacementScale;\n\n    params.maxClasBlocks = uint32_t(m_maxClasBytes / size_t(cluster::kClasByteAlignment));\n    params.maxClusters = m_maxClusters;\n    params.maxVertices = m_maxVertices;\n    params.clusterVertexPositionsBaseAddress = accels.clusterVertexPositionsBuffer.GetGpuVirtualAddress();\n    params.clasDataBaseAddress = accels.clasBuffer.GetGpuVirtualAddress();\n\n    if (m_tessellatorConfig.zbuffer)\n    {\n        params.numHiZLODs = m_tessellatorConfig.zbuffer->GetNumHiZLODs();\n        params.invHiZSize = m_tessellatorConfig.zbuffer->GetInvHiZSize();\n    }\n    \n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_computeClusterTilingParamsBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, subdivisionSurface.m_positionsBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(1, scene.GetGeometryBuffer()))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(2, scene.GetMaterialBuffer()))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(3, subdivisionSurface.m_surfaceToGeometryIndexBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(4, subdivisionSurface.m_vertexDeviceData.surfaceDescriptors))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(5, subdivisionSurface.m_vertexDeviceData.controlPointIndices))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(6, subdivisionSurface.m_vertexDeviceData.patchPointsOffsets))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(7, subdivisionSurface.GetTopologyMap()->plansBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(8, subdivisionSurface.GetTopologyMap()->subpatchTreesArraysBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(9, subdivisionSurface.GetTopologyMap()->patchPointIndicesArraysBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(10, subdivisionSurface.GetTopologyMap()->stencilMatrixArraysBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(11, m_templateBuffers.instantiationSizesBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(12, m_templateBuffers.addressesBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(13, subdivisionSurface.m_texcoordDeviceData.surfaceDescriptors))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(14, subdivisionSurface.m_texcoordDeviceData.controlPointIndices))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(15, subdivisionSurface.m_texcoordDeviceData.patchPointsOffsets))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(16, subdivisionSurface.m_texcoordsBuffer))\n        .addItem(nvrhi::BindingSetItem::Sampler(0, scene.GetDisplacementSampler()))\n        .addItem(nvrhi::BindingSetItem::Sampler(1, m_commonPasses->m_LinearClampSampler)) // hiZ sampler\n        \n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, m_gridSamplersBuffer,\n            nvrhi::Format::UNKNOWN,\n            nvrhi::BufferRange(surfaceOffset * sizeof(GridSampler), surfaceCount * sizeof(GridSampler))))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(1, m_tessellationCountersBuffer, nvrhi::Format::UNKNOWN, tessCounterRange))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(2, m_clustersBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(3, accels.clusterShadingDataBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(4, m_clasIndirectArgDataBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(5, accels.clasPtrsBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(6, subdivisionSurface.m_vertexDeviceData.patchPoints))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(7, subdivisionSurface.m_texcoordDeviceData.patchPoints))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(8, m_debugBuffer));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(m_device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_computeClusterTilingBL, bindingSet, true))\n    {\n        log::fatal(\"Failed to create binding set and layout for compute_cluster_tiling.hlsl\");\n    }\n\n    nvrhi::BindingLayoutDesc hizLayoutDesc;\n    nvrhi::BindingSetDesc hizSetDesc;\n    m_tessellatorConfig.zbuffer->GetHiZDesc(&hizLayoutDesc, &hizSetDesc);\n    \n    if (!m_computeClusterTilingHizBL)\n    {\n        hizLayoutDesc\n            .setVisibility(nvrhi::ShaderType::Compute)\n            .setRegisterSpace(1)\n            .setRegisterSpaceIsDescriptorSet(true);\n        m_computeClusterTilingHizBL = m_device->createBindingLayout(hizLayoutDesc);\n        if (!m_computeClusterTilingHizBL)\n        {\n            log::fatal(\"Failed to create hiz binding layout for compute_cluster_tiling.hlsl\");\n        }\n    }\n\n    nvrhi::BindingSetHandle hizSet = m_device->createBindingSet(hizSetDesc, m_computeClusterTilingHizBL);\n    if (!hizSet)\n    {\n        log::fatal(\"Failed to create hiz binding set for compute_cluster_tiling.hlsl\");\n    }\n\n    ComputeClusterTilingPermutation shaderPermutation(subdivisionSurface.m_hasDisplacementMaterial,\n        m_tessellatorConfig.enableFrustumVisibility,\n        m_tessellatorConfig.tessMode,\n        m_tessellatorConfig.visMode,\n        ShaderPermutationSurfaceType::PureBSpline);\n\n    auto GetComputeClusterTilingPSO = [this](const ComputeClusterTilingPermutation& shaderPermutation)\n        {\n            if (!m_computeClusterTilingPSOs[shaderPermutation.index()])\n            {\n                std::vector<donut::engine::ShaderMacro> macros;\n                macros.push_back(donut::engine::ShaderMacro(\"DISPLACEMENT_MAPS\", shaderPermutation.isDisplacementEnabled() ? \"1\" : \"0\"));\n                macros.push_back(donut::engine::ShaderMacro(\"TESS_MODE\", toString(shaderPermutation.tessellationMode())));\n                macros.push_back(donut::engine::ShaderMacro(\"ENABLE_FRUSTUM_VISIBILITY\", shaderPermutation.isFrustumVisibilityEnabled() ? \"1\" : \"0\"));\n                macros.push_back(donut::engine::ShaderMacro(\"VIS_MODE\", toString(shaderPermutation.visibilityMode())));\n                macros.push_back(donut::engine::ShaderMacro(\"SURFACE_TYPE\", toString(shaderPermutation.surfaceType())));\n\n                nvrhi::ShaderDesc tilingDesc(nvrhi::ShaderType::Compute);\n                nvrhi::ShaderHandle shader = m_shaderFactory.CreateShader(\"cluster_builder/compute_cluster_tiling.hlsl\", \"main\", &macros, tilingDesc);\n\n                auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n                    .setComputeShader(shader)\n                    .addBindingLayout(m_computeClusterTilingBL)\n                    .addBindingLayout(m_computeClusterTilingHizBL)\n                    .addBindingLayout(m_bindlessBL);\n\n                m_computeClusterTilingPSOs[shaderPermutation.index()] = m_device->createComputePipeline(computePipelineDesc);\n            }\n            return m_computeClusterTilingPSOs[shaderPermutation.index()];\n        };\n\n    auto state = nvrhi::ComputeState()\n        .addBindingSet(bindingSet)\n        .addBindingSet(hizSet)\n        .addBindingSet(m_descriptorTable);\n\n    if (m_tessellatorConfig.enableMonolithicClusterBuild)\n    {\n        // Skip no limit surfaces\n        params.surfaceStart = 0;\n        params.surfaceEnd = subdivisionSurface.m_surfaceOffsets[uint32_t(SubdivisionSurface::SurfaceType::NoLimit)];\n        uint32_t dispatchCount = params.surfaceEnd - params.surfaceStart;\n\n        commandList->writeBuffer(m_computeClusterTilingParamsBuffer, &params, sizeof(ComputeClusterTilingParams));\n        ShaderPermutationSurfaceType shaderSurfaceType = ShaderPermutationSurfaceType::All;\n        shaderPermutation.setSurfaceType(shaderSurfaceType);\n        state.setPipeline(GetComputeClusterTilingPSO(shaderPermutation));\n        commandList->setComputeState(state);\n\n        commandList->dispatch(div_ceil(dispatchCount, kComputeClusterTilingWaves), 1, 1);\n\n        // Save cluster offset for this instance\n        ClusterDispatchType dispatchType = ClusterDispatchType::All;\n        CopyClusterOffset(instanceIndex, dispatchType, tessCounterRange, commandList);\n    }\n    else\n    {\n        // Loop\n        for (uint32_t i = 0; i <= uint32_t(ClusterDispatchType::Limit); i++)\n        {\n            SubdivisionSurface::SurfaceType subdSurfaceType = SubdivisionSurface::SurfaceType(i);\n\n            // Skip no limit surfaces\n            params.surfaceStart = subdivisionSurface.m_surfaceOffsets[uint32_t(subdSurfaceType)];\n            params.surfaceEnd = subdivisionSurface.m_surfaceOffsets[uint32_t(subdSurfaceType) + 1];\n\n            uint32_t dispatchCount = params.surfaceEnd - params.surfaceStart;\n            if (dispatchCount)\n            {\n                commandList->writeBuffer(m_computeClusterTilingParamsBuffer, &params, sizeof(ComputeClusterTilingParams));\n\n                ShaderPermutationSurfaceType shaderSurfaceType = ShaderPermutationSurfaceType(i);\n                shaderPermutation.setSurfaceType(shaderSurfaceType);\n                state.setPipeline(GetComputeClusterTilingPSO(shaderPermutation));\n                commandList->setComputeState(state);\n\n                commandList->dispatch(div_ceil(dispatchCount, kComputeClusterTilingWaves), 1, 1);\n            }\n            // Save cluster offset for this instance\n            ClusterDispatchType dispatchType = ClusterDispatchType(i);\n            CopyClusterOffset(instanceIndex, dispatchType, tessCounterRange, commandList);\n        }\n    }\n\n    if (m_tessellatorConfig.debugSurfaceIndex >= 0 &&\n        m_tessellatorConfig.debugLaneIndex >= 0)\n    {\n        donut::log::info(\"Cluster Tiling Debug Instance:%d Mesh:%s (Surface:%d, Lane:%d)\", instanceIndex, donutMeshInfo->name.c_str(), m_tessellatorConfig.debugSurfaceIndex, m_tessellatorConfig.debugLaneIndex);\n\n        auto debugOutput = m_debugBuffer.Download(commandList);\n        uint numElements = debugOutput.front().payloadType;\n        vectorlog::Log(debugOutput, ShaderDebugElement::OutputLambda, vectorlog::FormatOptions{ .wrap = false, .header = false, .elementIndex = false, .startIndex = 1, .count = numElements });\n    }\n\n    if (m_tessellatorConfig.enableLogging)\n    {\n        donut::log::info(\"Vertex PatchPoints:%d Mesh:%s\", instanceIndex, donutMeshInfo->name.c_str());\n        {\n            auto readBackDesc = GetReadbackDesc(subdivisionSurface.m_vertexDeviceData.patchPoints->getDesc());\n            auto readbackBuffer = commandList->getDevice()->createBuffer(readBackDesc);\n\n            std::vector<float3> patchPoints;\n            DownloadBuffer<float3>(subdivisionSurface.m_vertexDeviceData.patchPoints, patchPoints, readbackBuffer, false, commandList);\n            vectorlog::Log(patchPoints);\n        }\n\n        donut::log::info(\"Texcoord PatchPoints:%d Mesh:%s\", instanceIndex, donutMeshInfo->name.c_str());\n        {\n            auto readBackDesc = GetReadbackDesc(subdivisionSurface.m_texcoordDeviceData.patchPoints->getDesc());\n            auto readbackBuffer = commandList->getDevice()->createBuffer(readBackDesc);\n\n            std::vector<float2> patchPoints;\n            DownloadBuffer<float2>(subdivisionSurface.m_texcoordDeviceData.patchPoints, patchPoints, readbackBuffer, false, commandList);\n            vectorlog::Log(patchPoints);\n        }\n    }\n}\n\nvoid ClusterAccelBuilder::CopyClusterOffset(uint32_t instanceIndex,\n    ClusterDispatchType dispatchType, const nvrhi::BufferRange& tessCounterRange, nvrhi::ICommandList* commandList)\n{\n    nvrhi::utils::ScopedMarker marker(commandList, \"ClusterAccelBuilder::CopyClusterOffset\");\n    CopyClusterOffsetParams params;\n    params.instanceIndex = instanceIndex;\n    params.dispatchTypeIndex = uint32_t(dispatchType);\n    commandList->writeBuffer(m_copyClusterOffsetParamsBuffer, &params, sizeof(CopyClusterOffsetParams));\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_tessellationCountersBuffer, nvrhi::Format::UNKNOWN, tessCounterRange))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, m_clusterOffsetCountsBuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(1, m_fillClustersDispatchIndirectBuffer))\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_copyClusterOffsetParamsBuffer));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(m_device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_copyClusterOffsetBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for copy_cluster_offset shader\");\n    }\n\n    if (!m_copyClusterOffsetPSO)\n    {\n        nvrhi::ShaderHandle shader = m_shaderFactory.CreateShader(\"cluster_builder/copy_cluster_offset.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(shader)\n            .addBindingLayout(m_copyClusterOffsetBL);\n\n        m_copyClusterOffsetPSO = m_device->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_copyClusterOffsetPSO)\n        .addBindingSet(bindingSet);\n    commandList->setComputeState(state);\n    commandList->dispatch(1, 1, 1);\n}\n\nvoid ClusterAccelBuilder::BuildBlasFromClas(ClusterAccels& accels, std::span<Instance const> instances, nvrhi::ICommandList* commandList)\n{\n    //// Allocate and build BLASes\n    nvrhi::utils::ScopedMarker marker(commandList, \"Blas Build from Clas\");\n    stats::clusterAccelSamplers.buildBlasTime.Start(commandList);\n\n    uint32_t numInstances = static_cast<uint32_t>(instances.size());\n    nvrhi::GpuVirtualAddress clasPtrsBaseAddress = accels.clasPtrsBuffer.GetGpuVirtualAddress();\n    FillBlasFromClasArgs(m_blasFromClasIndirectArgsBuffer, m_clusterOffsetCountsBuffer, clasPtrsBaseAddress, numInstances, commandList);\n\n    if (m_tessellatorConfig.enableLogging)\n    {\n        m_blasFromClasIndirectArgsBuffer.Log(commandList, [](std::ostream& ss, const cluster::IndirectArgs& e)\n            {\n                ss << \"{c: \" << std::dec << e.clusterCount <<\n                    \" | addr: \" << std::hex << e.clusterAddresses << \"}\";\n                return true;\n            });\n    }\n\n    //// Build Operation\n    cluster::OperationDesc createBlasDesc =\n    {\n        .params = m_createBlasParams,\n        .scratchSizeInBytes = m_createBlasSizeInfo.scratchSizeInBytes,\n        .inIndirectArgCountBuffer = nullptr,\n        .inIndirectArgCountOffsetInBytes = 0,\n        .inIndirectArgsBuffer = m_blasFromClasIndirectArgsBuffer,\n        .inIndirectArgsOffsetInBytes = 0,\n        .inOutAddressesBuffer = accels.blasPtrsBuffer,\n        .inOutAddressesOffsetInBytes = 0,\n        .outSizesBuffer = accels.blasSizesBuffer,\n        .outSizesOffsetInBytes = 0,\n        .outAccelerationStructuresBuffer = accels.blasBuffer,\n        .outAccelerationStructuresOffsetInBytes = 0,\n    };\n    commandList->executeMultiIndirectClusterOperation(createBlasDesc);\n\n    stats::clusterAccelSamplers.buildBlasTime.Stop();\n}\nvoid ClusterAccelBuilder::UpdateMemoryAllocations(ClusterAccels& accels, uint32_t numInstances, uint32_t sceneSubdPatches)\n{\n    uint32_t maxClusters = std::min(kMaxApiClusterCount, m_tessellatorConfig.memorySettings.maxClusters);\n    maxClusters = std::max(1u, maxClusters);\n\n    // Reallocate memory if settings changed\n    size_t maxClusterBlocks = (m_tessellatorConfig.memorySettings.clasBufferBytes + (size_t(cluster::kClasByteAlignment) - 1ull)) / size_t(cluster::kClasByteAlignment);\n    maxClusterBlocks = std::max(1ull, maxClusterBlocks);\n    size_t maxClasBytes = size_t(cluster::kClasByteAlignment) * maxClusterBlocks;\n\n    // Calculate max vertices based on vertex buffer bytes (same for positions and normals since both are float3)\n    uint32_t maxVertices = uint32_t(m_tessellatorConfig.memorySettings.vertexBufferBytes / sizeof(float3));\n    maxVertices = std::max(kClusterMaxVertices, maxVertices);\n\n    bool numInstancesChanged = m_numInstances != numInstances;\n    bool sceneSubdPatchesChanged = m_sceneSubdPatches != sceneSubdPatches;\n    bool numClustersChanged = m_maxClusters != maxClusters;\n    bool clasBytesChanged = m_maxClasBytes != maxClasBytes;\n    bool maxVerticesChanged = m_maxVertices != maxVertices;\n\n    // Check if vertex normals setting changed by comparing current setting to buffer state\n    bool prevVertexNormalsEnabled = accels.clusterVertexNormalsBuffer.GetBuffer() != nullptr && accels.clusterVertexNormalsBuffer.GetNumElements() == m_maxVertices;\n    bool enableVertexNormalsChanged = (m_tessellatorConfig.enableVertexNormals != prevVertexNormalsEnabled);\n\n    m_numInstances = numInstances;\n    m_sceneSubdPatches = sceneSubdPatches;\n    m_maxClusters = maxClusters;\n    m_maxClasBytes = maxClasBytes;\n    m_maxVertices = maxVertices;\n    \n    // No allocations needed\n    if (!numInstancesChanged && !sceneSubdPatchesChanged && !numClustersChanged && !clasBytesChanged && !maxVerticesChanged && !enableVertexNormalsChanged)\n    {\n        return;\n    }\n\n    // Wait for idle to ensure resources are not in use\n    m_device->waitForIdle();\n\n    if (numInstancesChanged)\n    {\n        if (m_copyClusterOffsetParamsBuffer)\n            m_copyClusterOffsetParamsBuffer->Release();\n        m_clusterOffsetCountsBuffer.Release();\n        m_fillClustersDispatchIndirectBuffer.Release();\n        m_blasFromClasIndirectArgsBuffer.Release();\n        accels.blasPtrsBuffer.Release();\n        accels.blasSizesBuffer.Release();\n\n        m_copyClusterOffsetParamsBuffer = m_device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n            sizeof(CopyClusterOffsetParams), \"CopyClusterOffsetParams\", m_numInstances * ClusterDispatchType::NumTypes * kFrameCount));\n\n        m_clusterOffsetCountsBuffer.Create(m_numInstances * ClusterDispatchType::NumTypes, \"ClusterOffsets\", m_device);\n        nvrhi::BufferDesc dispatchIndirectDesc =\n        {\n            .byteSize = m_numInstances * ClusterDispatchType::NumTypes * m_fillClustersDispatchIndirectBuffer.GetElementBytes(),\n            .structStride = uint32_t(m_fillClustersDispatchIndirectBuffer.GetElementBytes()),\n            .debugName = \"FillClustersIndirectArgs\",\n            .canHaveUAVs = true,\n            .isDrawIndirectArgs = true,\n            .initialState = nvrhi::ResourceStates::IndirectArgument,\n            .keepInitialState = true,\n        };\n        m_fillClustersDispatchIndirectBuffer.Create(dispatchIndirectDesc, m_device);\n\n        // Create and fill out the instantiate args buffer from addressesBuffer\n        nvrhi::BufferDesc clusterIndirectArgsDesc = {\n            .byteSize = sizeof(cluster::IndirectArgs) * m_numInstances,\n            .structStride = sizeof(cluster::IndirectArgs),\n            .debugName = \"cluster::IndirectArgs\",\n            .canHaveUAVs = true,\n            .isAccelStructBuildInput = true,\n            .initialState = nvrhi::ResourceStates::ShaderResource,\n            .keepInitialState = true,\n        };\n        m_blasFromClasIndirectArgsBuffer.Create(clusterIndirectArgsDesc, m_device);\n        accels.blasPtrsBuffer.Create(m_numInstances, \"BlasPtrs\", m_device);\n        accels.blasSizesBuffer.Create(m_numInstances, \"BlasSizes\", m_device);\n    }\n\n    if (sceneSubdPatchesChanged)\n    {\n        m_gridSamplersBuffer.Release();\n        m_gridSamplersBuffer.Create(m_sceneSubdPatches, \"GridSamplers\", m_device);\n    }\n\n    if (numClustersChanged)\n    {\n        m_clustersBuffer.Release();\n        m_clasIndirectArgDataBuffer.Release();\n        accels.clusterShadingDataBuffer.Release();\n        accels.clasPtrsBuffer.Release();\n\n        m_clustersBuffer.Create(m_maxClusters, \"clusters\", m_device);\n        m_clasIndirectArgDataBuffer.Create(m_maxClusters, \"indirect arg data\", m_device);\n\n        accels.clusterShadingDataBuffer.Create(m_maxClusters, \"cluster shading data\", m_device);\n        accels.clasPtrsBuffer.Create(m_maxClusters, \"ClasAddresses\", m_device);\n    }\n\n    if (numClustersChanged || numInstancesChanged)\n    {\n        accels.blasBuffer.Release();\n        m_createBlasParams =\n        {\n            .maxArgCount = m_numInstances,\n            .type = cluster::OperationType::BlasBuild,\n            .mode = cluster::OperationMode::ImplicitDestinations,\n            .flags = cluster::OperationFlags::None,\n            .blas =\n            {\n                .maxClasPerBlasCount = m_maxClusters,\n                .maxTotalClasCount = m_maxClusters\n            }\n        };\n        m_createBlasSizeInfo = m_device->getClusterOperationSizeInfo(m_createBlasParams);\n\n        nvrhi::BufferDesc blasBufferDesc = {\n            .byteSize = m_createBlasSizeInfo.resultMaxSizeInBytes,\n            .debugName = \"Blas Data\",\n            .canHaveUAVs = true,\n            .isAccelStructStorage = true,\n            .initialState = nvrhi::ResourceStates::AccelStructWrite,\n            .keepInitialState = true,\n        };\n        accels.blasBuffer.Create(blasBufferDesc, m_device);\n    }\n\n    if (clasBytesChanged)\n    {\n        accels.clasBuffer.Release();\n\n        nvrhi::BufferDesc clasDataDesc =\n        {\n            .byteSize = m_maxClasBytes,\n            .debugName = \"ClasData\",\n            .canHaveUAVs = true,\n            .isAccelStructStorage = true,\n            .initialState = nvrhi::ResourceStates::AccelStructWrite,\n            .keepInitialState = true,\n        };\n        accels.clasBuffer.Create(clasDataDesc, m_device);\n    }\n\n    if (maxVerticesChanged)\n    {\n        accels.clusterVertexPositionsBuffer.Release();\n        accels.clusterVertexPositionsBuffer.Create(m_maxVertices, \"cluster vertex positions\", m_device);\n    }\n        \n    if (maxVerticesChanged || enableVertexNormalsChanged)\n    {\n        accels.clusterVertexNormalsBuffer.Release();\n        accels.clusterVertexNormalsBuffer.Create(m_tessellatorConfig.enableVertexNormals ? m_maxVertices : 1, \"cluster vertex normals\", m_device);\n    }\n}\n\nvoid ClusterAccelBuilder::BuildAccel(const RTXMGScene& scene, const TessellatorConfig& config,\n    ClusterAccels& accels, ClusterStatistics& stats, uint32_t frameIndex, nvrhi::ICommandList* commandList)\n{\n    m_tessellatorConfig = config;\n\n    const auto& subdMeshes = scene.GetSubdMeshes();\n    const auto& instances = scene.GetSubdMeshInstances();\n\n    if (subdMeshes.empty() || instances.empty())\n        return;\n\n    UpdateMemoryAllocations(accels, uint32_t(instances.size()), scene.TotalSubdPatchCount());\n\n    const uint32_t maxGeometryCountPerMesh = uint32_t(scene.GetSceneGraph()->GetMaxGeometryCountPerMesh());\n    InitStructuredClusterTemplates(maxGeometryCountPerMesh, commandList);\n    \n    nvrhi::utils::ScopedMarker marker(commandList, \"ClusterAccelBuilder::BuildAccel\");\n\n    uint32_t tessCounterIndex = (m_buildAccelFrameIndex % kFrameCount);\n    nvrhi::BufferRange tessCounterRange = { m_tessellationCountersBuffer.GetElementBytes() * tessCounterIndex, m_tessellationCountersBuffer.GetElementBytes() };\n\n    // Clear tessellation counters for this frame\n    TessellationCounters tessCounters = {};\n    m_tessellationCountersBuffer.UploadElement(tessCounters, tessCounterIndex, commandList);\n\n    commandList->clearBufferUInt(m_clusterOffsetCountsBuffer, 0);\n    commandList->clearBufferUInt(m_fillClustersDispatchIndirectBuffer, 0);\n\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"ComputeClusterTiling\");\n        stats::clusterAccelSamplers.clusterTilingTime.Start(commandList);\n        uint32_t surfaceOffset = 0;\n        for (uint32_t i = 0; i < instances.size(); ++i)\n        {\n            const auto& inst = instances[i];\n            const auto& subd = *subdMeshes[inst.meshID];\n\n            uint32_t surfaceCount{ subd.SurfaceCount() };\n\n            ComputeInstanceClusterTiling(accels, scene, i, surfaceOffset, surfaceCount, tessCounterRange, commandList);\n\n            surfaceOffset += surfaceCount;\n        }\n        stats::clusterAccelSamplers.clusterTilingTime.Stop();\n    }\n\n    if (m_tessellatorConfig.enableLogging)\n    {\n        // sync download to get current frame results\n        m_tessellationCountersBuffer.Log(commandList, [](std::ostream& ss, const TessellationCounters& e)\n            {\n                ss << \"{cluster: \" << e.desiredClusters << \"/\" << e.clusters \n                    << \", vertices: \" << e.desiredVertices\n                    << \", tri: \" << e.desiredTriangles\n                    << \", clasBytes: \" << e.DesiredClasBytes() << \"}\";\n                return true;\n            });\n        TessellationCounters currentTessCounts = m_tessellationCountersBuffer.Download(commandList, false)[tessCounterIndex];\n\n        log::info(\"tessellation counters: \");\n        log::info(\"  clusters: %u\", currentTessCounts.clusters);\n        log::info(\"  desiredClusters: %u\", currentTessCounts.desiredClusters);\n        log::info(\"  desiredVertices: %u\", currentTessCounts.desiredVertices);\n        log::info(\"  desiredTriangles: %u\", currentTessCounts.desiredTriangles);\n        log::info(\"  desiredClasBytes: %llu\", currentTessCounts.DesiredClasBytes());\n\n        m_fillClustersDispatchIndirectBuffer.Log(commandList);\n        m_clusterOffsetCountsBuffer.Log(commandList);\n        \n        m_gridSamplersBuffer.Log(commandList, [](std::ostream& ss, const GridSampler& e)\n            {\n                ss << \"{\" << e.edgeSegments.x << \", \" << e.edgeSegments.y << \", \" << e.edgeSegments.z << \", \" << e.edgeSegments.w;\n                return true;\n            });\n        \n        m_clustersBuffer.Log(commandList, [](std::ostream& ss, const Cluster& e)\n        {\n              ss << \"{surface:\" << e.iSurface << \", vertexOffset:\" << e.nVertexOffset\n                  << \", offset:\" << e.offset.x << \", \" << e.offset.y << \", size: \" << e.sizeX << \", \" << e.sizeY << \"}\";\n              return true;\n        }, { .count = std::min(currentTessCounts.clusters, 64u) });\n\n        accels.clusterShadingDataBuffer.Log(commandList, [](std::ostream& ss, auto& e)\n        {\n            ss << \"{surface id: \" << e.m_surfaceId\n                << \", edges:\" << e.m_edgeSegments.x << \", \" << e.m_edgeSegments.y << \", \" << e.m_edgeSegments.z << \", \" << e.m_edgeSegments.w\n                << \", texcoords: (\" << e.m_texcoords[0].x << \",\" << e.m_texcoords[0].y << \"), (\" << e.m_texcoords[1].x << \",\" << e.m_texcoords[1].y << \")\" << \", (\" << e.m_texcoords[2].x << \",\" << e.m_texcoords[2].y << \")\" << \", (\" << e.m_texcoords[3].x << \",\" << e.m_texcoords[3].y << \")\"\n                << \", voffset:\" << e.m_vertexOffset\n                << \", clusterOffset:\" << e.m_clusterOffset.x << \",\" << e.m_clusterOffset.y\n                << \", cluster_size:\" << e.m_clusterSizeX << \",\" << e.m_clusterSizeY\n                << \"}\";\n            return true;\n        }, { .count = std::min(currentTessCounts.clusters, 64u) });\n\n        m_clasIndirectArgDataBuffer.Log(commandList, [](std::ostream& ss, auto& e)\n        {\n            ss << \"{clusterId:\" << e.clusterIdOffset << \", geometryIndexOffset:\" << e.geometryIndexOffset\n                << \", clusterTemplate:0x\" << std::hex << e.clusterTemplate\n                << \", vertexBuffer:0x\" << std::hex << e.vertexBuffer.startAddress\n                << \", vertexBufferStride:\" << std::dec << e.vertexBuffer.strideInBytes\n                << \"}\";\n            return true;\n        }, { .count = std::min(currentTessCounts.clusters, 64u) });\n\n        accels.clasPtrsBuffer.Log(commandList, { .count = std::min(currentTessCounts.clusters, 64u) });\n    }\n\n    FillInstanceClusters(scene, accels, commandList);\n\n    // Build CLASes for all instances at once\n    stats::clusterAccelSamplers.buildClasTime.Start(commandList);\n    BuildStructuredCLASes(accels, maxGeometryCountPerMesh, tessCounterRange, commandList);\n    stats::clusterAccelSamplers.buildClasTime.Stop();\n\n    BuildBlasFromClas(accels, instances, commandList);\n    \n    // Async read of counters\n    auto counterBufferData = m_tessellationCountersBuffer.Download(commandList, true);\n    TessellationCounters counters = counterBufferData[(tessCounterIndex + 1) % kFrameCount];\n\n    // Record the desired required memory instead of the max\n    stats.desired.m_numTriangles = counters.desiredTriangles;\n    stats.desired.m_numClusters = counters.desiredClusters;\n    stats.desired.m_vertexBufferSize = accels.clusterVertexPositionsBuffer.GetElementBytes() * counters.desiredVertices;\n    stats.desired.m_vertexNormalsBufferSize = m_tessellatorConfig.enableVertexNormals ? \n        (accels.clusterVertexNormalsBuffer.GetElementBytes() * counters.desiredVertices) : 0;\n    stats.desired.m_clasSize = counters.DesiredClasBytes();\n    stats.desired.m_clusterDataSize = (m_clustersBuffer.GetElementBytes() + \n        accels.clusterShadingDataBuffer.GetElementBytes() +\n        accels.clasPtrsBuffer.GetElementBytes()) * counters.desiredClusters;\n    stats.desired.m_blasSize = m_createBlasSizeInfo.resultMaxSizeInBytes;\n    stats.desired.m_blasScratchSize = m_createBlasSizeInfo.scratchSizeInBytes;\n\n    // Atomics are expensive so we don't track the number of allocated triangles\n    stats.allocated.m_numTriangles = counters.desiredTriangles;\n    stats.allocated.m_numClusters = m_maxClusters;\n    stats.allocated.m_vertexBufferSize = accels.clusterVertexPositionsBuffer.GetBytes();\n    stats.allocated.m_vertexNormalsBufferSize = accels.clusterVertexNormalsBuffer.GetBytes();\n    stats.allocated.m_clasSize = accels.clasBuffer.GetBytes();\n    stats.allocated.m_clusterDataSize = m_clustersBuffer.GetBytes() + accels.clusterShadingDataBuffer.GetBytes() + accels.clasPtrsBuffer.GetBytes();\n    stats.allocated.m_blasSize = accels.blasBuffer.GetBytes();\n    stats.allocated.m_blasScratchSize = m_createBlasSizeInfo.scratchSizeInBytes;\n\n    m_buildAccelFrameIndex++;\n}\n"
  },
  {
    "path": "rtxmg/cluster_builder/shaders/compute_cluster_tiling.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n#pragma pack_matrix(row_major)\n\n#include \"rtxmg/cluster_builder/compute_cluster_tiling_params.h\"\n\n// TESS_MODE\n#define TESS_MODE_SPHERICAL_PROJECTION 0\n#define TESS_MODE_WORLD_SPACE_EDGE_LENGTH 1\n#define TESS_MODE_UNIFORM 2\n\n// VIS_MODE\n#define VIS_MODE_LIMIT_EDGES 0\n#define VIS_MODE_SURFACE 1\n\n#define PATCH_POINTS_WRITEABLE\n\n// Group atomics is significantly faster by reducing pressure on the global atomic\n// This reduces the global atomic by a factor of 4 (kComputeClusterTilingWavesPerSurface)\n#define ENABLE_GROUP_ATOMICS 1\n\n// Wave intrinsics do not appear to make any difference in performance\n// This is most likely since we use the same number of registers (otherwise we pay for local data stores/reads)\n#define ENABLE_WAVE_INTRINSICS 0\n\n#include \"rtxmg/utils/shader_debug.h\"\n\n#include <donut/shaders/material_cb.h>\n#include <donut/shaders/binding_helpers.hlsli>\n#include <donut/shaders/bindless.h>\n\n#include \"rtxmg/cluster_builder/cluster.h\"\n#include \"rtxmg/subdivision/subdivision_eval.hlsli\"\n#include \"rtxmg/cluster_builder/tessellation_counters.h\"\n#include \"rtxmg/cluster_builder/tessellator_constants.h\"\n#include \"rtxmg/cluster_builder/tilings.h\"\n#include \"rtxmg/subdivision/osd_ports/tmr/surfaceDescriptor.h\"\n#include \"rtxmg/subdivision/osd_ports/tmr/subdivisionNode.h\"\n#include \"rtxmg/subdivision/osd_ports/tmr/nodeDescriptor.h\"\n#include \"rtxmg/utils/box3.h\"\n#include \"rtxmg/hiz/hiz_buffer_constants.h\"\n\n#include \"rtxmg/cluster_builder/displacement.hlsli\"\n\nstatic const uint32_t kComputeClusterTilingLanes = 32;\n\nstatic const float kEpsilon = 1e-6f;\n\nConstantBuffer<ComputeClusterTilingParams> g_Params : register(b0);\n\nStructuredBuffer<float3> t_VertexControlPoints : register(t0);\nStructuredBuffer<GeometryData> t_GeometryData : register(t1);\nStructuredBuffer<MaterialConstants> t_MaterialConstants: register(t2);\nStructuredBuffer<uint16_t> t_SurfaceToGeometryIndex: register(t3);\nStructuredBuffer<SurfaceDescriptor> t_VertexSurfaceDescriptors : register(t4);\nStructuredBuffer<Index> t_VertexControlPointIndices : register(t5);\nStructuredBuffer<uint32_t> t_VertexPatchPointsOffsets : register(t6);\nStructuredBuffer<SubdivisionPlanHLSL> t_Plans : register(t7);\nStructuredBuffer<uint32_t> t_SubpatchTrees : register(t8);\nStructuredBuffer<Index> t_PatchPointIndices : register(t9);\nStructuredBuffer<float> t_StencilMatrix : register(t10);\nStructuredBuffer<uint32_t> t_ClasInstantiationBytes : register(t11);\nStructuredBuffer<nvrhi::GpuVirtualAddress> t_TemplateAddresses : register(t12);\nStructuredBuffer<LinearSurfaceDescriptor> t_TexCoordSurfaceDescriptors : register(t13);\nStructuredBuffer<Index> t_TexCoordControlPointIndices : register(t14);\nStructuredBuffer<uint32_t> t_TexCoordPatchPointsOffsets : register(t15);\nStructuredBuffer<float2> t_TexCoords : register(t16);\n\nRWStructuredBuffer<GridSampler> u_GridSamplers : register(u0);\nRWStructuredBuffer<TessellationCounters> u_TessellationCounters : register(u1);\nRWStructuredBuffer<Cluster> u_Clusters : register(u2);\nRWStructuredBuffer<ClusterShadingData> u_ClusterShadingData : register(u3);\nRWStructuredBuffer<nvrhi::rt::cluster::IndirectInstantiateTemplateArgs> u_IndirectArgData : register(u4);\nRWStructuredBuffer<nvrhi::GpuVirtualAddress> u_ClasAddresses : register(u5);\nRWStructuredBuffer<float3> u_VertexPatchPoints : register(u6);\nRWStructuredBuffer<float2> u_TexCoordPatchPoints : register(u7);\nRWStructuredBuffer<ShaderDebugElement> u_Debug : register(u8);\n\nSamplerState s_DisplacementSampler : register(s0);\nSamplerState s_HizSampler : register(s1);\n\nVK_BINDING(0, 1) Texture2D<float> t_HiZBuffer[HIZ_MAX_LODS]: register(t0, space1);\n\n\nconst static uint32_t nSamples = kComputeClusterTilingWaves * kNumWaveSurfaceUVSamples;\ngroupshared LimitFrame samples[nSamples];\n\n#if ENABLE_GROUP_ATOMICS\ngroupshared uint32_t s_clusters;\ngroupshared uint32_t s_vertices;\ngroupshared uint32_t s_clasBlocks;\ngroupshared uint32_t s_triangles;\n\ngroupshared uint32_t s_groupClasBlocksOffset;\ngroupshared uint32_t s_groupClusterOffset;\ngroupshared uint32_t s_groupVertexOffset;\n\ngroupshared bool s_allocationSucceeded;\n#endif\n\nbool HasLimit(uint32_t iSurface)\n{\n    return t_VertexSurfaceDescriptors[iSurface].HasLimit();\n}\n\nbool HiZIsVisible(Box3 aabb)\n{\n    // tests visibility of a screen-space aligned aabb against hi-z buffer\n    if (aabb.m_min.z > 0.f)\n    {\n        float oldminy = aabb.m_min.y;\n        float oldmaxy = aabb.m_max.y;\n\n        aabb.m_max.y = g_Params.viewportSize.y - oldminy;\n        aabb.m_min.y = g_Params.viewportSize.y - oldmaxy;\n\n        float invTileSize = 1.f / float(HIZ_LOD0_TILE_SIZE);\n\n        float sizeInTiles = max(aabb.m_max.x - aabb.m_min.x, aabb.m_max.y - aabb.m_min.y) * invTileSize;\n        uint32_t level = (uint32_t)ceil(log2(sizeInTiles));\n\n        if (level < g_Params.numHiZLODs)\n        {\n            float2 uv = float2(aabb.m_min.x + aabb.m_max.x, aabb.m_min.y + aabb.m_max.y) * .5f;\n            uv *= invTileSize * g_Params.invHiZSize;\n\n            int lw, lh;\n            t_HiZBuffer[level].GetDimensions(lw, lh);\n\n            // gather & reduce 4 texels in case the aabb straddles pixel boundaries\n            float4 z4 = t_HiZBuffer[level].Gather(s_HizSampler, uv, 0);\n            float zfar = max(max(z4.x, z4.y), max(z4.z, z4.w));\n\n            if (aabb.m_min.z > zfar)\n            {\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nfloat FrustumVisibility(SubdivisionEvaluatorHLSL subd, uint iLane)\n{\n    SurfaceDescriptor desc = subd.GetSurfaceDesc();\n    uint32_t numControlPoints = subd.GetPlan().m_data.numControlPoints;\n\n    Box3 aabb;\n    aabb.Init();\n\n    // if bit 0 is set, then X value is > +1\n    // if bit 1 is set, then X value is < -1\n    // bits 2, 3: same for Y\n    // bit 4: behind eye\n    uint signBits = 0xFF;\n\n    for (uint32_t i = iLane; i < numControlPoints; i += kComputeClusterTilingLanes)\n    {\n        Index index = subd.m_vertexControlPointIndices[desc.firstControlPoint + i];\n        float3 cp = subd.m_vertexControlPoints[index];\n\n        float4 p = { cp.x, cp.y, cp.z, 1.0f };\n        float3 pWorld = mul(g_Params.localToWorld, p);\n        float4 pClip = mul(g_Params.matWorldToClip, float4(pWorld, 1.0f));\n\n        uint16_t bits = 0u;\n        bits |= (uint16_t(pClip.x > pClip.w) << 0);\n        bits |= (uint16_t(pClip.x < -pClip.w) << 1);\n        bits |= (uint16_t(pClip.y > pClip.w) << 2);\n        bits |= (uint16_t(pClip.y < -pClip.w) << 3);\n        bits |= (uint16_t(pClip.w < 0) << 4); // behind eye\n\n        signBits &= bits;\n\n        // accumulate screen-space AABB\n\n        float3 pScreen = float3(\n            (.5f + pClip.x / pClip.w * .5f) * g_Params.viewportSize.x,\n            (.5f + pClip.y / pClip.w * .5f) * g_Params.viewportSize.y,\n            pClip.w);\n        aabb.Include(pScreen);\n    }\n\n    // reduce sign bits across lanes\n    uint surfaceSignBits = WaveActiveBitAnd(signBits);\n\n    bool visible = (surfaceSignBits == 0);\n    bool surfaceVisible = WaveReadLaneAt(visible, 0);\n\n    if (!surfaceVisible || !g_Params.enableHiZVisibility)\n    {\n        return surfaceVisible;\n    }\n\n    // butterfly reduction of AABB across lanes\n    for (int i = (kComputeClusterTilingLanes/2); i >= 1; i /= 2)\n    {\n        uint targetLane = WaveGetLaneIndex() ^ i;\n        aabb.m_min.x = min(aabb.m_min.x, WaveReadLaneAt(aabb.m_min.x, targetLane));\n        aabb.m_min.y = min(aabb.m_min.y, WaveReadLaneAt(aabb.m_min.y, targetLane));\n        aabb.m_min.z = min(aabb.m_min.z, WaveReadLaneAt(aabb.m_min.z, targetLane));\n\n        aabb.m_max.x = max(aabb.m_max.x, WaveReadLaneAt(aabb.m_max.x, targetLane));\n        aabb.m_max.y = max(aabb.m_max.y, WaveReadLaneAt(aabb.m_max.y, targetLane));\n        aabb.m_max.z = max(aabb.m_max.z, WaveReadLaneAt(aabb.m_max.z, targetLane));\n    }\n\n\n    if (iLane == 0 && aabb.Valid())\n    {\n        aabb.m_min.x = clamp(aabb.m_min.x, 0.f, g_Params.viewportSize.x);\n        aabb.m_max.x = clamp(aabb.m_max.x, 0.f, g_Params.viewportSize.x);\n        aabb.m_min.y = clamp(aabb.m_min.y, 0.f, g_Params.viewportSize.y);\n        aabb.m_max.y = clamp(aabb.m_max.y, 0.f, g_Params.viewportSize.y);\n\n        surfaceVisible = HiZIsVisible(aabb);\n    }\n\n    surfaceVisible = WaveReadLaneAt(surfaceVisible, 0);\n\n    return (float)surfaceVisible;\n}\n\nfloat CalculateVisibility(SubdivisionEvaluatorHLSL subd, uint iLane)\n{\n    float visibility = 1.0; // fully visible\n#if VIS_MODE == VIS_MODE_SURFACE\n    if (g_Params.enableFrustumVisibility)\n    {\n        visibility = FrustumVisibility(subd, iLane);\n    }\n#elif VIS_MODE == VIS_MODE_LIMIT_EDGES\n    // for edge visibility, all the work IsBSplinePatch done below int `calculateEdgeVisibility`\n#else\n#error UNKNOWN VIS MODE VIS_MODE\n#endif\n    return visibility;\n}\n\nfloat CalculateEdgeVisibility(uint32_t iLane, uint32_t waveSampleOffset, float visibility)\n{\n#if VIS_MODE == VIS_MODE_SURFACE\n    return visibility;\n#elif VIS_MODE == VIS_MODE_LIMIT_EDGES\n    //\n    // expected limitFrames layout:\n    //\n    //          e2\n    //\n    //     p6---p5---p4\n    //     |          |\n    // e3  p7        p3  e1\n    //     |          |\n    //     p0---p1---p2\n    //\n    //          e0\n    //\n\n    // each lane is assigned to one of the 4 surface edges\n    if (iLane > 3)\n        return 1.f;\n\n    visibility = 1.f;\n\n    // compute a normalized visibility factor for the 3 limit samples locations\n    // along the edge against the frustum, hi-z and back-facing criteria.\n    float3 pworld[3];\n    float4 pclip[3];\n\n    float frustumFactor = 0.f;\n    for (int i = 0; i < 3; ++i)\n    {\n        pworld[i] = samples[waveSampleOffset + (iLane * 2 + i) % kNumWaveSurfaceUVSamples].p;\n        pclip[i] = mul(g_Params.matWorldToClip, float4(pworld[i], 1.0));\n\n        float dist = (1.f / pclip[i].w) * sqrt(pclip[i].x * pclip[i].x + pclip[i].y * pclip[i].y);\n        frustumFactor = max(frustumFactor, pclip[i].w < 0.f ? 1.f : smoothstep(1.3f, 2.5f, dist));\n    }\n\n    if (g_Params.enableFrustumVisibility)\n    {\n        visibility *= (1.f - frustumFactor);\n    }\n\n    if (visibility == 0.f)\n        return visibility;\n\n    if (g_Params.enableHiZVisibility)\n    {\n        float3 pscreen[3];\n        [unroll]\n        for (uint16_t i = 0; i < 3; ++i)\n        {\n            pscreen[i].x = (.5f + pclip[i].x / pclip[i].w * .5f) * g_Params.viewportSize.x;\n            pscreen[i].y = (.5f + pclip[i].y / pclip[i].w * .5f) * g_Params.viewportSize.y;\n            pscreen[i].z = pclip[i].w;\n        }\n\n        Box3 aabb; \n        aabb.Init(pscreen[0], pscreen[1], pscreen[2]);\n        aabb.m_min.x = clamp(aabb.m_min.x, 0.f, g_Params.viewportSize.x);\n        aabb.m_max.x = clamp(aabb.m_max.x, 0.f, g_Params.viewportSize.x);\n        aabb.m_min.y = clamp(aabb.m_min.y, 0.f, g_Params.viewportSize.y);\n        aabb.m_max.y = clamp(aabb.m_max.y, 0.f, g_Params.viewportSize.y);\n\n        if (!HiZIsVisible(aabb))\n            return 0.f;\n    }\n\n    if (g_Params.enableBackfaceVisibility)\n    {\n        for (uint16_t i = 0; i < 3; ++i)\n        {\n            uint32_t sampleIndex = (iLane * 2 + i) % kNumWaveSurfaceUVSamples;\n            float3 t0 = samples[waveSampleOffset + sampleIndex].deriv1;\n            float3 t1 = samples[waveSampleOffset + sampleIndex].deriv2;\n\n            if (!IsParallel(t0, t1))\n            {\n                float3 nobj = cross(t0, t1);\n                float3 nworld = normalize(mul(g_Params.localToWorld, float4(nobj, 0.f)).xyz);\n                float cosTheta = dot(normalize(pworld[i] - g_Params.cameraPos), nworld);\n                float backfaceFactor = smoothstep(.6f, 1.f, cosTheta);\n\n                visibility *= (1.f - backfaceFactor);\n            }            \n        }\n    }\n    return visibility;\n#else\n#error UNKNOWN VIS MODE VIS_MODE\n#endif\n}\n\nvoid WaveEvaluateBSplinePatch8(uint32_t iWave,\n    uint32_t iLane,\n    SubdivisionEvaluatorHLSL subd,\n    TexcoordEvaluatorHLSL texcoordEval)\n{\n    uint32_t waveSampleOffset = kNumWaveSurfaceUVSamples * iWave;\n\n    // always do the non-displaced evaluation first.  Displacement maps will perturb this calculation below\n#if SURFACE_TYPE == SURFACE_TYPE_ALL\n    if (subd.IsPureBSplinePatch())\n    {\n        LimitFrame limit = subd.WaveEvaluatePureBsplinePatch8(iLane);\n        if (iLane < kNumWaveSurfaceUVSamples)\n        {\n            samples[waveSampleOffset + iLane] = limit;\n        }\n    }\n    else if (subd.IsBSplinePatch())\n    {\n        LimitFrame limit = subd.WaveEvaluateBsplinePatch(iLane);\n        if (iLane < kNumWaveSurfaceUVSamples)\n        {\n            samples[waveSampleOffset + iLane] = limit;\n        }\n    }\n    else\n    {\n        // there is no wave parallel implementation for non-bspline patches falling back to single thread\n        subd.WaveEvaluatePatchPoints(iLane, kComputeClusterTilingLanes);\n        if (iLane < kNumWaveSurfaceUVSamples)\n        {\n            LimitFrame limit = subd.EvaluateLimitSurface(kWaveSurfaceUVSamples[iLane]);\n            samples[waveSampleOffset + iLane] = limit;\n        }\n    }\n#elif SURFACE_TYPE == SURFACE_TYPE_PUREBSPLINE\n    LimitFrame limit = subd.WaveEvaluatePureBsplinePatch8(iLane);\n    if (iLane < kNumWaveSurfaceUVSamples)\n        samples[waveSampleOffset + iLane] = limit;\n#elif SURFACE_TYPE == SURFACE_TYPE_REGULARBSPLINE\n    LimitFrame limit = subd.WaveEvaluateBsplinePatch(iLane);\n    if (iLane < kNumWaveSurfaceUVSamples)\n    {\n        samples[waveSampleOffset + iLane] = limit;\n    }\n#elif SURFACE_TYPE == SURFACE_TYPE_LIMIT\n    // there is no wave parallel implementation for non-bspline patches falling back to single thread\n    subd.WaveEvaluatePatchPoints(iLane, kComputeClusterTilingLanes);\n    if (iLane < kNumWaveSurfaceUVSamples)\n    {\n        LimitFrame limit = subd.EvaluateLimitSurface(kWaveSurfaceUVSamples[iLane]);\n        samples[waveSampleOffset + iLane] = limit;\n    }\n#endif\n\n#if DISPLACEMENT_MAPS\n    uint32_t geometryIndex = t_SurfaceToGeometryIndex[subd.m_surfaceIndex] + g_Params.firstGeometryIndex;\n    GeometryData geometry = t_GeometryData[geometryIndex];\n    MaterialConstants material = t_MaterialConstants[geometry.materialIndex];\n\n    float displacementScale;\n    int displacementTexIndex;\n    GetDisplacement(material, g_Params.globalDisplacementScale, displacementTexIndex, displacementScale);\n    if (displacementTexIndex >= 0 && iLane < kNumWaveSurfaceUVSamples)\n    {\n        Texture2D<float> displacementTexture = ResourceDescriptorHeap[NonUniformResourceIndex(displacementTexIndex)];\n        LimitFrame displaced = DoDisplacement(texcoordEval,\n            samples[waveSampleOffset + iLane], subd.m_surfaceIndex, kWaveSurfaceUVSamples[iLane], 0, 0,\n            displacementTexture,\n            s_DisplacementSampler, displacementScale);\n        samples[waveSampleOffset + iLane] = displaced;\n    }\n#endif\n}\n\nfloat CalculateEdgeRates(LimitFrame limitFrame)\n{\n#if TESS_MODE == TESS_MODE_SPHERICAL_PROJECTION\n    const float3 poi = mul(g_Params.localToWorld, float4(limitFrame.p, 1.0f)).xyz;\n    const float distance = max(length(poi - g_Params.cameraPos), 0.01f);\n    float edgeRate = float(g_Params.viewportSize.y) * g_Params.fineTessellationRate / distance;\n    return edgeRate;\n#elif TESS_MODE == TESS_MODE_WORLD_SPACE_EDGE_LENGTH\n    float diagonalLength = length(g_Params.aabb.Extent());\n    return g_Params.fineTessellationRate * 1000.f / diagonalLength;\n#endif\n    return -1; // should not Get here; uniform tess mode doesn't call this function\n}\n\nuint16_t EvaluateEdgeSegments(uint32_t iWave, uint32_t iLane, float visibility, float visibilityRateMultiplier)\n{\n    uint32_t waveSampleOffset = kNumWaveSurfaceUVSamples * iWave;\n#if TESS_MODE == TESS_MODE_UNIFORM\n    if (iLane < 4)\n    {\n        uint32_t segments = g_Params.edgeSegments[iLane];\n        float segmentVisibility = CalculateEdgeVisibility(iLane, waveSampleOffset, visibility);\n\n        segments = float(segments) * (visibilityRateMultiplier + segmentVisibility * (1.f - visibilityRateMultiplier));\n        return (uint16_t)clamp(segments, 1u, 1024u);\n    }\n    return 0;\n#else\n    float segmentRate = iLane < 4 ? CalculateEdgeRates(samples[waveSampleOffset + 2 * iLane + 1]) : .0f;\n    if (iLane < kNumWaveSurfaceUVSamples)\n    {\n        samples[waveSampleOffset + iLane].p = mul(g_Params.localToWorld, float4(samples[waveSampleOffset + iLane].p, 1.0)).xyz;\n    }\n    float edgeLength = length(samples[waveSampleOffset + (iLane + 1) % kNumWaveSurfaceUVSamples].p - samples[waveSampleOffset + iLane % kNumWaveSurfaceUVSamples].p);\n\n    float finalEdgeLength = edgeLength;\n    float edgeLength2 = edgeLength;\n    if (iLane < 8)\n    {\n        edgeLength2 = edgeLength + WaveReadLaneAt(edgeLength, iLane + 1);\n\n        // Get the edge lengths starting at the corner vertices (in 2*iLane threads).\n        // if (2 * iLane < kNumWaveSurfaceUVSamples)\n        {\n            finalEdgeLength = WaveReadLaneAt(edgeLength2, 2 * iLane);\n        }\n    }\n\n    float edgeVisibility = CalculateEdgeVisibility(iLane, waveSampleOffset, visibility);\n    segmentRate *= (visibilityRateMultiplier + edgeVisibility * (1.f - visibilityRateMultiplier));\n\n#ifdef MAX_EDGES\n    return iLane < 4 ? min(MAX_EDGES, max(1u, (uint32_t)(round(edgeLength * segmentRate)))) : 0;\n#else\n    float edgeSegments = round(finalEdgeLength * segmentRate);\n\n    return iLane < 4 ? ((uint16_t)max(1.0, edgeSegments)) : 0;\n#endif\n\n#endif\n}\n\nvoid GathererWriteCluster(Cluster cluster, uint32_t clusterIndex, GridSampler gridSampler, uint32_t localGeometryIndex, nvrhi::GpuVirtualAddress templateAddress)\n{\n    const nvrhi::GpuVirtualAddress vertexBufferAddress = g_Params.clusterVertexPositionsBaseAddress + nvrhi::GpuVirtualAddress(cluster.nVertexOffset * sizeof(float3));\n\n    nvrhi::rt::cluster::IndirectInstantiateTemplateArgs indirectArgs = (nvrhi::rt::cluster::IndirectInstantiateTemplateArgs)0;\n    indirectArgs.clusterIdOffset = clusterIndex;\n    indirectArgs.geometryIndexOffset = localGeometryIndex;\n    indirectArgs.clusterTemplate = templateAddress;\n    indirectArgs.vertexBuffer.startAddress = vertexBufferAddress;\n    indirectArgs.vertexBuffer.strideInBytes = sizeof(float3);\n    u_IndirectArgData[clusterIndex] = indirectArgs;\n\n    ClusterShadingData shadingData = (ClusterShadingData)0;\n    shadingData.m_edgeSegments = gridSampler.edgeSegments;\n    shadingData.m_surfaceId = cluster.iSurface;\n    shadingData.m_vertexOffset = cluster.nVertexOffset;\n    shadingData.m_clusterOffset = cluster.offset;\n    shadingData.m_clusterSizeX = cluster.sizeX;\n    shadingData.m_clusterSizeY = cluster.sizeY;\n\n    u_ClusterShadingData[clusterIndex] = shadingData;\n}\n\nvoid WriteSurfaceWave(uint32_t iWave, uint32_t iLane, uint32_t iSurface, GridSampler rSampler)\n{\n    uint32_t waveSampleOffset = kNumWaveSurfaceUVSamples * iWave;\n    if (iLane == 0)\n    {\n        u_GridSamplers[iSurface] = rSampler;\n    }\n\n    uint16_t2 surfaceSize = rSampler.GridSize();\n    SurfaceTiling surfaceTiling = MakeSurfaceTiling(surfaceSize);\n\n    uint32_t clusterCount = 0;\n    uint32_t vertexCount = 0;\n    uint32_t clasBlocks = 0;\n    uint32_t clusterTris = 0;\n    \n#if ENABLE_WAVE_INTRINSICS\n    _Static_assert(SurfaceTiling::N_SUB_TILINGS <= kComputeClusterTilingLanes, \"Must have enough lanes to use wave ops\");\n    if (iLane < SurfaceTiling::N_SUB_TILINGS)\n    {\n        ClusterTiling clusterTiling = surfaceTiling.subTilings[iLane];\n        uint32_t templateIndex = GetTemplateIndex(clusterTiling.clusterSize);\n        uint32_t tilingClusterCount = clusterTiling.ClusterCount();\n        uint32_t tilingVertexCount = clusterTiling.VertexCount();\n\n        clasBlocks = (t_ClasInstantiationBytes[templateIndex] / nvrhi::rt::cluster::kClasByteAlignment) * tilingClusterCount;\n\n        clasBlocks += WaveReadLaneAt(clasBlocks, WaveGetLaneIndex() + 2); // Lanes: [0+2], [1+3]\n        clasBlocks += WaveReadLaneAt(clasBlocks, WaveGetLaneIndex() + 1); // Lanes: [0+1]\n\n        clusterCount = tilingClusterCount;\n        clusterCount += WaveReadLaneAt(clusterCount, WaveGetLaneIndex() + 2);\n        clusterCount += WaveReadLaneAt(clusterCount, WaveGetLaneIndex() + 1);\n\n        vertexCount = tilingVertexCount;\n        vertexCount += WaveReadLaneAt(vertexCount, WaveGetLaneIndex() + 2);\n        vertexCount += WaveReadLaneAt(vertexCount, WaveGetLaneIndex() + 1);\n    }\n#endif\n\n    bool allocationSucceeded = false;\n    // compute cluster and vertex offsets into linear storage using global counters\n    uint32_t surfaceClusterOffset, surfaceVertexOffset, clasBlocksOffset;\n    if (iLane == 0)\n    {\n#if !ENABLE_WAVE_INTRINSICS\n        [unroll]\n        for (uint16_t iTiling = 0; iTiling < surfaceTiling.N_SUB_TILINGS; ++iTiling)\n        {\n            ClusterTiling clusterTiling = surfaceTiling.subTilings[iTiling];\n            uint32_t templateIndex = GetTemplateIndex(clusterTiling.clusterSize);\n            uint32_t tilingClusterCount = clusterTiling.ClusterCount();\n            uint32_t tilingVertexCount = clusterTiling.VertexCount();\n\n            clusterCount += tilingClusterCount;\n            vertexCount += tilingVertexCount;\n            clasBlocks += (t_ClasInstantiationBytes[templateIndex] / nvrhi::rt::cluster::kClasByteAlignment) * tilingClusterCount;\n        }\n#endif\n        clusterTris = 2 * (uint32_t)surfaceSize.x * (uint32_t)surfaceSize.y;\n    }\n\n#if ENABLE_GROUP_ATOMICS\n    uint32_t waveClasBlocksOffset, waveSurfaceClusterOffset, waveSurfaceVertexOffset;\n    if (iLane == 0)\n    {\n        // Coalesce waves into group shared memory\n        uint32_t dummy;\n        InterlockedAdd(s_clasBlocks, clasBlocks, waveClasBlocksOffset);\n        InterlockedAdd(s_clusters, clusterCount, waveSurfaceClusterOffset);\n        InterlockedAdd(s_vertices, vertexCount, waveSurfaceVertexOffset);\n        InterlockedAdd(s_triangles, clusterTris, dummy);\n    \n        GroupMemoryBarrierWithGroupSync();\n    \n        // 1 global atomic per thread group\n        if (iWave == 0)\n        {\n            uint32_t dummy;\n            InterlockedAdd(u_TessellationCounters[0].desiredClasBlocks, s_clasBlocks, s_groupClasBlocksOffset);\n            InterlockedAdd(u_TessellationCounters[0].desiredClusters, s_clusters, s_groupClusterOffset);\n            InterlockedAdd(u_TessellationCounters[0].desiredVertices, s_vertices, s_groupVertexOffset);\n            InterlockedAdd(u_TessellationCounters[0].desiredTriangles, s_triangles, dummy);\n\n            allocationSucceeded = ((s_groupClasBlocksOffset + s_clasBlocks) <= g_Params.maxClasBlocks) &&\n                ((s_groupClusterOffset + s_clusters) <= g_Params.maxClusters) &&\n                ((s_groupVertexOffset + s_vertices) <= g_Params.maxVertices);\n\n            // If we passed all allocations increment the real cluster counter.\n            // This code used to track allocated clasBlocks, vertices, triangles \n            // but atomics are expensive and it doubled the number of stalls on atomics\n            if (allocationSucceeded)\n            {\n                InterlockedAdd(u_TessellationCounters[0].clusters, s_clusters, s_groupClusterOffset);\n            }\n\n            // write back global offset\n            s_allocationSucceeded = allocationSucceeded;\n        }\n\n        GroupMemoryBarrierWithGroupSync();\n\n        // Read back to each wave\n        clasBlocksOffset = s_groupClasBlocksOffset + waveClasBlocksOffset;\n        surfaceClusterOffset = s_groupClusterOffset + waveSurfaceClusterOffset;\n        surfaceVertexOffset = s_groupVertexOffset + waveSurfaceVertexOffset;\n        allocationSucceeded = s_allocationSucceeded;\n    }\n#else\n    if (iLane == 0)\n    {\n        uint32_t dummy;\n        InterlockedAdd(u_TessellationCounters[0].desiredClasBlocks, clasBlocks, clasBlocksOffset);\n        InterlockedAdd(u_TessellationCounters[0].desiredClusters, clusterCount, surfaceClusterOffset);\n        InterlockedAdd(u_TessellationCounters[0].desiredVertices, vertexCount, surfaceVertexOffset);\n        InterlockedAdd(u_TessellationCounters[0].desiredTriangles, clusterTris, dummy);\n\n        allocationSucceeded = ((clasBlocksOffset + clasBlocks) <= g_Params.maxClasBlocks) &&\n            ((surfaceClusterOffset + clusterCount) <= g_Params.maxClusters) &&\n            ((surfaceVertexOffset + vertexCount) <= g_Params.maxVertices);\n\n        // If we passed all allocations increment the real cluster counter.\n        // This code used to track allocated clasBlocks, vertices, triangles \n        // but atomics are expensive and it doubled the number of stalls on atomics\n        if (allocationSucceeded)\n        {\n            InterlockedAdd(u_TessellationCounters[0].clusters, clusterCount, surfaceClusterOffset);\n        }\n    }\n#endif\n\n    allocationSucceeded = WaveReadLaneAt(allocationSucceeded, 0);\n    if (!allocationSucceeded)\n    {\n        return;\n    }\n\n    surfaceClusterOffset = WaveReadLaneAt(surfaceClusterOffset, 0);\n    surfaceVertexOffset = WaveReadLaneAt(surfaceVertexOffset, 0);\n    clasBlocksOffset = WaveReadLaneAt(clasBlocksOffset, 0);\n\n    uint32_t tilingClusterOffset = surfaceClusterOffset;\n    uint32_t tilingVertexOffset = surfaceVertexOffset;\n    \n    nvrhi::GpuVirtualAddress tilingClusterBaseAddress = g_Params.clasDataBaseAddress + clasBlocksOffset * nvrhi::rt::cluster::kClasByteAlignment;\n    uint32_t localGeometryIndex = t_SurfaceToGeometryIndex[iSurface];\n\n    // Unroll so that we don't have local data loads from the surface tiling array\n    [unroll]\n    for (uint16_t iTiling = 0; iTiling < surfaceTiling.N_SUB_TILINGS; ++iTiling)\n    {\n        const ClusterTiling clusterTiling = surfaceTiling.subTilings[iTiling];\n        uint32_t tilingClusterCount = clusterTiling.ClusterCount();\n        uint32_t tilingClusterVertexCount = clusterTiling.ClusterVertexCount();\n        uint16_t2 tilingClusterSize = clusterTiling.clusterSize;\n        uint32_t tilingVertexCount =  tilingClusterCount * tilingClusterVertexCount;\n\n        const uint32_t templateIndex = GetTemplateIndex(tilingClusterSize);\n        nvrhi::GpuVirtualAddress templateAddress = t_TemplateAddresses[templateIndex];\n        uint32_t clasBytes = t_ClasInstantiationBytes[templateIndex];\n\n        // make clusters with tilingSize\n        for (uint32_t iCluster = iLane;\n            iCluster < tilingClusterCount;\n            iCluster += kComputeClusterTilingLanes)\n        {\n            Cluster cluster = MakeCluster(iSurface, tilingVertexOffset + tilingClusterVertexCount * iCluster,\n                    surfaceTiling.ClusterOffset(iTiling, iCluster), tilingClusterSize.x, tilingClusterSize.y);\n            uint32_t clusterIndex = tilingClusterOffset + iCluster;\n            u_Clusters[clusterIndex] = cluster;\n\n            GathererWriteCluster(cluster, clusterIndex, rSampler, localGeometryIndex, templateAddress);\n\n            u_ClasAddresses[clusterIndex] = tilingClusterBaseAddress + clasBytes * iCluster;\n        }\n        tilingClusterOffset += tilingClusterCount;\n        tilingVertexOffset += tilingVertexCount;\n        tilingClusterBaseAddress += clasBytes * tilingClusterCount;\n    }\n}\n\n[numthreads(kComputeClusterTilingLanes * kComputeClusterTilingWaves, 1, 1)]\nvoid main(uint3 threadIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID)\n{  \n    const uint32_t iLane = threadIdx.x % kComputeClusterTilingLanes;\n    const uint32_t iWave = threadIdx.x / kComputeClusterTilingLanes;\n    const uint32_t iSurface = kComputeClusterTilingWaves * groupIdx.x + iWave + g_Params.surfaceStart;\n\n    SHADER_DEBUG_INIT(u_Debug, uint2(g_Params.debugSurfaceIndex, g_Params.debugLaneIndex), uint2(iSurface, iLane));\n\n#if ENABLE_GROUP_ATOMICS\n    if (iLane == 0 && iWave == 0)\n    {\n        s_clusters = 0;\n        s_vertices = 0;\n        s_clasBlocks = 0;\n        s_triangles = 0;\n        s_allocationSucceeded = false;\n    }\n    GroupMemoryBarrierWithGroupSync();\n#endif\n\n    uint numSurfaceDescriptors, surfaceDescriptorStride;\n    t_VertexSurfaceDescriptors.GetDimensions(numSurfaceDescriptors, surfaceDescriptorStride);\n\n    if (iSurface >= g_Params.surfaceEnd)\n    {\n        return; // early out waves beyond cluster array end\n    }\n\n    if (!HasLimit(iSurface))\n    {\n        return; // don't process surfaces that have no limit\n    }\n\n    SubdivisionEvaluatorHLSL subd;\n    subd.m_surfaceIndex = iSurface;\n\n    subd.m_isolationLevel = uint16_t(g_Params.isolationLevel);\n    subd.m_surfaceDescriptors = t_VertexSurfaceDescriptors;\n    subd.m_plans = t_Plans;\n    subd.m_subpatchTrees = t_SubpatchTrees;\n    subd.m_vertexPatchPointIndices = t_PatchPointIndices;\n    subd.m_stencilMatrix = t_StencilMatrix;\n\n    subd.m_vertexControlPointIndices = t_VertexControlPointIndices;\n    subd.m_vertexControlPoints = t_VertexControlPoints;\n    subd.m_vertexPatchPointsOffsets = t_VertexPatchPointsOffsets;\n    subd.m_vertexPatchPoints = u_VertexPatchPoints;\n\n    TexcoordEvaluatorHLSL texcoordEval;\n    texcoordEval.m_surfaceDescriptors = t_TexCoordSurfaceDescriptors;\n    texcoordEval.m_texcoordControlPointIndices = t_TexCoordControlPointIndices;\n    texcoordEval.m_texcoordPatchPointsOffsets = t_TexCoordPatchPointsOffsets;\n    texcoordEval.m_texcoordPatchPoints = u_TexCoordPatchPoints;\n    texcoordEval.m_texcoordControlPoints = t_TexCoords;\n    texcoordEval.WaveEvaluateTexCoordPatchPoints(iLane, iSurface);\n\n    // Frustum \"culling\"\n    float visibility = CalculateVisibility(subd, iLane);\n\n    // -------------------------------------------------------------------------\n    // Evaluate corner and mid points for surface quad\n    // -------------------------------------------------------------------------\n    //\n    // sample locations:\n    //\n    //          e2\n    //\n    //     p6---p5---p4\n    //     |          |\n    // e3  p7        p3  e1\n    //     |          |\n    //     p0---p1---p2\n    //\n    //          e0\n    //\n    WaveEvaluateBSplinePatch8(iWave, iLane, subd, texcoordEval);\n\n    const float tessFactor = g_Params.coarseTessellationRate / g_Params.fineTessellationRate;\n\n    uint16_t edgeSegments = EvaluateEdgeSegments(iWave, iLane, visibility, tessFactor);\n\n    GridSampler rSampler;\n    rSampler.edgeSegments[0] = WaveReadLaneAt(edgeSegments, 0);\n    rSampler.edgeSegments[1] = WaveReadLaneAt(edgeSegments, 1);\n    rSampler.edgeSegments[2] = WaveReadLaneAt(edgeSegments, 2);\n    rSampler.edgeSegments[3] = WaveReadLaneAt(edgeSegments, 3);\n    \n    WriteSurfaceWave(iWave, iLane, iSurface, rSampler);\n}"
  },
  {
    "path": "rtxmg/cluster_builder/shaders/copy_cluster_offset.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n#pragma pack_matrix(row_major)\n\n#include \"rtxmg/cluster_builder/copy_cluster_offset_params.h\"\n#include \"rtxmg/cluster_builder/tessellation_counters.h\"\n#include \"rtxmg/cluster_builder/fill_clusters_params.h\"\n\nStructuredBuffer<TessellationCounters> t_TessellationCounters : register(t0);\nRWStructuredBuffer<uint2> u_ClusterOffsetCounts : register(u0);\nRWStructuredBuffer<uint3> u_FillClustersIndirectArgs : register(u1);\nConstantBuffer<CopyClusterOffsetParams> g_Params : register(b0);\n\n[numthreads(1, 1, 1)]\nvoid main(uint3 threadIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID)\n{\n    uint totalClusterCount = t_TessellationCounters[0].clusters;\n\n    // Offsets goes by the order of ClusterDispatchType\n    // PureBSpline Clusters\n    // RegularBSpline Clusters\n    // Limit Clusters\n    // All Clusters\n    if (g_Params.dispatchTypeIndex <= ClusterDispatchType::Limit)\n    {\n        uint dispatchIndex = g_Params.instanceIndex * ClusterDispatchType::NumTypes + g_Params.dispatchTypeIndex;\n        uint dispatchClusterCount = 0;\n        if (dispatchIndex == 0)\n        {\n            dispatchClusterCount = totalClusterCount;\n            u_ClusterOffsetCounts[0] = uint2(0, dispatchClusterCount);\n        }\n        else\n        {\n            uint2 previousOffsetCount = u_ClusterOffsetCounts[dispatchIndex - 1];\n            uint instanceOffset = previousOffsetCount.x + previousOffsetCount.y;\n            dispatchClusterCount = totalClusterCount - instanceOffset;\n            u_ClusterOffsetCounts[dispatchIndex] = uint2(instanceOffset, dispatchClusterCount);\n        }\n\n        // Write the number of clusters for the surface type\n        const uint32_t vertThreadGroupsX = (dispatchClusterCount + kFillClustersVerticesWaves - 1) / kFillClustersVerticesWaves;\n        u_FillClustersIndirectArgs[dispatchIndex] = uint3(vertThreadGroupsX, 1, 1);\n    }\n\n    // Write the total number of clusters for the instance\n    if (g_Params.dispatchTypeIndex == ClusterDispatchType::Limit || g_Params.dispatchTypeIndex == ClusterDispatchType::All)\n    {\n        uint32_t instanceTotalIndex = g_Params.instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::All;\n        uint dispatchClusterCount = 0;\n        if (g_Params.instanceIndex == 0)\n        {\n            dispatchClusterCount = totalClusterCount;\n            u_ClusterOffsetCounts[instanceTotalIndex] = uint2(0, dispatchClusterCount);\n        }\n        else\n        {\n            uint2 previousOffsetCount = u_ClusterOffsetCounts[(g_Params.instanceIndex - 1) * ClusterDispatchType::NumTypes + ClusterDispatchType::All];\n            uint instanceOffset = previousOffsetCount.x + previousOffsetCount.y;\n            dispatchClusterCount = totalClusterCount - instanceOffset;\n            u_ClusterOffsetCounts[instanceTotalIndex] = uint2(instanceOffset, dispatchClusterCount);\n        }\n\n        if (g_Params.dispatchTypeIndex == ClusterDispatchType::All)\n        {\n            // Write the number of clusters for the surface type\n            const uint32_t vertThreadGroupsX = (dispatchClusterCount + kFillClustersVerticesWaves - 1) / kFillClustersVerticesWaves;\n            u_FillClustersIndirectArgs[g_Params.instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::Limit] = uint3(vertThreadGroupsX, 1, 1);\n        }\n\n        const uint32_t texcoordsThreadGroupsX = (dispatchClusterCount + kFillClustersTexcoordsThreadsX - 1) / kFillClustersTexcoordsThreadsX;\n        u_FillClustersIndirectArgs[g_Params.instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::All] = uint3(texcoordsThreadGroupsX, 1, 1);\n    }\n}"
  },
  {
    "path": "rtxmg/cluster_builder/shaders/fill_blas_from_clas_args.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n#pragma pack_matrix(row_major)\n\n#include <nvrhi/nvrhiHLSL.h>\n#include \"rtxmg/cluster_builder/fill_blas_from_clas_args_params.h\"\n#include \"rtxmg/cluster_builder/copy_cluster_offset_params.h\"\n\nConstantBuffer<FillBlasFromClasArgsParams> g_Params : register(b0);\n\nStructuredBuffer<uint2> t_ClusterOffsetCounts : register(t0);\nRWStructuredBuffer<nvrhi::rt::cluster::IndirectArgs> u_BlasFromClasArgs : register(u0);\n\n[numthreads(kFillBlasFromClasArgsThreads, 1, 1)]\nvoid main(uint3 threadIdx : SV_DispatchThreadID)\n{\n    uint32_t instanceIndex = threadIdx.x;\n    if (instanceIndex > g_Params.numInstances)\n        return;\n\n    uint2 offsetCount = t_ClusterOffsetCounts[instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::All];\n\n    nvrhi::rt::cluster::IndirectArgs args = (nvrhi::rt::cluster::IndirectArgs)0;\n    args.clusterCount = offsetCount.y;\n    args.clusterAddresses = g_Params.clasAddressesBaseAddress + sizeof(nvrhi::GpuVirtualAddress) * offsetCount.x;\n    u_BlasFromClasArgs[instanceIndex] = args;\n}"
  },
  {
    "path": "rtxmg/cluster_builder/shaders/fill_clusters.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#include <nvrhi/nvrhiHLSL.h>\n\n#include \"rtxmg/utils/shader_debug.h\"\n\n#include <donut/shaders/bindless.h>\n#include <donut/shaders/binding_helpers.hlsli>\n\n#include \"rtxmg/cluster_builder/fill_clusters_params.h\"\n#include \"rtxmg/cluster_builder/copy_cluster_offset_params.h\"\n#include \"rtxmg/subdivision/vertex.h\"\n\n#define ENABLE_GROUP_PUREBSPLINE 1\n\n// No other way to pass groupshared array to SubdivisionSurfaceEvaluator\n#if SURFACE_TYPE == SURFACE_TYPE_PUREBSPLINE && ENABLE_GROUP_PUREBSPLINE\ngroupshared float3 s_patchControlPoints[kPatchSize * kFillClustersVerticesWaves];\n#define GROUP_SHARED_CONTROL_POINTS(waveIndex, controlPointIndex) s_patchControlPoints[waveIndex * kPatchSize + controlPointIndex]\n#endif\n\n#include \"rtxmg/subdivision/subdivision_eval.hlsli\"\n#include \"rtxmg/cluster_builder/tessellator_constants.h\"\n#include \"rtxmg/subdivision/subdivision_plan_hlsl.h\"\n#include \"rtxmg/cluster_builder/cluster.h\"\n\n#include \"rtxmg/cluster_builder/fill_instantiate_template_args_params.h\"\n#include \"rtxmg/cluster_builder/displacement.hlsli\"\n\n#include \"rtxmg/subdivision/osd_ports/tmr/surfaceDescriptor.h\"\n\nStructuredBuffer<GridSampler> t_GridSamplers : register(t0);\nStructuredBuffer<uint2> t_ClusterOffsetCounts : register(t1);\nStructuredBuffer<Cluster> t_Clusters : register(t2);\n\n// Buffers for the subd surface \nStructuredBuffer<float3> t_VertexControlPoints : register(t3);\nStructuredBuffer<SurfaceDescriptor> t_VertexSurfaceDescriptors : register(t4);\nStructuredBuffer<Index> t_VertexControlPointIndices : register(t5);\nStructuredBuffer<uint32_t> t_VertexPatchPointsOffsets : register(t6);\nStructuredBuffer<SubdivisionPlanHLSL> t_Plans : register(t7);\nStructuredBuffer<uint32_t> t_SubpatchTrees : register(t8);\nStructuredBuffer<Index> t_PatchPointIndices : register(t9);\nStructuredBuffer<float> t_StencilMatrix : register(t10);\nStructuredBuffer<float3> t_VertexPatchPoints : register(t11);\n\n// Displacement/Materials\nStructuredBuffer<GeometryData> t_GeometryData : register(t12);\nStructuredBuffer<MaterialConstants> t_MaterialConstants : register(t13);\nStructuredBuffer<uint16_t> t_SurfaceToGeometryIndex : register(t14);\n\n// Texcoord evaluation\nStructuredBuffer<LinearSurfaceDescriptor> t_TexCoordSurfaceDescriptors : register(t15);\nStructuredBuffer<Index> t_TexCoordControlPointIndices : register(t16);\nStructuredBuffer<uint32_t> t_TexCoordPatchPointsOffsets : register(t17);\nStructuredBuffer<float2> t_TexCoordPatchPoints : register(t18);\nStructuredBuffer<float2> t_TexCoords : register(t19);\n\n// Buffers for the gatherer\nRWStructuredBuffer<float3> u_ClusterVertexPositions : register(u0);\nRWStructuredBuffer<ClusterShadingData> u_ClusterShadingData : register(u1);\nRWStructuredBuffer<ShaderDebugElement> u_Debug : register(u2);\n#if VERTEX_NORMALS\nRWStructuredBuffer<float3> u_ClusterVertexNormals : register(u3);\n#endif\n\nSamplerState s_DisplacementSampler : register(s0);\n\n\nConstantBuffer<FillClustersParams> g_TessParams : register(b0);\n\nvoid GathererWriteLimit(LimitFrame vertexLimit, Cluster cluster, uint32_t vertexIndex)\n{\n    u_ClusterVertexPositions[cluster.nVertexOffset + vertexIndex] = quantize(vertexLimit.p, g_TessParams.quantNBits);\n}\n\n#if VERTEX_NORMALS\nvoid GathererWriteNormal(LimitFrame vertexLimit, Cluster cluster, uint32_t vertexIndex, SubdivisionEvaluatorHLSL subd)\n{\n    // Use the subdivision evaluator's robust normal calculation method\n    float3 normal = subd.CalculateLimitFrameNormal(vertexLimit);\n    u_ClusterVertexNormals[cluster.nVertexOffset + vertexIndex] = normal;\n}\n#endif\n\nvoid GathererWriteTexcoord(TexCoordLimitFrame texcoord, uint32_t clusterIndex, uint32_t cornerIndex)\n{\n    u_ClusterShadingData[clusterIndex].m_texcoords[cornerIndex] = texcoord.uv;\n}\n\n[numthreads(kFillClustersVerticesLanes * kFillClustersVerticesWaves, 1, 1)]\nvoid FillClustersMain(uint3 threadIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID)\n{\n    uint32_t iLane = threadIdx.x % kFillClustersVerticesLanes;\n    uint32_t iWave = threadIdx.x / kFillClustersVerticesLanes;\n    const uint32_t groupClusterIndex = groupIdx.x * kFillClustersVerticesWaves + iWave;\n\n#if SURFACE_TYPE == SURFACE_TYPE_ALL\n    uint32_t kDispatchTypeIndex = ClusterDispatchType::All;\n#elif SURFACE_TYPE == SURFACE_TYPE_PUREBSPLINE\n    uint32_t kDispatchTypeIndex = ClusterDispatchType::PureBSpline;\n#elif SURFACE_TYPE == SURFACE_TYPE_REGULARBSPLINE\n    uint32_t kDispatchTypeIndex = ClusterDispatchType::RegularBSpline;\n#elif SURFACE_TYPE == SURFACE_TYPE_LIMIT\n    uint32_t kDispatchTypeIndex = ClusterDispatchType::Limit;\n#endif\n\n    uint2 offsetCount = t_ClusterOffsetCounts[g_TessParams.instanceIndex * ClusterDispatchType::NumTypes + kDispatchTypeIndex];\n    if (groupClusterIndex >= offsetCount.y)\n        return; // early out waves beyond cluster array end\n\n    uint32_t clusterIndex = groupClusterIndex + offsetCount.x;\n    const Cluster rCluster = t_Clusters[clusterIndex];\n\n    // Shader debug only\n    uint surfaceIndex = rCluster.iSurface;\n    uint linearClusterOffset = (rCluster.offset.y * rCluster.sizeX) + rCluster.offset.x;\n\n    SHADER_DEBUG_INIT(u_Debug, uint3(g_TessParams.debugSurfaceIndex, g_TessParams.debugClusterIndex, g_TessParams.debugLaneIndex), uint3(surfaceIndex, linearClusterOffset, iLane));\n    \n    const uint32_t iSurface = rCluster.iSurface;\n    const GridSampler rSampler = t_GridSamplers[iSurface];\n\n    SubdivisionEvaluatorHLSL subd;\n    subd.m_surfaceIndex = iSurface;\n\n    subd.m_isolationLevel = uint16_t(g_TessParams.isolationLevel);\n    subd.m_surfaceDescriptors = t_VertexSurfaceDescriptors;\n    subd.m_plans = t_Plans;\n    subd.m_subpatchTrees = t_SubpatchTrees;\n    subd.m_vertexPatchPointIndices = t_PatchPointIndices;\n    subd.m_stencilMatrix = t_StencilMatrix;\n\n    subd.m_vertexControlPointIndices = t_VertexControlPointIndices;\n    subd.m_vertexControlPoints = t_VertexControlPoints;\n    subd.m_vertexPatchPointsOffsets = t_VertexPatchPointsOffsets;\n    subd.m_vertexPatchPoints = t_VertexPatchPoints;\n\n#if DISPLACEMENT_MAPS\n    TexcoordEvaluatorHLSL texcoordEval;\n    texcoordEval.m_surfaceDescriptors = t_TexCoordSurfaceDescriptors;\n    texcoordEval.m_texcoordControlPointIndices = t_TexCoordControlPointIndices;\n    texcoordEval.m_texcoordPatchPointsOffsets = t_TexCoordPatchPointsOffsets;\n    texcoordEval.m_texcoordPatchPoints = t_TexCoordPatchPoints;\n    texcoordEval.m_texcoordControlPoints = t_TexCoords;\n\n    float displacementScale;\n    int displacementTexIndex;\n\n    uint32_t geometryIndex = t_SurfaceToGeometryIndex[iSurface] + g_TessParams.firstGeometryIndex;\n    GeometryData geometry = t_GeometryData[geometryIndex];\n    MaterialConstants material = t_MaterialConstants[geometry.materialIndex];\n\n    GetDisplacement(material, g_TessParams.globalDisplacementScale, displacementTexIndex, displacementScale);\n#endif\n\n#if SURFACE_TYPE == SURFACE_TYPE_PUREBSPLINE && ENABLE_GROUP_PUREBSPLINE\n    subd.PrefetchPatchControlPoints(iWave, iLane);\n#endif\n\n    {\n        // wave wide loop\n        for (uint16_t pointIndex = (uint16_t)iLane; pointIndex < rCluster.VerticesPerCluster(); pointIndex += kFillClustersVerticesLanes)\n        {\n            float2 uv = rSampler.UV(rCluster.Linear2Idx2D(pointIndex) + rCluster.offset, (ClusterPattern)g_TessParams.clusterPattern);\n\n            // always do the non-displaced evaluation first.  Displacement maps will perturb this calculation below\n#if SURFACE_TYPE == SURFACE_TYPE_ALL\n            LimitFrame limit = subd.Evaluate(uv);\n#elif SURFACE_TYPE == SURFACE_TYPE_PUREBSPLINE\n        #if ENABLE_GROUP_PUREBSPLINE\n            LimitFrame limit = subd.EvaluatePureBsplinePatchGroupShared(iWave, uv);\n        #else\n            LimitFrame limit = subd.EvaluatePureBsplinePatch(uv);\n        #endif\n#elif SURFACE_TYPE == SURFACE_TYPE_REGULARBSPLINE\n            LimitFrame limit = subd.EvaluateBsplinePatch(uv);\n#elif SURFACE_TYPE == SURFACE_TYPE_LIMIT\n            LimitFrame limit = subd.EvaluateLimitSurface(uv);\n#endif\n\n#if DISPLACEMENT_MAPS\n            if (displacementTexIndex >= 0)\n            {\n                Texture2D<float> displacementTexture = ResourceDescriptorHeap[NonUniformResourceIndex(displacementTexIndex)];\n\n                float du = rSampler.DU(uv);\n                float dv = rSampler.DV(uv);\n                \n                limit = DoDisplacement(texcoordEval,\n                        limit, iSurface, uv, du, dv,\n                        displacementTexture,\n                        s_DisplacementSampler, displacementScale);\n            }\n#endif\n\n            GathererWriteLimit(limit, rCluster, pointIndex);\n\n#if VERTEX_NORMALS\n            GathererWriteNormal(limit, rCluster, pointIndex, subd);\n#endif\n        }\n    }\n}\n\n[numthreads(kFillClustersTexcoordsThreadsX * 4, 1, 1)]\nvoid FillClustersTexcoordsMain(uint3 threadIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID)\n{\n    const uint32_t clusterThreadIdx = threadIdx.x % kFillClustersTexcoordsThreadsX;\n    const uint32_t cornerIdx = threadIdx.x / kFillClustersTexcoordsThreadsX;\n    const uint32_t groupClusterIndex = groupIdx.x * kFillClustersTexcoordsThreadsX + clusterThreadIdx;\n    uint2 offsetCount = t_ClusterOffsetCounts[g_TessParams.instanceIndex * ClusterDispatchType::NumTypes + ClusterDispatchType::All];\n    if (groupClusterIndex >= offsetCount.y)\n        return; // early out waves beyond cluster array end\n\n    uint32_t clusterIndex = groupClusterIndex + offsetCount.x;\n    const Cluster rCluster = t_Clusters[clusterIndex];\n    const uint32_t iSurface = rCluster.iSurface;\n    const GridSampler rSampler = t_GridSamplers[iSurface];\n\n    // Extra shading data: texcoords on corners of surfaces (patches). This might not exactly match texcoords used in displacement\n    // above if the subd evaluator is cubic.\n    \n    // TODO: This can be removed here and the uvs in the surface corners should be written into a dedicated array in the coarse\n    // rasterizer. If we ever go to higher-order texture coord eval per surface, the per cluster corner uvs or even per vertex\n    // uvs can be added back.\n    const float2 kSurfaceUVs[4] = { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 0, 1 } };\n\n    TexcoordEvaluatorHLSL texcoordEval;\n    texcoordEval.m_surfaceDescriptors = t_TexCoordSurfaceDescriptors;\n    texcoordEval.m_texcoordControlPointIndices = t_TexCoordControlPointIndices;\n    texcoordEval.m_texcoordPatchPointsOffsets = t_TexCoordPatchPointsOffsets;\n    texcoordEval.m_texcoordPatchPoints = t_TexCoordPatchPoints;\n    texcoordEval.m_texcoordControlPoints = t_TexCoords;\n\n    TexCoordLimitFrame texcoord = texcoordEval.EvaluateLinearSubd(kSurfaceUVs[cornerIdx], iSurface);\n\n    GathererWriteTexcoord(texcoord, clusterIndex, cornerIdx);\n}"
  },
  {
    "path": "rtxmg/cluster_builder/shaders/fill_instance_descs.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n#pragma pack_matrix(row_major)\n\n#include <nvrhi/nvrhiHLSL.h>\n#include \"rtxmg/cluster_builder/fill_instance_descs_params.h\"\n\nConstantBuffer<FillInstanceDescsParams> g_Params : register(b0);\n\nStructuredBuffer<nvrhi::GpuVirtualAddress> t_BlasAddresses : register(t0);\nRWStructuredBuffer<nvrhi::rt::IndirectInstanceDesc> u_InstanceDescs : register(u0);\n\n[numthreads(kFillInstanceDescsThreads, 1, 1)]\nvoid main(uint3 threadIdx : SV_DispatchThreadID)\n{\n    uint32_t instanceIndex = threadIdx.x;\n    if (instanceIndex > g_Params.numInstances)\n        return;\n\n    nvrhi::GpuVirtualAddress blasAddress = t_BlasAddresses[instanceIndex];\n    \n    nvrhi::rt::IndirectInstanceDesc instanceDesc = u_InstanceDescs[instanceIndex];\n    instanceDesc.blasDeviceAddress = blasAddress;\n    u_InstanceDescs[instanceIndex] = instanceDesc;\n}"
  },
  {
    "path": "rtxmg/cluster_builder/shaders/fill_instantiate_template_args.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n#pragma pack_matrix(row_major)\n\n#include <nvrhi/nvrhiHLSL.h>\n#include \"rtxmg/cluster_builder/fill_instantiate_template_args_params.h\"\n\nConstantBuffer<FillInstantiateTemplateArgsParams> g_Params : register(b0);\n\nStructuredBuffer<nvrhi::GpuVirtualAddress> t_TemplateAddresses : register(t0);\nRWStructuredBuffer<nvrhi::rt::cluster::IndirectInstantiateTemplateArgs> u_InstantiateTemplateArgs : register(u0);\n\n[numthreads(kFillInstantiateTemplateArgsThreads, 1, 1)]\nvoid main(uint3 threadIdx : SV_DispatchThreadID)\n{\n    uint templateIndex = threadIdx.x;\n    if (templateIndex > g_Params.numTemplates)\n        return;\n\n    nvrhi::rt::cluster::IndirectInstantiateTemplateArgs args = (nvrhi::rt::cluster::IndirectInstantiateTemplateArgs)0;\n    args.clusterTemplate = t_TemplateAddresses[templateIndex];\n    args.vertexBuffer.startAddress = 0; // not providing vertex positions returns the worst case m_size \n    args.vertexBuffer.strideInBytes = 0;\n    u_InstantiateTemplateArgs[templateIndex] = args;\n}"
  },
  {
    "path": "rtxmg/cluster_builder/shaders.cfg",
    "content": "shaders/fill_clusters.hlsl -E \"FillClustersMain\" -T cs --compilerOptionsSPIRV \"-fvk-bind-resource-heap 0 1\" -D DISPLACEMENT_MAPS={0,1} -D VERTEX_NORMALS={0,1} -D SURFACE_TYPE={SURFACE_TYPE_PUREBSPLINE,SURFACE_TYPE_REGULARBSPLINE,SURFACE_TYPE_LIMIT,SURFACE_TYPE_ALL}\nshaders/fill_clusters.hlsl -E \"FillClustersTexcoordsMain\" -T cs --compilerOptionsSPIRV \"-fvk-bind-resource-heap 0 1\"\nshaders/copy_cluster_offset.hlsl -T cs\nshaders/fill_blas_from_clas_args.hlsl -T cs\nshaders/fill_instance_descs.hlsl -T cs\nshaders/fill_instantiate_template_args.hlsl -T cs\nshaders/compute_cluster_tiling.hlsl -T cs --compilerOptionsSPIRV \"-fvk-bind-resource-heap 0 2\" -D DISPLACEMENT_MAPS={0,1} -D TESS_MODE={TESS_MODE_SPHERICAL_PROJECTION,TESS_MODE_WORLD_SPACE_EDGE_LENGTH,TESS_MODE_UNIFORM} -D ENABLE_FRUSTUM_VISIBILITY={0,1} -D VIS_MODE={VIS_MODE_LIMIT_EDGES,VIS_MODE_SURFACE} -D SURFACE_TYPE={SURFACE_TYPE_PUREBSPLINE,SURFACE_TYPE_REGULARBSPLINE,SURFACE_TYPE_LIMIT,SURFACE_TYPE_ALL}\n"
  },
  {
    "path": "rtxmg/hiz/CMakeLists.txt",
    "content": "set(lib hiz)\nset(folder RTXMG)\ninclude (\"${DONUT_DIR}/compileshaders.cmake\")\n\nfile(GLOB shaders \"shaders/*\")\nfile(GLOB sources \"*.cpp\" \"../include/rtxmg/${lib}/*.h\" *.cfg)\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include\"\n)\ntarget_link_libraries(${lib} donut_engine implot)\nset_target_properties(${lib} PROPERTIES FOLDER ${folder})\n\ndonut_compile_shaders_all_platforms(\n    TARGET ${lib}_shaders\n    CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/shaders.cfg\n    FOLDER ${folder}\n    SOURCES ${shaders}\n    OUTPUT_BASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders/${lib}\n    SHADERMAKE_OPTIONS ${RTXMG_SHADERMAKE_OPTIONS}\n    SHADERMAKE_OPTIONS_SPIRV ${RTXMG_SHADERMAKE_OPTIONS_SPIRV}\n    SHADER_MODEL ${RTXMG_SHADERS_SHADERMODEL}\n    IGNORE_INCLUDES ${RTXMG_SHADERS_IGNORED_INCLUDES}\n    INCLUDES ${RTXMG_SHADERS_INCLUDE_DIR}\n)\nadd_dependencies(${lib} ${lib}_shaders)"
  },
  {
    "path": "rtxmg/hiz/hiz_buffer.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n\n#include \"rtxmg/hiz/hiz_buffer.h\"\n#include \"rtxmg/hiz/hiz_buffer_reduce_params.h\"\n#include \"rtxmg/hiz/hiz_buffer_display_params.h\"\n#include \"rtxmg/hiz/hiz_buffer_constants.h\"\n\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/utils/debug.h\"\n\n#include \"rtxmg/profiler/statistics.h\"\n\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/core/log.h>\n\nusing namespace donut;\n\nstd::unique_ptr<HiZBuffer> HiZBuffer::Create(uint2 size,\n    std::shared_ptr<engine::CommonRenderPasses> commonPasses,\n    std::shared_ptr<engine::ShaderFactory> shaderFactory,\n    nvrhi::ICommandList* commandList)\n{\n    auto device = commandList->getDevice();\n    auto hiz = std::make_unique<HiZBuffer>();\n\n    hiz->m_size = size;\n    hiz->m_invSize = { 1.f / float(size.x), 1.f / float(size.y) };\n    hiz->m_sampler = commonPasses->m_LinearClampSampler;\n\n    nvrhi::TextureDesc desc;\n    desc.isUAV = true;\n    desc.keepInitialState = true;\n    desc.format = nvrhi::Format::R32_FLOAT;\n    desc.initialState = nvrhi::ResourceStates::UnorderedAccess;\n\n    uint2 mipSize = hiz->m_size;\n\n    for (uint8_t level = 0; level < HIZ_MAX_LODS; ++level)\n    {\n        desc.width = mipSize.x;\n        desc.height = mipSize.y;\n\n        std::stringstream ss;\n        ss << \"HiZ Buffer Level \" << (int)level;\n\n        desc.debugName = ss.str();\n        hiz->textureObjects[level] = device->createTexture(desc);\n\n        hiz->m_numLODs = level + 1;\n\n        mipSize = { mipSize.x >> 1, mipSize.y >> 1 };\n        if (mipSize.x == 0 || mipSize.y == 0)\n            break;\n    }\n\n    for (uint8_t level = hiz->m_numLODs; level < HIZ_MAX_LODS; ++level)\n    {\n        desc.width = 1;\n        desc.height = 1;\n\n        std::stringstream ss;\n        ss << \"UNUSED HiZ Buffer Level \" << (int)level;\n\n        desc.debugName = ss.str();\n        hiz->textureObjects[level] = device->createTexture(desc);\n    }\n\n    nvrhi::ShaderDesc clearDesc(nvrhi::ShaderType::Compute);\n\n    hiz->m_pass1Shader = shaderFactory->CreateShader(\"hiz/hiz_pass1.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n    if (!hiz->m_pass1Shader)\n    {\n        log::fatal(\"Failed to create hiz pass 1 shader\");\n    }\n\n    hiz->m_pass2Shader = shaderFactory->CreateShader(\"hiz/hiz_pass2.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n    if (!hiz->m_pass2Shader)\n    {\n        log::fatal(\"Failed to create hiz pass 2 shader\");\n    }\n    hiz->m_displayShader = shaderFactory->CreateShader(\"hiz/hiz_display.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n    if (!hiz->m_displayShader)\n    {\n        log::fatal(\"Failed to create hiz display shader\");\n    }\n\n    hiz->m_reduceParamsBuffer = CreateBuffer(1, sizeof(HiZReducePass1Params), \"HiZReducePass1Params\", device);\n\n    hiz->m_displayParamsBuffer = device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(\n        sizeof(HiZDisplayParams), \"HiZDisplayParams\", engine::c_MaxRenderPassConstantBufferVersions));\n\n    return hiz;\n}\n\nvoid HiZBuffer::Display(nvrhi::ITexture* output, nvrhi::ICommandList* commandList)\n{\n    nvrhi::utils::ScopedMarker marker(commandList, \"HiZBuffer::display\");\n    static constexpr uint32_t spacing = 10u;\n\n    uint2 offset{ spacing, spacing };\n\n    auto device = commandList->getDevice();\n\n    nvrhi::BindingLayoutDesc bindingLayoutDesc;\n    nvrhi::BindingSetDesc bindingSetDesc;\n    GetDesc(&bindingLayoutDesc, &bindingSetDesc, true);\n    bindingLayoutDesc\n        .addItem(nvrhi::BindingLayoutItem::ConstantBuffer(0))\n        .addItem(nvrhi::BindingLayoutItem::Texture_UAV(0))\n        .setVisibility(nvrhi::ShaderType::Compute);\n    bindingSetDesc\n        .addItem(nvrhi::BindingSetItem::ConstantBuffer(0, m_displayParamsBuffer))\n        .addItem(nvrhi::BindingSetItem::Texture_UAV(0, output));\n\n    // need to write *something* to the constant buffer before we set up the compute state\n    HiZDisplayParams params;\n    params.level = 0;\n    params.offsetX = offset.x;\n    params.offsetY = offset.y;\n    commandList->writeBuffer(m_displayParamsBuffer, &params, sizeof(params));\n\n    if (!m_displayBL)\n    {\n        m_displayBL = device->createBindingLayout(bindingLayoutDesc);\n        if (!m_displayBL)\n        {\n            log::fatal(\"Failed to create binding layout for hiz display\");\n        }\n    }\n\n    nvrhi::BindingSetHandle bindingSet = device->createBindingSet(bindingSetDesc, m_displayBL);\n    if (!bindingSet)\n    {\n        log::fatal(\"Failed to create binding set for hiz display\");\n    }\n\n    if (!m_displayPSO)\n    {\n        nvrhi::ComputePipelineDesc computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(m_displayShader)\n            .addBindingLayout(m_displayBL);\n\n        m_displayPSO = device->createComputePipeline(computePipelineDesc);\n    }\n    \n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_displayPSO)\n        .addBindingSet(bindingSet);\n\n    commandList->setComputeState(state);\n\n    for (uint8_t level = 0; level < HIZ_MAX_LODS; level++)\n    {\n        if (!textureObjects[level])\n            continue;\n\n        constexpr int const blocksize = 32;\n\n        uint2 extent{ textureObjects[level]->getDesc().width,\n            textureObjects[level]->getDesc().height };\n        uint2 numBlocks{ extent.x / blocksize + 1,\n            extent.y / blocksize + 1 };\n\n        HiZDisplayParams params;\n        params.level = level;\n        params.offsetX = offset.x;\n        params.offsetY = offset.y;\n        commandList->writeBuffer(m_displayParamsBuffer, &params, sizeof(params));\n\n        commandList->dispatch(numBlocks.x, numBlocks.y);\n\n        offset.x += extent.x + spacing;\n    }\n}\n\nvoid HiZBuffer::GetDesc(nvrhi::BindingLayoutDesc* outBindingLayout, nvrhi::BindingSetDesc* outBindingSet, bool writeable) const\n{\n    *outBindingLayout = nvrhi::BindingLayoutDesc();\n    *outBindingSet = nvrhi::BindingSetDesc();\n\n    if (writeable)\n    {\n        outBindingLayout->addItem(nvrhi::BindingLayoutItem::Texture_UAV(0).\n            setSize(HIZ_MAX_LODS));\n        for (uint32_t i = 0; i < HIZ_MAX_LODS; ++i)\n        {\n            outBindingSet->addItem(nvrhi::BindingSetItem::Texture_UAV(0, textureObjects[i]).setArrayElement(i));\n        }\n    }\n    else\n    {\n        outBindingLayout->addItem(nvrhi::BindingLayoutItem::Texture_SRV(0).\n            setSize(HIZ_MAX_LODS));\n        for (uint32_t i = 0; i < HIZ_MAX_LODS; ++i)\n        {\n            outBindingSet->addItem(nvrhi::BindingSetItem::Texture_SRV(0, textureObjects[i]).setArrayElement(i));\n        }\n    }\n}\n\nvoid HiZBuffer::Reduce(nvrhi::ITexture* zbuffer, nvrhi::ICommandList* commandList)\n{\n    constexpr uint32_t const kGroupSizePass1 = HIZ_GROUP_SIZE * (1 << 3); // 128\n\n    uint32_t zwidth, zheight;\n\n    zwidth = zbuffer->getDesc().width;\n    zheight = zbuffer->getDesc().height;\n\n    if (zwidth < kGroupSizePass1 || zheight < kGroupSizePass1)\n        return;\n\n    //uint2 dispatchSize = { (uint32_t(zwidth) + kGroupSizePass1 - 1) / kGroupSizePass1,\n    //                   (uint32_t(zheight) + kGroupSizePass1 - 1) / kGroupSizePass1, };\n\n    uint2 dispatchSize = m_size;\n\n    auto device = commandList->getDevice();\n\n    HiZReducePass1Params params;\n    params.zBufferInvSize = float2(1.f / zwidth, 1.f / zheight);\n    commandList->writeBuffer(m_reduceParamsBuffer, &params, sizeof(params));\n\n    nvrhi::BindingLayoutDesc bindingLayoutDesc;\n    nvrhi::BindingSetDesc bindingSetDesc;\n    GetDesc(&bindingLayoutDesc, &bindingSetDesc, true);\n    bindingLayoutDesc\n        .addItem(nvrhi::BindingLayoutItem::Texture_SRV(0))\n        .addItem(nvrhi::BindingLayoutItem::StructuredBuffer_SRV(1))\n        .addItem(nvrhi::BindingLayoutItem::Sampler(0))\n        .setVisibility(nvrhi::ShaderType::Compute);\n    bindingSetDesc\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(0, zbuffer))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(1, m_reduceParamsBuffer))\n        .addItem(nvrhi::BindingSetItem::Sampler(0, m_sampler));\n\n    if (!m_passBL)\n    {\n        m_passBL = device->createBindingLayout(bindingLayoutDesc);\n        if (!m_passBL)\n        {\n            log::fatal(\"Failed to create binding layout for hiz reduce\");\n        }\n    }\n\n    nvrhi::BindingSetHandle bindingSet = device->createBindingSet(bindingSetDesc, m_passBL);\n    if (!bindingSet)\n    {\n        log::fatal(\"Failed to create binding set for hiz reduce\");\n    }\n  \n    if (!m_pass1PSO)\n    {\n        nvrhi::ComputePipelineDesc computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(m_pass1Shader)\n            .addBindingLayout(m_passBL);\n\n        m_pass1PSO = device->createComputePipeline(computePipelineDesc);\n\n        computePipelineDesc.setComputeShader(m_pass2Shader);\n        m_pass2PSO = device->createComputePipeline(computePipelineDesc);\n    }\n\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_pass1PSO)\n        .addBindingSet(bindingSet);\n\n    commandList->setComputeState(state);\n\n    stats::frameSamplers.hiZRenderTime.Start(commandList);\n    \n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"HiZBuffer::reduce pass 1\");\n        commandList->dispatch(dispatchSize.x, dispatchSize.y);\n    }\n\n    if (m_numLODs > 5)\n    {\n        nvrhi::utils::TextureUavBarrier(commandList, textureObjects[4]);\n\n        // apply in-place 1x reduction in second pass.\n        constexpr uint32_t const kGroupSizePass2 = HIZ_GROUP_SIZE;\n        uint2 dispatchSizePass2 = {\n            (uint32_t(m_size.x >> 4) + kGroupSizePass2 - 1) / kGroupSizePass2,\n            (uint32_t(m_size.y >> 4) + kGroupSizePass2 - 1) / kGroupSizePass2,\n        };\n\n        // ok to leave all bindings the same, just change shader\n        state.setPipeline(m_pass2PSO);\n        commandList->setComputeState(state);\n        nvrhi::utils::ScopedMarker marker(commandList, \"HiZBuffer::reduce pass 2\");\n        commandList->dispatch(dispatchSizePass2.x, dispatchSizePass2.y);\n    }\n\n    stats::frameSamplers.hiZRenderTime.Stop();\n}\n\nvoid HiZBuffer::Clear(nvrhi::ICommandList* commandList)\n{\n    nvrhi::utils::ScopedMarker marker(commandList, \"HiZBuffer::clear\");\n    for (uint8_t level = 0; level < HIZ_MAX_LODS; ++level)\n    {\n        if (textureObjects[level])\n        {\n            commandList->clearTextureFloat(textureObjects[level], nvrhi::AllSubresources, nvrhi::Color(std::numeric_limits<float>::max()));\n        }\n    }\n}\n"
  },
  {
    "path": "rtxmg/hiz/shaders/hiz_display.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#include \"rtxmg/hiz/hiz_buffer_display_params.h\"\n#include \"rtxmg/hiz/hiz_buffer_constants.h\"\n\nConstantBuffer<HiZDisplayParams> g_params: register(b0);\nTexture2D<float> u_hiz[HIZ_MAX_LODS]: register(t0);\nRWTexture2D<float4> output: register(u0);\n\n[numthreads(32, 32, 1)]\nvoid main(uint2 threadIdx : SV_GroupThreadID, uint2 dispatchThreadId : SV_DispatchThreadID)\n{\n    uint32_t width, height;\n    u_hiz[g_params.level].GetDimensions(width, height);\n\n    uint32_t outWidth, outHeight;\n    output.GetDimensions(outWidth, outHeight);\n\n    uint32_t x = dispatchThreadId.x;\n    uint32_t y = dispatchThreadId.y;\n\n    if ((x >= width) || (y >= height))\n    {\n        return;\n    }\n\n    float depth = u_hiz[g_params.level][dispatchThreadId];\n\n    uint32_t2 outputIdx = uint32_t2(x + g_params.offsetX, outHeight + y - height - g_params.offsetY);\n\n    if (isinf(depth))\n    {\n        output[outputIdx] = float4(1, 0, 0, 0);\n    }\n    else\n    {\n        depth = (depth <= 0.f) ? 1.f : 1.f / depth;\n        output[outputIdx] = float4(depth, depth, depth, 1);\n    }\n}"
  },
  {
    "path": "rtxmg/hiz/shaders/hiz_pass1.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#include \"rtxmg/hiz/hiz_buffer_reduce_params.h\"\n#include \"rtxmg/hiz/hiz_buffer_constants.h\"\n\nTexture2D<float> t_zbuffer: register(t0);\nStructuredBuffer<HiZReducePass1Params> g_params: register(t1);\nRWTexture2D<float> u_output[HIZ_MAX_LODS]: register(u0);\n\ngroupshared float s_reductionData[HIZ_GROUP_SIZE][HIZ_GROUP_SIZE];\nSamplerState s : register(s0);\n\ninline float Reduce(float a, float b)\n{\n    return max(a, b);\n}\n\ninline float Reduce(float4 a)\n{\n    return Reduce(Reduce(a.x, a.y), Reduce(a.z, a.w));\n}\n\n// perform 8x reduction using fixed-function pixel gather-4\ninline float GetZFarFromTile(uint2 tile, int tilesize)\n{\n    float2 uv = (float2(tile) * float(tilesize) + 1.f);\n\n    float zfar = 0;\n\n    uint2 offset;\n\n#pragma unroll 4\n    for (offset.y = 0; offset.y < tilesize; offset.y += 2)\n    {\n#pragma unroll 4\n        for (offset.x = 0; offset.x < tilesize; offset.x += 2)\n        {\n            float x = (uv.x + float(offset.x)) * g_params[0].zBufferInvSize.x;\n            float y = (uv.y + float(offset.y)) * g_params[0].zBufferInvSize.y;\n\n            float4 values = t_zbuffer.Gather(s, float2(x, y), 0);\n            zfar = Reduce(zfar, Reduce(values));\n        }\n    }\n    return zfar;\n}\n\n[numthreads(HIZ_GROUP_SIZE, HIZ_GROUP_SIZE, 1)]\nvoid main(uint2 threadIdx : SV_GroupThreadID, uint2 dispatchThreadId : SV_DispatchThreadID, uint2 groupIdx : SV_GroupID)\n{\n    uint32_t x = dispatchThreadId.x;\n    uint32_t y = dispatchThreadId.y;\n\n\n    float value = GetZFarFromTile(uint2(x, y), HIZ_LOD0_TILE_SIZE);\n    u_output[0][dispatchThreadId.xy] = value;\n\n    // level 0 dimensions are always a multiple of 16\n    // so this reduction will never miss any pixels\n\n    // reduce the next 4 LODs using shared memory across each block\n#pragma unroll\n    for (uint16_t level = 1; level < 5; ++level)\n    {\n        uint16_t outGroupSize = ((uint16_t)HIZ_GROUP_SIZE) >> level;\n        uint16_t inGroupSize = outGroupSize << 1;\n\n        if (threadIdx.x < inGroupSize && threadIdx.y < inGroupSize)\n        {\n            s_reductionData[threadIdx.y][threadIdx.x] = value;\n        }\n\n        GroupMemoryBarrierWithGroupSync();\n\n        if (threadIdx.x < outGroupSize && threadIdx.y < outGroupSize)\n        {\n            float a = s_reductionData[threadIdx.y * 2 + 0][threadIdx.x * 2 + 0];\n            float b = s_reductionData[threadIdx.y * 2 + 0][threadIdx.x * 2 + 1];\n            float c = s_reductionData[threadIdx.y * 2 + 1][threadIdx.x * 2 + 0];\n            float d = s_reductionData[threadIdx.y * 2 + 1][threadIdx.x * 2 + 1];\n\n            value = Reduce(float4(a, b, c, d));\n\n            uint32_t x = groupIdx.x * outGroupSize + threadIdx.x;\n            uint32_t y = groupIdx.y * outGroupSize + threadIdx.y;\n\n            u_output[level][uint2(x, y)] = value;\n        }\n        GroupMemoryBarrierWithGroupSync();\n    }\n}"
  },
  {
    "path": "rtxmg/hiz/shaders/hiz_pass2.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#include \"rtxmg/hiz/hiz_buffer_reduce_params.h\"\n#include \"rtxmg/hiz/hiz_buffer_constants.h\"\n\nTexture2D<float> t_zbuffer: register(t0);\nStructuredBuffer<HiZReducePass1Params> g_params: register(t1);\nRWTexture2D<float> u_output[HIZ_MAX_LODS]: register(u0);\n\ngroupshared float s_reductionData[HIZ_GROUP_SIZE][HIZ_GROUP_SIZE];\nSamplerState s : register(s0);\n\ninline float Reduce(float a, float b)\n{\n    return max(a, b);\n}\n\ninline float Reduce(float4 a)\n{\n    return Reduce(Reduce(a.x, a.y), Reduce(a.z, a.w));\n}\n\n[numthreads(HIZ_GROUP_SIZE, HIZ_GROUP_SIZE, 1)]\nvoid main(uint2 threadIdx : SV_GroupThreadID, uint2 dispatchThreadId : SV_DispatchThreadID, uint2 groupIdx : SV_GroupID)\n{\n    uint32_t x = dispatchThreadId.x;\n    uint32_t y = dispatchThreadId.y;\n\n    uint32_t hizwidth, hizheight;\n    u_output[0].GetDimensions(hizwidth, hizheight);\n\n    // current LOD may be smaller than a single tile\n    uint2 size = uint2(hizwidth >> 4, hizheight >> 4);\n    if (x >= size.x || y >= size.y)\n        return;\n\n\n    float value = u_output[4][dispatchThreadId];\n\n#pragma unroll\n    for (uint16_t level = 5; level < HIZ_MAX_LODS; level++)\n    {\n        uint16_t outGroupSize = ((uint16_t)HIZ_GROUP_SIZE) >> (level - 4);\n        uint16_t inGroupSize = outGroupSize << 1;\n\n        if (threadIdx.x < inGroupSize && threadIdx.y < inGroupSize)\n        {\n            s_reductionData[threadIdx.y][threadIdx.x] = value;\n        }\n\n        x = groupIdx.x * outGroupSize + threadIdx.x;\n        y = groupIdx.y * outGroupSize + threadIdx.y;\n\n        // the base level is guaranteed to be a multiple of 32, so we won't have an \n        // odd sized parent level until we go from L5 --> L6.\n\n        bool extraRow = (size.x & 1) != 0;\n        bool extraCol = (size.y & 1) != 0;\n\n        size = uint2(size.x >> 1, size.y >> 1);\n        if (x >= size.x || y >= size.y)\n            return;\n\n        GroupMemoryBarrierWithGroupSync();\n\n        if (threadIdx.x < outGroupSize && threadIdx.y < outGroupSize)\n        {\n            float a = s_reductionData[threadIdx.y * 2 + 0][threadIdx.x * 2 + 0];\n            float b = s_reductionData[threadIdx.y * 2 + 0][threadIdx.x * 2 + 1];\n            float c = s_reductionData[threadIdx.y * 2 + 1][threadIdx.x * 2 + 0];\n            float d = s_reductionData[threadIdx.y * 2 + 1][threadIdx.x * 2 + 1];\n\n            value = Reduce(float4(a, b, c, d));\n#if 1\n            if (extraCol)\n            {\n                // Get the two values to the right\n                a = s_reductionData[threadIdx.y * 2 + 0][threadIdx.x * 2 + 2];\n                b = s_reductionData[threadIdx.y * 2 + 1][threadIdx.x * 2 + 2];\n\n                if (extraRow)\n                {\n                    // Get the corner value\n                    c = s_reductionData[threadIdx.y * 2 + 2][threadIdx.x * 2 + 2];\n                }\n\n                // okay to re-use d here because reduce is a maximum\n                value = Reduce(float4(a, b, c, d));\n            }\n            if (extraRow)\n            {\n                // Get the two values below\n                a = s_reductionData[threadIdx.y * 2 + 2][threadIdx.x * 2 + 0];\n                b = s_reductionData[threadIdx.y * 2 + 2][threadIdx.x * 2 + 1];\n\n                // okay to re-use c and d here because reduce is a maximum\n                value = Reduce(float4(a, b, c, d));\n            }\n#endif\n            u_output[level][uint2(x, y)] = value;\n        }\n\n        GroupMemoryBarrierWithGroupSync();\n    }\n}"
  },
  {
    "path": "rtxmg/hiz/shaders/zbuffer_display.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\nTexture2D<float> zbuffer: register(t0);\nStructuredBuffer<float> minmax: register(t1);\n\nRWTexture2D<float4> output: register(u0);\n\n[numthreads(16, 16, 1)]\nvoid main(uint2 threadIdx : SV_GroupThreadID, uint2 dispatchThreadId : SV_DispatchThreadID)\n{\n    uint32_t width, height;\n    zbuffer.GetDimensions(width, height);\n\n    uint32_t x = dispatchThreadId.x;\n    uint32_t y = dispatchThreadId.y;\n\n    if ((x >= width) || (y >= height))\n    {\n        return;\n    }\n\n    float depth = zbuffer[dispatchThreadId];\n    if (!isinf(depth))\n    {\n        float minz = minmax[0];\n        float maxz = minmax[1];\n        float deltaz = maxz - minz;\n        depth = deltaz > 1e-6 ? (depth - minz) / deltaz : .5f;\n        output[dispatchThreadId] = float4(depth, depth, depth, 1);\n    }\n}"
  },
  {
    "path": "rtxmg/hiz/shaders/zbuffer_minmax.hlsl",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\nTexture2D<float> zbuffer: register(t0);\nRWStructuredBuffer<uint> minmax: register(u0); // floats-as-uints because depth values are non-negative.\n\n[numthreads(16, 16, 1)]\nvoid main(uint2 threadIdx : SV_GroupThreadID, uint2 dispatchThreadId : SV_DispatchThreadID)\n{\n    uint32_t width, height;\n    zbuffer.GetDimensions(width, height);\n\n    uint32_t x = dispatchThreadId.x;\n    uint32_t y = dispatchThreadId.y;\n\n    if ((x >= width) || (y >= height))\n    {\n        return;\n    }\n\n    float depth = zbuffer[dispatchThreadId];\n    if (depth < 0) depth = 0;\n\n    uint wmin = asuint(depth), wmax = asuint(depth);\n    if (isinf(depth))\n    {\n        wmin = 0xffffffff;\n        wmax = 0;\n    }\n\n    for (int i = 16; i >= 1; i /= 2)\n    {\n        uint targetLane = WaveGetLaneIndex() ^ i;\n\n        wmin = min(wmin, WaveReadLaneAt(wmin, targetLane));\n        wmax = max(wmax, WaveReadLaneAt(wmax, targetLane));\n    }\n    if (threadIdx.x == 0 && threadIdx.y == 0)\n    {\n        uint orig;\n        InterlockedMin(minmax[0], asuint(wmin), orig);\n        InterlockedMax(minmax[1], asuint(wmax), orig);\n    }\n}"
  },
  {
    "path": "rtxmg/hiz/shaders.cfg",
    "content": "shaders/hiz_display.hlsl -T cs\nshaders/hiz_pass1.hlsl -T cs\nshaders/hiz_pass2.hlsl -T cs\nshaders/zbuffer_display.hlsl -T cs\nshaders/zbuffer_minmax.hlsl -T cs\n"
  },
  {
    "path": "rtxmg/hiz/zbuffer.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include \"rtxmg/hiz/zbuffer.h\"\n#include \"rtxmg/hiz/hiz_buffer.h\"\n\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/utils/debug.h\"\n\n#include <donut/core/log.h>\n\n#include <fstream>\n\nusing namespace donut;\n\nstd::unique_ptr<ZBuffer> ZBuffer::Create(uint2 size,\n    std::shared_ptr<engine::CommonRenderPasses> commonPasses,\n    std::shared_ptr<engine::ShaderFactory> shaderFactory,\n    nvrhi::ICommandList* commandList)\n{\n    auto device = commandList->getDevice();\n    auto zbuffer = std::make_unique<ZBuffer>();\n\n    nvrhi::TextureDesc desc;\n    desc.width = size.x;\n    desc.height = size.y;\n    desc.isUAV = true;\n    desc.keepInitialState = true;\n    desc.format = nvrhi::Format::R32_FLOAT;\n    desc.initialState = nvrhi::ResourceStates::UnorderedAccess;\n    desc.debugName = \"ZBuffer\";\n    zbuffer->m_currentTexture = device->createTexture(desc);\n    zbuffer->m_minmaxBuffer.Create(2, \"ZBufferMinMax\", device);\n    zbuffer->m_commonPasses = commonPasses;\n\n    nvrhi::ShaderDesc minmaxDesc(nvrhi::ShaderType::Compute);\n\n    zbuffer->m_minmaxShader = shaderFactory->CreateShader(\"hiz/zbuffer_minmax.hlsl\", \"main\", nullptr, minmaxDesc);\n    if (!zbuffer->m_minmaxShader)\n    {\n        log::fatal(\"Failed to create ZBufferMinMax shader\");\n    }\n\n    zbuffer->m_displayShader = shaderFactory->CreateShader(\"hiz/zbuffer_display.hlsl\", \"main\", nullptr, nvrhi::ShaderType::Compute);\n    if (!zbuffer->m_displayShader)\n    {\n        log::fatal(\"Failed to create ZBufferDisplay shader\");\n    }\n\n    // make level 0 m_size a multiple of 32 to avoid nasty edge cases\n    uint2  hizSize = { (((uint32_t(size.x) + 255) & ~255u) / 8),\n                      (((uint32_t(size.y) + 255) & ~255u) / 8) };\n    zbuffer->m_hierarchy = HiZBuffer::Create(hizSize, commonPasses, shaderFactory, commandList);\n\n    return zbuffer;\n}\n\nvoid ZBuffer::Display(nvrhi::ITexture* output, nvrhi::ICommandList* commandList)\n{\n    if (!m_currentTexture) return;\n\n    nvrhi::utils::ScopedMarker marker(commandList, \"ZBuffer::display\");\n    uint2 size = { m_currentTexture->getDesc().width, m_currentTexture->getDesc().height };\n\n    uint32_t minmax[2] = { std::numeric_limits<uint32_t>::max(), 0 };\n    commandList->writeBuffer(m_minmaxBuffer, minmax, sizeof(minmax));\n\n    auto device = commandList->getDevice();\n\n    auto bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_UAV(0, m_minmaxBuffer))\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(0, m_currentTexture));\n\n    nvrhi::BindingSetHandle bindingSet;\n    if (!nvrhi::utils::CreateBindingSetAndLayout(device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_minmaxBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for zbuffer minmax\");\n    }\n    \n    if (!m_minmaxPSO)\n    {\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(m_minmaxShader)\n            .addBindingLayout(m_minmaxBL);\n\n        m_minmaxPSO = device->createComputePipeline(computePipelineDesc);\n    }\n\n    auto state = nvrhi::ComputeState()\n        .setPipeline(m_minmaxPSO)\n        .addBindingSet(bindingSet);\n\n    commandList->setComputeState(state);\n    commandList->dispatch(size.x / 16 + 1, size.y / 16 + 1, 1);\n\n    bindingSetDesc = nvrhi::BindingSetDesc()\n        .addItem(nvrhi::BindingSetItem::Texture_SRV(0, m_currentTexture))\n        .addItem(nvrhi::BindingSetItem::StructuredBuffer_SRV(1, m_minmaxBuffer))\n        .addItem(nvrhi::BindingSetItem::Texture_UAV(0, output));\n\n    bindingSet.Reset();\n    if (!nvrhi::utils::CreateBindingSetAndLayout(device, nvrhi::ShaderType::Compute, 0, bindingSetDesc, m_displayBL, bindingSet))\n    {\n        log::fatal(\"Failed to create binding set and layout for zbuffer minmax\");\n    }\n\n    if (!m_displayPSO)\n    {\n        auto computePipelineDesc = nvrhi::ComputePipelineDesc()\n            .setComputeShader(m_displayShader)\n            .addBindingLayout(m_displayBL);\n\n        m_displayPSO = device->createComputePipeline(computePipelineDesc);\n    }\n\n    state = nvrhi::ComputeState()\n        .setPipeline(m_displayPSO)\n        .addBindingSet(bindingSet);\n\n    commandList->setComputeState(state);\n    commandList->dispatch(size.x / 16 + 1, size.y / 16 + 1, 1);\n\n    if (m_hierarchy)\n    {\n        m_hierarchy->Display(output, commandList);\n    }\n}\n\nvoid ZBuffer::ReduceHierarchy(nvrhi::ICommandList* commandList)\n{\n    if (m_hierarchy && m_currentTexture)\n    {\n        nvrhi::utils::ScopedMarker marker(commandList, \"ZBuffer::reduceHierarchy\");\n        m_hierarchy->Reduce(m_currentTexture, commandList);\n    }\n}\n\nvoid ZBuffer::Clear(nvrhi::ICommandList* commandList)\n{\n    if (!m_currentTexture)\n        return;\n    nvrhi::utils::ScopedMarker marker(commandList, \"ZBuffer::clear\");\n    commandList->clearTextureFloat(m_currentTexture, nvrhi::AllSubresources, nvrhi::Color(std::numeric_limits<float>::max()));\n    \n    if (m_hierarchy)\n    {\n        m_hierarchy->Clear(commandList);\n    }\n}"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/cluster.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#ifdef __cplusplus\n\n#include <donut/core/math/math.h>\n\n#include <assert.h>\n#include <cstdint>\n#include <stdio.h>\n\n#include <cmath>\n\nusing namespace donut::math;\n\ntypedef vector<unsigned short, 2> uint16_t2;\ntypedef vector<unsigned short, 4> uint16_t4;\n\nusing std::round;\nusing std::max;\n#else\n#define assert(x)\n#endif\n\nenum class ClusterShape\n{\n    RECTANGULAR,\n    SQUARE,\n};\n\nenum class ClusterPattern : uint32_t\n{\n    REGULAR,\n    SLANTED\n};\n\n// Class for computing uv sample locations on a rectangular grid.\n//\n// The (edgeSegments + 1) defines the number of distinct\n// vertex locations on the rectangular surface patche's edge.\n// For this multiple vertices may by snapped into indentical\n// locations on the edge.\n//\nstruct GridSampler\n{\n    uint16_t4 edgeSegments;\n\n#ifndef __cplusplus\n    // only need the code for HLSL\n    uint16_t GridSizeX() { return max(edgeSegments.x, edgeSegments.z); }\n    uint16_t GridSizeY() { return max(edgeSegments.y, edgeSegments.w); }\n    uint16_t2 GridSize() { return uint16_t2(GridSizeX(), GridSizeY()); }\n\n    float UV0(uint16_t u_index)\n    {\n        return round(edgeSegments.x / (float)(GridSizeX()) * u_index) / edgeSegments.x;\n    }\n\n    float UV1(uint16_t u_index)\n    {\n        return round(edgeSegments.z / (float)(GridSizeX()) * u_index) / edgeSegments.z;\n    }\n\n    float U1V(uint16_t v_index)\n    {\n        return round(edgeSegments.y / (float)(GridSizeY()) * v_index) / edgeSegments.y;\n    }\n\n    float U0V(uint16_t v_index)\n    {\n        return  round(edgeSegments.w / (float)(GridSizeY()) * v_index) / edgeSegments.w;\n    }\n\n    float2 RegularInteriorUV(uint16_t i, uint16_t j)\n    {\n        assert(0 < i && i < GridSizeX());\n        assert(0 < j && j < GridSizeY());\n        float clusterU = i / (float)(GridSizeX());\n        float clusterV = j / (float)(GridSizeY());\n        return float2(clusterU, clusterV);\n    }\n\n    float2 SlantedInteriorUV(uint16_t i, uint16_t j)\n    {\n        assert(0 < i && i < GridSizeX());\n        assert(0 < j && j < GridSizeY());\n        float Du = UV1(i) - UV0(i);\n        float Dv = U1V(j) - U0V(j);\n        float clusterU = (UV0(i) + U0V(j) * Du) / (1.0f - Du * Dv);\n        float clusterV = (U0V(j) + UV0(i) * Dv) / (1.0f - Du * Dv);\n        return float2(clusterU, clusterV);\n    }\n\n    float2 BoundaryUV(uint16_t i, uint16_t j)\n    {\n        if (j == 0)\n            return float2(UV0(i), 0.0f);\n        if (j == GridSizeY())\n            return float2(UV1(i), 1.0f);\n\n        if (i == 0)\n            return float2(0.0f, U0V(j));\n        if (i == GridSizeX())\n            return float2(1.0f, U1V(j));\n        assert(false);\n\n        return float2(0.0f, 0.0f);\n    }\n\n    float2 UV(uint16_t2 uvIndex, ClusterPattern pattern)\n    {\n        uint16_t i = uvIndex.x, j = uvIndex.y;\n        // interior uv locations\n        if (0 < i && i < GridSizeX() && 0 < j && j < GridSizeY())\n        {\n            if (pattern == ClusterPattern::SLANTED)\n               return SlantedInteriorUV(i, j);\n            else\n               return RegularInteriorUV(i, j);\n        }\n        else\n            return BoundaryUV(i, j);\n\n        return float2(0.0f, 0.0f);\n    }\n\n    // The functions below estimate the parametric edge lengths du and dv around any point on\n    // a surface, i.e., if you wanted to tessellate at this point, what should be the spacing.\n    // They are needed for displacement texture filtering on the surface.\n    //\n    // TODO: define something that is C0 continuous across clusters.  The lerp is not quite C0.\n    float DU(float2 uvVals)\n    {\n        float v = uvVals.y;\n        return 1 / ((1.0f - v) * float(edgeSegments.x) + v * float(edgeSegments.z));\n    }\n\n    float DV(float2 uvVals)\n    {\n        float u = uvVals.x;\n        return 1 / ((1.0f - u) * float(edgeSegments.w) + u * float(edgeSegments.y));\n    }\n\n    bool IsEmpty()\n    {\n        return GridSizeX() == 0 && GridSizeY() == 0;\n    }\n#endif\n};\n\n\n// Cluster class.\n//\n// Tessellator's representation of Clusters.\n// This representation contains all data necessary for tessellating analytic\n// surface patches (tmr surfaces) into RTX Cluster\n// primitives.\n//\n// This base class stores the target edge resolutions (number of edge segments).\n// It provides a number of helper methods for computing resolution of an NxM\n// patch (max of the parallel edges) and (u, v)-locations of the cluster samples.\n//\n\nstruct Cluster\n{\n    uint32_t iSurface;  // index of the surface (patch) generating this cluster\n    uint32_t nVertexOffset;  // vertex array index of this cluster's [0, 0]-corner\n    uint16_t2 offset;  // cluster's offset inside sample grid\n    uint sizeX : 8;  // cluster's m_size\n    uint sizeY : 8;  // cluster's m_size\n\n#ifndef __cplusplus\n    // HLSL code only\n\n    inline uint32_t VerticesPerCluster()\n    {\n        return (sizeX + 1) * (sizeY + 1);\n    }\n\n    inline uint32_t QuadsPerCluster()\n    {\n        return sizeX * sizeY;\n    }\n\n    inline uint32_t TrianglesPerCluster() { return 2 * QuadsPerCluster(); }\n\n    uint16_t2 Linear2Idx2D(uint16_t indexLinear)\n    {\n        const uint16_t vertices_u = (uint16_t)(sizeX + 1);\n        return uint16_t2((uint16_t)(indexLinear % vertices_u),\n            (uint16_t)(indexLinear / vertices_u));\n    }\n\n    inline bool Equals(Cluster other)\n    {\n        return (iSurface == other.iSurface) && (nVertexOffset == other.nVertexOffset)\n            && (offset.x == other.offset.x) && (offset.y == other.offset.y)\n            && (sizeX == other.sizeX) && (sizeY == other.sizeY);\n    }\n#endif\n};\n#ifndef __cplusplus\nCluster MakeCluster(uint32_t iSurface = 0u,\n    uint32_t vertexOffset = 0u,\n    uint16_t2  offset = uint16_t2(0u, 0u),\n    uint sizeX = 0u,\n    uint sizeY = 0u)\n{\n    Cluster c;\n    c.iSurface = iSurface;\n    c.nVertexOffset = vertexOffset;\n    c.offset = offset;\n    c.sizeX = sizeX;\n    c.sizeY = sizeY;\n    return c;\n}\n#endif\n\nstruct ClusterShadingData\n{\n    uint16_t4 m_edgeSegments;\n    float2   m_texcoords[4];\n    uint32_t m_surfaceId;\n    uint32_t m_vertexOffset;\n\n    uint16_t2  m_clusterOffset;\n    uint   m_clusterSizeX : 8;\n    uint   m_clusterSizeY : 8;\n    uint   m_pad0 : 16;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/cluster_accel_builder.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n// clang-format off\n#include \"rtxmg/cluster_builder/cluster.h\"\n#include \"rtxmg/cluster_builder/cluster_accels.h\"\n#include \"rtxmg/cluster_builder/tessellator_config.h\"\n#include \"rtxmg/cluster_builder/tessellation_counters.h\"\n#include \"rtxmg/cluster_builder/copy_cluster_offset_params.h\"\n#include \"rtxmg/cluster_builder/fill_blas_from_clas_args_params.h\"\n\n#include \"rtxmg/scene/model.h\"\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/utils/shader_debug.h\"\n\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/engine/ShaderFactory.h>\n#include <nvrhi/utils.h>\n#include <nvrhi/nvrhi.h>\n\n#include <memory>\n#include <vector>\n#include <span>\n// clang-format on\n\nclass RTXMGScene;\nclass SubdivisionSurface;\nstruct TopologyMap;\n\nstruct TemplateGridDesc\n{\n    uint32_t xEdges = 0;\n    uint32_t yEdges = 0;\n    uint32_t indexOffset = 0;\n    uint32_t vertexOffset = 0;\n\n    uint32_t getXVerts() const { return xEdges + 1; }\n    uint32_t getYVerts() const { return yEdges + 1; }\n    uint32_t getNumTriangles() const { return xEdges * yEdges * 2; }\n    uint32_t getNumVerts() const { return getXVerts() * getYVerts(); }\n};\n\nstruct TemplateGrids\n{\n    typedef uint8_t IndexType;\n\n    std::vector<TemplateGridDesc> descs;\n    std::vector<IndexType> indices;\n    std::vector<float> vertices;\n\n    uint32_t maxVertices = 0;\n    uint32_t maxTriangles = 0;\n    uint32_t totalVertices = 0;\n    uint32_t totalTriangles = 0;\n};\n\nenum class ShaderPermutationSurfaceType : uint32_t\n{\n    PureBSpline,\n    RegularBSpline,\n    Limit,\n    All,\n    Count\n};\n\n// Permutation definitions\nclass ComputeClusterTilingPermutation\n{\npublic:\n    static constexpr uint32_t kTessModeBitCount = 2;\n    static constexpr uint32_t kVisibilityBitCount = 1;\n    static constexpr uint32_t kSurfaceTypeBitCount = 2;\n    static_assert(uint32_t(ShaderPermutationSurfaceType::Count) <= (1u << kSurfaceTypeBitCount));\n\n    static_assert(uint32_t(TessellatorConfig::AdaptiveTessellationMode::COUNT) <= (1u << kTessModeBitCount));\n    static_assert(uint32_t(TessellatorConfig::VisibilityMode::COUNT) <= (1u << kVisibilityBitCount));\n\n    enum BitIndices : uint32_t\n    {\n        DisplacementMaps,\n        FrustumVisibility,\n        TessMode,\n        VisibilityMode = TessMode + kTessModeBitCount,\n        SurfaceTypeStartBit = VisibilityMode + kVisibilityBitCount,\n        Count = SurfaceTypeStartBit + kSurfaceTypeBitCount\n    };\n\n    static constexpr size_t kCount = 1u << BitIndices::Count;\n\n    ComputeClusterTilingPermutation(bool enableDisplacement,\n        bool enableFrustumVisibility,\n        TessellatorConfig::AdaptiveTessellationMode tessMode,\n        TessellatorConfig::VisibilityMode visMode,\n        ShaderPermutationSurfaceType surfaceType)\n        : m_bits\n        ((enableDisplacement ? (1u << BitIndices::DisplacementMaps) : 0u) \n         | (enableFrustumVisibility ? (1u << BitIndices::FrustumVisibility) : 0u)\n         | (uint32_t(tessMode) << BitIndices::TessMode)\n         | (uint32_t(visMode) << BitIndices::VisibilityMode)\n         | (uint32_t(surfaceType) << BitIndices::SurfaceTypeStartBit))\n    {}\n\n    bool isDisplacementEnabled() const { return m_bits & (1u << BitIndices::DisplacementMaps); }\n    bool isFrustumVisibilityEnabled() const { return m_bits & (1u << BitIndices::FrustumVisibility); }\n\n    TessellatorConfig::AdaptiveTessellationMode tessellationMode() const\n    {\n        constexpr uint32_t kBitMask = (1 << kTessModeBitCount) - 1;\n        return TessellatorConfig::AdaptiveTessellationMode((m_bits >> BitIndices::TessMode) & kBitMask);\n    }\n\n    TessellatorConfig::VisibilityMode visibilityMode() const\n    {\n        constexpr uint32_t kBitMask = (1 << kVisibilityBitCount) - 1;\n        return TessellatorConfig::VisibilityMode((m_bits >> BitIndices::VisibilityMode) & kBitMask);\n    }\n\n    ShaderPermutationSurfaceType surfaceType() const\n    {\n        constexpr uint32_t kBitMask = (1 << kSurfaceTypeBitCount) - 1;\n        return ShaderPermutationSurfaceType((m_bits >> BitIndices::SurfaceTypeStartBit) & kBitMask);\n    }\n    void setSurfaceType(ShaderPermutationSurfaceType surfaceType)\n    {\n        constexpr uint32_t kBitMask = (1 << kSurfaceTypeBitCount) - 1;\n        m_bits &= ~(kBitMask << BitIndices::SurfaceTypeStartBit);\n        m_bits |= (uint32_t(surfaceType) << BitIndices::SurfaceTypeStartBit);\n    }\n\n    uint32_t index() const { return m_bits; }\n\nprivate:\n    uint32_t m_bits = 0;\n};\n\nclass FillClustersPermutation\n{\npublic:\n    static constexpr uint32_t kSurfaceTypeBitCount = 2;\n    static_assert(uint32_t(ShaderPermutationSurfaceType::Count) <= (1u << kSurfaceTypeBitCount));\n\n    enum BitIndices : uint32_t\n    {\n        DisplacementMaps = 0,\n        VertexNormals,\n        SurfaceTypeStartBit,\n        Count = SurfaceTypeStartBit + kSurfaceTypeBitCount\n    };\n    static constexpr size_t kCount = 1u << BitIndices::Count;\n    uint32_t index() const { return m_bits; }\n\n    FillClustersPermutation(bool enableDisplacement,\n        bool enableVertexNormals,\n        ShaderPermutationSurfaceType surfaceType)\n        : m_bits((enableDisplacement ? (1u << BitIndices::DisplacementMaps) : 0u)\n         | (enableVertexNormals ? (1u << BitIndices::VertexNormals) : 0u)\n         | (uint32_t(surfaceType) << BitIndices::SurfaceTypeStartBit))\n    {}\n\n    bool isDisplacementEnabled() const { return m_bits & (1u << BitIndices::DisplacementMaps); }\n    bool isVertexNormalsEnabled() const { return m_bits & (1u << BitIndices::VertexNormals); }\n    ShaderPermutationSurfaceType surfaceType() const\n    {\n        constexpr uint32_t kBitMask = (1 << kSurfaceTypeBitCount) - 1;\n        return ShaderPermutationSurfaceType((m_bits >> BitIndices::SurfaceTypeStartBit) & kBitMask);\n    }\n\nprivate:\n    uint32_t m_bits = 0;\n};\n\n\nclass ClusterAccelBuilder\n{\npublic:\n    ClusterAccelBuilder(donut::engine::ShaderFactory& shaderFactory, \n        std::shared_ptr<donut::engine::CommonRenderPasses> commonPasses,\n        nvrhi::DescriptorTableHandle descriptorTable,\n        nvrhi::DeviceHandle device);\n\n    void BuildAccel(const RTXMGScene& scene, const TessellatorConfig& config, \n        ClusterAccels& accels, ClusterStatistics& stats, uint32_t frameIndex, nvrhi::ICommandList* commandList);\n    \n    RTXMGBuffer<ShaderDebugElement>& GetDebugBuffer() { return m_debugBuffer; }\n\nprotected:\n    void UpdateMemoryAllocations(ClusterAccels& accels, uint32_t numInstances, uint32_t sceneSubdPatches);\n\n    nvrhi::BufferHandle GenerateStructuredClusterTemplateArgs(const TemplateGrids& grids, nvrhi::ICommandList* commandList);\n    void InitStructuredClusterTemplates(uint32_t maxGeometryCountPerMesh, nvrhi::ICommandList* commandList);\n    void BuildStructuredCLASes(ClusterAccels& accels, uint32_t maxGeometryCountPerMesh, const nvrhi::BufferRange& tessCounterRange, nvrhi::ICommandList* commandList);\n    void BuildBlasFromClas(ClusterAccels& accels, std::span<Instance const> instances, nvrhi::ICommandList* commandList);\n\n    void FillInstantiateTemplateArgs(nvrhi::IBuffer* outArgs, nvrhi::IBuffer* templateAddresses, uint32_t numTemplates, nvrhi::ICommandList* commandList);\n    void FillInstanceClusters(const RTXMGScene& scene, ClusterAccels& accels, nvrhi::ICommandList* commandList);\n    void FillBlasFromClasArgs(nvrhi::IBuffer* outArgs, nvrhi::IBuffer* clusterOffsets, \n        nvrhi::GpuVirtualAddress clasPtrsBaseAddress, uint32_t numInstances, nvrhi::ICommandList* commandList);\n\n    // Calculates the cluster layout based off of various visibility metrics\n    // A cluster tiling is the number of clusters and cluster sizes that are used to cover a surface.\n    // Outputs cluster headers, shading data, and addresses\n    void ComputeInstanceClusterTiling(ClusterAccels& accels,\n        const RTXMGScene& scene,\n        uint32_t instanceIndex,\n        uint32_t surfaceOffset,\n        uint32_t surfaceCount,\n        const nvrhi::BufferRange& tessCounterRange,\n        nvrhi::ICommandList* commandList);\n    void CopyClusterOffset(uint32_t instanceIndex, ClusterDispatchType dispatchType,\n        const nvrhi::BufferRange& tessCounterRange, nvrhi::ICommandList* commandList);\n\nprotected:\n    TessellatorConfig m_tessellatorConfig;\n    donut::engine::ShaderFactory& m_shaderFactory;\n\n    nvrhi::DeviceHandle m_device;\n    nvrhi::DescriptorTableHandle m_descriptorTable;\n    std::shared_ptr<donut::engine::CommonRenderPasses> m_commonPasses;\n    \n    RTXMGBuffer<TessellationCounters> m_tessellationCountersBuffer;\n    uint32_t m_buildAccelFrameIndex = 0; // substition for frameIndex since we don't necessarily build every frame\n\n    // Pipeline descs\n    nvrhi::BindingLayoutHandle m_bindlessBL;\n\n    nvrhi::BindingLayoutHandle m_fillInstantiateTemplateBL;\n    nvrhi::ComputePipelineHandle m_fillInstantiateTemplatePSO;\n\n    nvrhi::BindingLayoutHandle m_fillBlasFromClasArgsBL;\n    nvrhi::ComputePipelineHandle m_fillBlasFromClasArgsPSO;\n\n    nvrhi::BindingLayoutHandle m_copyClusterOffsetBL;\n    nvrhi::ComputePipelineHandle m_copyClusterOffsetPSO;\n\n    nvrhi::BindingLayoutHandle m_fillClustersBL;\n    nvrhi::ComputePipelineHandle m_fillClustersPSOs[FillClustersPermutation::kCount];\n    nvrhi::ComputePipelineHandle m_fillClustersTexcoordsPSO;\n\n    nvrhi::BindingLayoutHandle m_computeClusterTilingBL;\n    nvrhi::BindingLayoutHandle m_computeClusterTilingHizBL;\n    nvrhi::ComputePipelineHandle m_computeClusterTilingPSOs[ComputeClusterTilingPermutation::kCount];\n    \n    RTXMGBuffer<uint3> m_fillClustersDispatchIndirectBuffer; // number of thread groups per each instance\n    RTXMGBuffer<uint2> m_clusterOffsetCountsBuffer; // offset+count per each instance\n    \n    nvrhi::rt::cluster::OperationParams m_createBlasParams;\n    nvrhi::rt::cluster::OperationSizeInfo m_createBlasSizeInfo;\n\n    // Per input surface patch \n    RTXMGBuffer<GridSampler> m_gridSamplersBuffer;\n    \n    RTXMGBuffer<Cluster> m_clustersBuffer;\n    RTXMGBuffer<nvrhi::rt::cluster::IndirectArgs> m_blasFromClasIndirectArgsBuffer;\n    RTXMGBuffer<nvrhi::rt::cluster::IndirectInstantiateTemplateArgs> m_clasIndirectArgDataBuffer;\n\n    uint32_t m_numInstances = 0;\n    uint32_t m_sceneSubdPatches = 0;\n    uint32_t m_maxClusters = 0;\n    uint32_t m_maxVertices = 0;\n    uint64_t m_maxClasBytes = 0;\n\n    struct TemplateBuffers\n    {\n        uint32_t                                maxGeometryCountPerMesh = 0;\n        uint32_t                                quantNBits = 0;\n        nvrhi::BufferHandle                     dataBuffer; // Holds the template data\n        RTXMGBuffer<nvrhi::GpuVirtualAddress>   addressesBuffer; // Array of addresses within dataBuffer, one per template\n        RTXMGBuffer<uint32_t>                   instantiationSizesBuffer; // Size to instanstiate each template\n        std::vector<uint32_t>                   instantiationSizes;\n    };\n    TemplateBuffers m_templateBuffers; // Buffers used to Create templates. They are created once but need to be persistent throughout the app's run time.\n    \n    nvrhi::BufferHandle m_fillInstantiateTemplateArgsParamsBuffer; // constant buffer for filling indirect args for getting template sizes\n    nvrhi::BufferHandle m_computeClusterTilingParamsBuffer; // constant buffer for compute cluster tiling\n    nvrhi::BufferHandle m_copyClusterOffsetParamsBuffer; // constant buffer for copying cluster offsets\n    nvrhi::BufferHandle m_fillClustersParamsBuffer; // constant buffer for fill clusters\n    nvrhi::BufferHandle m_fillBlasFromClasArgsParamsBuffer; // constant buffer for filling indirect args to initialize blas from clas\n\n    RTXMGBuffer<ShaderDebugElement> m_debugBuffer;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/cluster_accels.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n// clang-format off\n#include <nvrhi/nvrhi.h>\n#include <donut/core/math/math.h>\n\nusing namespace donut::math;\n\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/cluster_builder/tessellator_config.h\"\n// clang-format on\n\n#include <memory>\n#include <span>\n\nstruct ClusterAccels\n{\n    RTXMGBuffer<uint8_t> blasBuffer;\n    RTXMGBuffer<uint8_t> clasBuffer;\n\n    RTXMGBuffer<nvrhi::GpuVirtualAddress> clasPtrsBuffer;  // address of each CLAS header in clasBuffer\n    RTXMGBuffer<nvrhi::GpuVirtualAddress> blasPtrsBuffer;  // handles in device memory\n    RTXMGBuffer<uint32_t> blasSizesBuffer;\n\n    // -------------------------------------------------------------------------\n    // Cluster data buffer for shading information\n    //\n    RTXMGBuffer<ClusterShadingData> clusterShadingDataBuffer;\n\n    // -------------------------------------------------------------------------\n    // Vertex Position buffer that we stage into before creating CLASes\n    //\n    RTXMGBuffer<float3> clusterVertexPositionsBuffer;\n\n    // -------------------------------------------------------------------------\n    // Vertex Normal buffer (optional - only allocated when vertex normals are enabled)\n    //\n    RTXMGBuffer<float3> clusterVertexNormalsBuffer;\n};\n\nstruct ClusterStatistics\n{    \n    struct BufferStatistics\n    {\n        uint32_t m_numClusters = 0;\n        uint32_t m_numTriangles = 0;\n        size_t m_blasScratchSize = 0;\n        size_t m_blasSize = 0;\n        size_t m_vertexBufferSize = 0;\n        size_t m_vertexNormalsBufferSize = 0;\n        size_t m_clasSize = 0;\n        size_t m_clusterDataSize = 0;\n    };\n\n    BufferStatistics desired;\n    BufferStatistics allocated;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/compute_cluster_tiling_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include \"rtxmg/utils/box3.h\"\n#include \"nvrhi/nvrhiHLSL.h\"\n\nstatic const uint32_t kComputeClusterTilingWaves = 4;\n\nstruct ComputeClusterTilingParams\n{\n    uint32_t surfaceStart; //inclusive\n    uint32_t surfaceEnd; //exclusive\n    uint32_t debugSurfaceIndex;\n    uint32_t debugLaneIndex;\n    \n    float4x4 matWorldToClip;\n    float3x4 localToWorld;\n\n    float3 cameraPos;\n    float pad1;\n\n    Box3 aabb;\n\n    uint4 edgeSegments;\n    \n    uint firstGeometryIndex;\n    uint isolationLevel;\n    float fineTessellationRate;\n    float coarseTessellationRate;\n\n    float2 viewportSize;\n    float2 invHiZSize;\n\n    int enableFrustumVisibility;\n    int enableBackfaceVisibility;\n    int enableHiZVisibility;\n    int numHiZLODs;\n    \n    float globalDisplacementScale;\n    uint maxClusters;\n    uint maxVertices;\n    uint maxClasBlocks;\n\n    nvrhi::GpuVirtualAddress clasDataBaseAddress;\n    nvrhi::GpuVirtualAddress clusterVertexPositionsBaseAddress;\n};\n\n#if defined(__cplusplus)\nstatic_assert(sizeof(ComputeClusterTilingParams) % 16 == 0);\n#elif defined(TARGET_D3D12)\n_Static_assert(sizeof(ComputeClusterTilingParams) % 16 == 0, \"Must be 16 byte aligned\");\n#endif\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/copy_cluster_offset_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n\nenum ClusterDispatchType\n{\n    PureBSpline,\n    RegularBSpline,\n    Limit,\n    All, // used for texcoords, fill clas to blas\n    NumTypes\n};\n\nstruct CopyClusterOffsetParams\n{\n    uint32_t instanceIndex;\n    uint32_t dispatchTypeIndex;\n    uint2 pad;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/displacement.hlsli",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#ifndef DISPLACEMENT_H\n#define DISPLACEMENT_H\n\n#include \"rtxmg/subdivision/subdivision_eval.hlsli\"\n#include <donut/shaders/material_cb.h>\n\nvoid GetDisplacement(MaterialConstants material,\n                     float globalScale,\n                     out int displacementTexIndex, \n                     out float scale)\n{\n    scale = 0.0f; // Default value indicating no displacement\n    displacementTexIndex = -1;\n    if (material.normalTextureIndex != -1)\n    {\n        displacementTexIndex = material.normalTextureIndex;\n        scale = material.normalTextureScale * globalScale;\n    }\n}\n\n\nfloat DisplacementMipLevel(float2 dx, float2 dy) \n{\n    float d = max(dot(dx, dx), dot(dy, dy));\n    return max(0.5f * log2(d), 0.f);\n}\n\nLimitFrame DoDisplacement(TexcoordEvaluatorHLSL texcoordEval,\n    LimitFrame limit,\n    uint32_t iSurface,\n    float2 uv,\n    float du,\n    float dv,\n    Texture2D<float> displacementTex,\n    SamplerState dispSampler,\n    float scale)\n{\n    if (scale == 0)\n    {\n        return limit;\n    }\n        \n    // compute subd limit and normal\n    const float3 normal = normalize(cross(limit.deriv1, limit.deriv2));\n    TexCoordLimitFrame texcoord = texcoordEval.EvaluateLinearSubd(uv, iSurface);\n    \n    // Sample 1 texel \n    float2 gradDu = du * texcoord.deriv1;\n    float2 gradDv = dv * texcoord.deriv2;\n\n    float mipLevel = DisplacementMipLevel(gradDu, gradDv);\n    float displacement = scale * displacementTex.SampleLevel(dispSampler, texcoord.uv, mipLevel);\n    \n    // compute derivatives of displacement map, (dD/du) and (dD/dv) from finite differences:\n    const float2 delta = float2(max(du, 0.01f), max(dv, 0.01f));\n    float2 texcoordDu = texcoord.uv + delta.x * texcoord.deriv1;\n    float2 texcoordDv = texcoord.uv + delta.y * texcoord.deriv2;\n    \n    float displacement1 = scale * displacementTex.SampleLevel(dispSampler, texcoordDu, mipLevel);\n    float displacement2 = scale * displacementTex.SampleLevel(dispSampler, texcoordDv, mipLevel);\n    float  dDdu = ( displacement1 - displacement ) / delta.x;\n    float  dDdv = ( displacement2 - displacement ) / delta.y;\n    \n    // compute displaced partial derivates\n    const float3 dpdu = limit.deriv1 + dDdu * normal;\n    const float3 dpdv = limit.deriv2 + dDdv * normal;\n\n    LimitFrame ret;\n    \n    ret.p = limit.p + displacement * normal;\n    ret.deriv1 = dpdu;\n    ret.deriv2 = dpdv;\n    \n    return ret;\n}\n\n#endif // DISPLACEMENT_H"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/fill_blas_from_clas_args_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IFclust ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <nvrhi/nvrhiHLSL.h>\n\nstatic const uint32_t kFillBlasFromClasArgsThreads = 32;\n\n// Constant Buffer params\nstruct FillBlasFromClasArgsParams\n{\n    nvrhi::GpuVirtualAddress clasAddressesBaseAddress;\n    uint32_t numInstances;\n    uint32_t pad;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/fill_clusters_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef FILL_CLUSTERS_PARAMS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define FILL_CLUSTERS_PARAMS_H\n\n#include <nvrhi/nvrhiHLSL.h>\n#include \"rtxmg/subdivision/osd_ports/tmr/types.h\"\n\n// Number of clusters to calculate vertices for in a thread group\nstatic const uint32_t kFillClustersVerticesWaves = 4;\n\n// Number of lanes (threads) per wave for vertex cluster filling\nstatic const uint32_t kFillClustersVerticesLanes = 32;\n\n// We do cluster per x, y is the cluster UV evaluation points\nstatic const uint32_t kFillClustersTexcoordsThreadsX = 32;\n\nstruct FillClustersParams\n{\n    uint32_t instanceIndex;\n    uint32_t quantNBits;\n    uint32_t isolationLevel;\n    uint32_t firstGeometryIndex;\n\n    float globalDisplacementScale;\n    uint32_t clusterPattern;\n    uint32_t pad0;\n    uint32_t pad1;\n\n    uint32_t debugSurfaceIndex;\n    uint32_t debugClusterIndex;\n    uint32_t debugLaneIndex;\n    uint32_t pad2;\n};\n\n#endif // FILL_CLUSTERS_PARAMS_H"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/fill_instance_descs_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IFclust ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\nstatic const uint32_t kFillInstanceDescsThreads = 32;\n\n// Constant Buffer params\nstruct FillInstanceDescsParams\n{\n    uint32_t numInstances;\n    uint32_t pad;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/fill_instantiate_template_args_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IFclust ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\nstatic const uint32_t kFillInstantiateTemplateArgsThreads = 32;\n\n// Constant Buffer params\nstruct FillInstantiateTemplateArgsParams\n{\n    uint32_t numTemplates;\n    uint3 pad;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/tessellation_counters.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#ifdef __cplusplus\n#include <cstdint>\n#endif\n\n#include <nvrhi/nvrhiHLSL.h>\n\n// Scratch device memory needed while filling clusters\nstruct TessellationCounters\n{\n    uint32_t clusters;\n    uint32_t desiredClusters;\n    uint32_t desiredVertices;\n    uint32_t desiredTriangles;\n    uint32_t desiredClasBlocks;\n\n    // Pad for vulkan minStorageBufferOffsetAlignment = 16\n    uint32_t pad[3];\n\n#ifdef __cplusplus\n    size_t DesiredClasBytes() const { return size_t(desiredClasBlocks) * nvrhi::rt::cluster::kClasByteAlignment; }\n#endif\n};\n\n#ifdef __cplusplus\nconstexpr uint32_t kClusterCountByteOffset = offsetof(TessellationCounters, clusters);\n#endif\n\n"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/tessellator_config.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n\n#pragma once\n\n#include \"rtxmg/cluster_builder/cluster.h\"\n\nclass Camera;\nclass ZBuffer;\n\nstruct TessellatorConfig\n{\n    static constexpr float kDefaultFineTessellationRate = 1.0f;\n    static constexpr float kDefaultCoarseTessellationRate = 1.0f / 15.0f;\n\n    // 2M clusters\n    static constexpr uint32_t kDefaultMaxClusters = (1u << 21);\n\n    // 1024MB vertices at 1440p render res\n    static constexpr size_t kDefaultVertexBufferBytes = (1024ull << 20);\n\n    // 3GB CLAS memory at 1440p render res\n    static constexpr size_t kDefaultClasBufferBytes = (3076ull << 20);\n\n    static constexpr uint32_t kMinIsolationLevel = 1u;\n    static constexpr uint32_t kMaxIsolationLevel = 6u;\n\n    enum class VisibilityMode\n    {\n        VIS_LIMIT_EDGES = 0,\n        VIS_SURFACE = 1,\n        COUNT\n    };\n\n    enum class AdaptiveTessellationMode\n    {\n        UNIFORM = 0,\n        WORLD_SPACE_EDGE_LENGTH,\n        SPHERICAL_PROJECTION,\n        COUNT\n    };\n    \n    struct MemorySettings\n    {\n        uint32_t maxClusters = kDefaultMaxClusters;\n        size_t clasBufferBytes = kDefaultClasBufferBytes;\n        size_t vertexBufferBytes = kDefaultVertexBufferBytes;\n\n        bool operator==(const MemorySettings& o) const\n        {\n            return vertexBufferBytes == o.vertexBufferBytes &&\n                maxClusters == o.maxClusters &&\n                clasBufferBytes == o.clasBufferBytes;\n        }\n    };\n    \n    MemorySettings memorySettings;\n    VisibilityMode visMode = VisibilityMode::VIS_LIMIT_EDGES;\n    AdaptiveTessellationMode tessMode = AdaptiveTessellationMode::WORLD_SPACE_EDGE_LENGTH;\n\n    float fineTessellationRate = kDefaultFineTessellationRate;\n    float coarseTessellationRate = kDefaultCoarseTessellationRate;\n    bool  enableFrustumVisibility = true;\n    bool  enableHiZVisibility = true;\n    bool  enableBackfaceVisibility = true;\n    bool  enableLogging = false; // enable debug logging for tessellator build\n    bool  enableMonolithicClusterBuild = false;\n    bool  enableVertexNormals = false; // enable vertex normal computation\n\n    uint2            viewportSize = { 0u, 0u };\n    uint4            edgeSegments = { 8, 8, 8, 8 };\n    uint32_t         isolationLevel = 0; // 0 is dynamic, >0 is fixed\n    ClusterPattern   clusterPattern = ClusterPattern::SLANTED;\n    unsigned char    quantNBits = 0;\n\n    float            displacementScale = 1.0f;\n\n    const Camera* camera = nullptr;\n    const ZBuffer* zbuffer = nullptr;\n\n    int debugSurfaceIndex = 0;\n    int debugClusterIndex = 0;\n    int debugLaneIndex = 0;\n};\n\n#if __cplusplus\n#include <array>\nconstexpr auto kAdaptiveTessellationModeNames = std::to_array<const char*>(\n{\n    \"Uniform\",\n    \"WS Edge Length\",\n    \"Spherical Projection\"\n});\nstatic_assert(kAdaptiveTessellationModeNames.size() == size_t(TessellatorConfig::AdaptiveTessellationMode::COUNT));\n\nconstexpr auto kVisibilityModeNames = std::to_array<const char*>(\n{\n    \"Limit Edge\",\n    \"Surface 1-Ring\"\n});\nstatic_assert(kVisibilityModeNames.size() == size_t(TessellatorConfig::VisibilityMode::COUNT));\n#endif"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/tessellator_constants.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n#ifndef TESSELLATOR_CONSTANTS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define TESSELLATOR_CONSTANTS_H\n\nstatic const uint32_t kMaxApiClusterCount = 1 << 22;\n\n// Max edge size per cluster/template\nstatic const uint32_t kMaxClusterEdgeSegments = 11;\n\n#ifndef __cplusplus\nuint32_t GetTemplateIndex(uint16_t2 clusterSize)\n{\n    return (clusterSize.y - 1) * kMaxClusterEdgeSegments + (clusterSize.x - 1);\n}\n\n#endif\n\n#endif // TESSELLATOR_CONSTANTS_H"
  },
  {
    "path": "rtxmg/include/rtxmg/cluster_builder/tilings.h",
    "content": "#pragma once\n\nstruct ClusterTiling\n{\n    uint16_t2 tilingSize;   // number of tiles in x and y direction\n    uint16_t2  clusterSize;  // number of quads in x and y direction inside tile\n\n    inline uint32_t ClusterCount() { return uint32_t(tilingSize.x) * uint32_t(tilingSize.y); }\n    inline uint32_t ClusterVertexCount() { return (clusterSize.x + 1) * (clusterSize.y + 1); }\n    inline uint32_t VertexCount() { return ClusterVertexCount() * ClusterCount(); }\n\n    inline uint16_t2 ClusterIndex2D(uint32_t rowMajorIndex)\n    {\n        return uint16_t2((uint16_t)(rowMajorIndex % tilingSize.x), (uint16_t)(rowMajorIndex / tilingSize.x));\n    }\n\n    inline uint16_t2 QuadOffset2D(uint32_t rowMajorIndex)\n    {\n        return ClusterIndex2D(rowMajorIndex) * uint16_t2(clusterSize.x, clusterSize.y);\n    }\n\n    inline uint2 VertexIndex2D(uint32_t rowMajorIndex)\n    {\n        uint32_t verticesU = clusterSize.x + 1;\n        return uint2(rowMajorIndex % verticesU, rowMajorIndex / verticesU);\n    }\n};\n\nstruct SurfaceTiling\n{\n    enum\n    {\n        REGULAR = 0,\n        RIGHT,\n        TOP,\n        CORNER,\n        N_SUB_TILINGS\n    };\n    ClusterTiling subTilings[N_SUB_TILINGS];\n    uint16_t2       quadOffsets[N_SUB_TILINGS];  // quad offset of the tiling in x and y direction\n\n    uint32_t inline ClusterCount()\n    {\n        uint32_t sum = 0;\n        for (int iTiling = 0; iTiling < N_SUB_TILINGS; ++iTiling)\n            sum += subTilings[iTiling].ClusterCount();\n        return sum;\n    }\n\n    uint32_t inline VertexCount()\n    {\n        uint32_t sum = 0;\n        for (int iTiling = 0; iTiling < N_SUB_TILINGS; ++iTiling)\n            sum += subTilings[iTiling].VertexCount();\n        return sum;\n    }\n\n    uint16_t2 inline ClusterOffset(uint16_t iTiling, uint32_t iCluster)\n    {\n        return quadOffsets[iTiling] + subTilings[iTiling].QuadOffset2D(iCluster);\n    }\n};\n\ninline SurfaceTiling MakeSurfaceTiling(uint16_t2 surfaceSize)\n{\n    SurfaceTiling ret;\n    uint16_t targetEdgeSegments = 8;\n\n    uint16_t2 regularGridSize;\n    uint16_t2  modCluster;\n    {\n        uint16_t2 divClusters = uint16_t2((uint16_t)(surfaceSize.x / targetEdgeSegments),\n            (uint16_t)(surfaceSize.y / targetEdgeSegments));\n        modCluster = uint16_t2((uint16_t)(surfaceSize.x % targetEdgeSegments),\n            (uint16_t)(surfaceSize.y % targetEdgeSegments));\n\n        uint32_t maxEdgeSegments = kMaxClusterEdgeSegments;\n        if (divClusters.x > 0 && modCluster.x + targetEdgeSegments <= maxEdgeSegments)\n        {\n            divClusters.x -= 1;\n            modCluster.x += targetEdgeSegments;\n        }\n        if (divClusters.y > 0 && modCluster.y + targetEdgeSegments <= maxEdgeSegments)\n        {\n            divClusters.y -= 1;\n            modCluster.y += targetEdgeSegments;\n        }\n        regularGridSize = divClusters;\n    }\n\n    ret.subTilings[SurfaceTiling::REGULAR].tilingSize = regularGridSize;\n    ret.subTilings[SurfaceTiling::REGULAR].clusterSize = uint16_t2(targetEdgeSegments, targetEdgeSegments);\n    ret.quadOffsets[SurfaceTiling::REGULAR] = uint16_t2(0u, 0u);\n\n    ret.subTilings[SurfaceTiling::RIGHT].tilingSize = uint16_t2(1u, regularGridSize.y);\n    ret.subTilings[SurfaceTiling::RIGHT].clusterSize = uint16_t2(modCluster.x, targetEdgeSegments);\n    ret.quadOffsets[SurfaceTiling::RIGHT] = uint16_t2((uint16_t)(regularGridSize.x * targetEdgeSegments), 0u);\n\n    ret.subTilings[SurfaceTiling::TOP].tilingSize = uint16_t2(regularGridSize.x, 1u);\n    ret.subTilings[SurfaceTiling::TOP].clusterSize = uint16_t2(targetEdgeSegments, modCluster.y);\n    ret.quadOffsets[SurfaceTiling::TOP] = uint16_t2(0u, (uint16_t)(regularGridSize.y * targetEdgeSegments));\n\n    ret.subTilings[SurfaceTiling::CORNER].tilingSize = uint16_t2(1u, 1u);\n    ret.subTilings[SurfaceTiling::CORNER].clusterSize = modCluster;\n    ret.quadOffsets[SurfaceTiling::CORNER] = uint16_t2((uint16_t)(regularGridSize.x * targetEdgeSegments),\n        (uint16_t)(regularGridSize.y * targetEdgeSegments));\n\n    return ret;\n}\n\n"
  },
  {
    "path": "rtxmg/include/rtxmg/hiz/hiz_buffer.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <memory>\n\n#include <nvrhi/nvrhi.h>\n#include <donut/core/math/math.h>\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/engine/ShaderFactory.h>\n\n#include \"rtxmg/utils/buffer.h\"\n\n#include \"hiz_buffer_constants.h\"\n\nusing namespace donut::math;\n\nclass HiZBuffer\n{\npublic:\n    ~HiZBuffer() = default;\n\n    static std::unique_ptr<HiZBuffer> Create(uint2 size,\n            std::shared_ptr<donut::engine::CommonRenderPasses> commonPasses,\n            std::shared_ptr<donut::engine::ShaderFactory> shaderFactory,\n            nvrhi::ICommandList* commandList);\n\n    nvrhi::ITexture* GetTextureObject(uint32_t lod) const { return textureObjects[lod]; }\n\n    // resets the depth values across the hi-z mip levels to +inf\n    // note: there should be no need to call this on a per-frame basis\n    void Clear(nvrhi::ICommandList* commandList);\n\n    // applies max reduction to the input zbuffer data to populate\n    // the hi-z mip levels\n    void Reduce(nvrhi::ITexture* zbuffer, nvrhi::ICommandList* commandList);\n\n    // composites the hi-z mip levels over an arbitrary rgba texture\n    // (starting from a small offset at the bottom left corner)\n    void Display(nvrhi::ITexture* output, nvrhi::ICommandList* commandList);\n    \n    void GetDesc(nvrhi::BindingLayoutDesc* outBindingLayout, nvrhi::BindingSetDesc* outBindingSet, bool writeable = false) const;\n\n    uint32_t GetNumLevels() const { return m_numLODs; }\n    float2 GetInvSize() const { return m_invSize; }\nprivate:\n    uint2 m_size = { 0, 0 };\n    float2 m_invSize = { 0.f, 0.f };\n    uint32_t m_numLODs = 0;\n\n    nvrhi::TextureHandle textureObjects[HIZ_MAX_LODS] = { 0 };\n\n    nvrhi::ShaderHandle m_pass1Shader;\n    nvrhi::ShaderHandle m_pass2Shader;\n    nvrhi::ShaderHandle m_displayShader;\n\n    nvrhi::BindingLayoutHandle m_passBL;\n    nvrhi::ComputePipelineHandle m_pass1PSO;\n    nvrhi::ComputePipelineHandle m_pass2PSO;\n\n    nvrhi::BindingLayoutHandle m_displayBL;\n    nvrhi::ComputePipelineHandle m_displayPSO;\n\n    nvrhi::SamplerHandle m_sampler;\n\n    nvrhi::BufferHandle m_reduceParamsBuffer;\n    nvrhi::BufferHandle m_displayParamsBuffer;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/hiz/hiz_buffer_constants.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef HIZBUFFER_CONSTANTS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define HIZBUFFER_CONSTANTS_H\n\n// 9 LODs should support up to 8k monitor \n#define HIZ_MAX_LODS 9\n#define HIZ_LOD0_TILE_SIZE 8u\n#define HIZ_GROUP_SIZE 16\n\n#endif // HIZBUFFER_CONSTANTS_H"
  },
  {
    "path": "rtxmg/include/rtxmg/hiz/hiz_buffer_display_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\nstruct HiZDisplayParams\n{\n    uint32_t level;\n    uint32_t offsetX;\n    uint32_t offsetY;\n    uint32_t pad;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/hiz/hiz_buffer_reduce_params.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\nstruct HiZReducePass1Params\n{\n    float2 zBufferInvSize;\n    float2 pad;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/hiz/zbuffer.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <memory>\n\n#include <nvrhi/nvrhi.h>\n#include <donut/core/math/math.h>\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/engine/ShaderFactory.h>\n\n#include \"rtxmg/utils/buffer.h\"\n\n#include \"rtxmg/hiz/hiz_buffer.h\"\n\nusing namespace donut::math;\n\nclass ZBuffer\n{\n    nvrhi::TextureHandle m_currentTexture;\n    RTXMGBuffer<float> m_minmaxBuffer;\n\n    nvrhi::ShaderHandle m_minmaxShader;\n    nvrhi::ShaderHandle m_displayShader;\n\n    nvrhi::BindingLayoutHandle m_minmaxBL;\n    nvrhi::ComputePipelineHandle m_minmaxPSO;\n\n    nvrhi::BindingLayoutHandle m_displayBL;\n    nvrhi::ComputePipelineHandle m_displayPSO;\n\n    std::unique_ptr<HiZBuffer> m_hierarchy;\n    std::shared_ptr<donut::engine::CommonRenderPasses> m_commonPasses;\n\npublic:\n    static std::unique_ptr<ZBuffer> Create(uint2 size, \n        std::shared_ptr<donut::engine::CommonRenderPasses> commonPasses,\n        std::shared_ptr<donut::engine::ShaderFactory> shaderFactory,\n        nvrhi::ICommandList* commandList);\n\n    nvrhi::TextureHandle GetCurrent() { return m_currentTexture; }\n    const nvrhi::TextureHandle GetCurrent() const { return m_currentTexture; }\n\n    void Display(nvrhi::ITexture *output, nvrhi::ICommandList* commandList);\n    void ReduceHierarchy(nvrhi::ICommandList* commandList);\n    void Clear(nvrhi::ICommandList* commandList);\n\n    int GetNumHiZLODs() const\n    {\n        if (m_hierarchy) return m_hierarchy->GetNumLevels();\n        return 0;\n    }\n\n    float2 GetInvHiZSize() const\n    {\n        if (m_hierarchy) return m_hierarchy->GetInvSize();\n        return float2(0.f, 0.f);\n    }\n    nvrhi::ITexture* GetHierarchyTexture(uint32_t level) const { return m_hierarchy->GetTextureObject(level); }\n    void GetHiZDesc(nvrhi::BindingLayoutDesc* outBindingLayout, nvrhi::BindingSetDesc* outBindingSet) const\n    { \n        m_hierarchy->GetDesc(outBindingLayout, outBindingSet, false);\n    }\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/profiler/gui.h",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#pragma once\n\n#include \"rtxmg/profiler/profiler.h\"\n#include <imgui_internal.h>\n#include \"donut/core/math/math.h\"\n\nstruct ImPlotContext;\nclass UserInterface;\n\n// clang-format on\n\nclass ProfilerGUI\n{\n  public:\n\n    // if fps >= 0 displays value in profiler controller window\n    int fps = -1;\n\n    // if ntris > 0 displays value in profiler controller window \n    uint32_t desiredTris = 0;\n    uint32_t allocatedTris = 0;\n    uint32_t desiredClusters = 0;\n    uint32_t allocatedClusters = 0;\n\n    struct ControllerWindow\n    {\n        ImVec2    pos = ImVec2(0, 0);\n        ImVec2    pivot = ImVec2(0, 0);\n        ImVec2    size = ImVec2(115, 0);\n    } controllerWindow;\n\n    struct ProfilerWindow\n    {\n        ImVec2    pos = ImVec2(0, 0);\n        ImVec2    pivot = ImVec2(1, 0);\n        ImVec2    size = ImVec2(0, 0);\n        ImVec2    screenLayoutSize = ImVec2(0, 0);\n    } profilerWindow;\n\n    bool displayGraphWindow = true;\n\n  public:\n    template <typename... SamplerGroup>\n    void BuildUI( ImFont *iconicFont, ImPlotContext *plotContext, SamplerGroup&... groups );\n\n  private:\n\n    void BuildControllerUI( ImFont *iconicFont, ImPlotContext *plotContext );\n    void BuildFrequencySelectorUI();\n};\n\ninline ImVec2 MakeImVec2(const dm::float2& v)\n{\n    return ImVec2{ v.x, v.y };\n}\n\ninline ImVec2 MakeImVec2(const dm::int2& v)\n{\n    return ImVec2{ float(v.x), float(v.y) };\n}\n\ninline dm::float2 MakeFloat2(const ImVec2& v)\n{\n    return dm::float2{ v.x, v.y };\n}\n\ninline void SetConstrainedWindowPos(const char *windowName, ImVec2 windowPos, const ImVec2& windowPivot, const ImVec2& screenSize)\n{\n    ImGuiCond cond = ImGuiCond_FirstUseEver;    \n    ImGuiWindow* window = ImGui::FindWindowByName(windowName);\n\n    // Bound the window position to be on screen by a margin\n    const float kMinOnscreenLength = 20.0f;\n    if (window)\n    {\n        const dm::float2 kMinOnscreenSize = { kMinOnscreenLength, kMinOnscreenLength };\n        dm::float2 currentWindowPos = MakeFloat2(window->Pos);\n        dm::float2 currentWindowSize = MakeFloat2(window->Size);\n        dm::box2 windowRect{ currentWindowPos, currentWindowPos + currentWindowSize };\n        dm::box2 screenLayoutRect{ kMinOnscreenSize, MakeFloat2(screenSize) - kMinOnscreenSize };\n        \n        if (!screenLayoutRect.intersects(windowRect))\n        {\n            cond = ImGuiCond_Always;\n            dm::float2 minCornerAdjustment = -min(windowRect.m_maxs - screenLayoutRect.m_mins, dm::float2::zero());\n            dm::float2 maxCornerAdjustment = -max(windowRect.m_mins - screenLayoutRect.m_maxs, dm::float2::zero());\n            dm::float2 adjustment = minCornerAdjustment + maxCornerAdjustment;\n            windowRect = windowRect.translate(adjustment);\n\n            windowPos = MakeImVec2(windowRect.m_mins + MakeFloat2(windowPivot) * currentWindowSize);\n        }\n    }\n    ImGui::SetNextWindowPos(windowPos, cond, windowPivot);\n}\n\ntemplate <typename... SamplerGroup>\ninline void ProfilerGUI::BuildUI( ImFont *iconicFont, ImPlotContext *context, SamplerGroup&... groups )\n{\n    BuildControllerUI(iconicFont, context);\n\n    if (displayGraphWindow)\n    {\n        const char* kWindowName = \"Profiler\";\n        SetConstrainedWindowPos(kWindowName, profilerWindow.pos, profilerWindow.pivot, profilerWindow.screenLayoutSize);\n        ImGui::SetNextWindowSize(profilerWindow.size, ImGuiCond_FirstUseEver);\n        ImGui::SetNextWindowCollapsed(true, ImGuiCond_FirstUseEver); // Collapse the window by default\n        ImGui::SetNextWindowBgAlpha(.65f);\n\n        if (ImGui::Begin(kWindowName, &displayGraphWindow, ImGuiWindowFlags_None))\n        {\n            BuildFrequencySelectorUI();\n\n            if (ImGui::BeginTabBar(\"MyTabBar\", ImGuiTabBarFlags_Reorderable))\n            {\n                ImVec2 tabSize = profilerWindow.size;\n                (\n                    [&] {\n                        if( ImGui::BeginTabItem( groups.name.c_str() ) )\n                        {\n                            groups.BuildUI( iconicFont, context );\n                            ImGui::EndTabItem();\n                        }\n                    }(),\n                    ... );\n                ImGui::EndTabBar();\n            }\n        }\n        ImGui::End();\n    }\n}\n"
  },
  {
    "path": "rtxmg/include/rtxmg/profiler/profiler.h",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#pragma once\n\n#include \"rtxmg/profiler/stopwatch.h\"\n#include \"rtxmg/profiler/sampler.h\"\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <vector>\n\n// clang-format on\n\nstruct ImGuiContext;\n\n// Generic execution framework for host/device profiling data.\n// \n// * Typical benchmark usage pattern:\n//   \n//          Timer<> t0 = profiler.InitTimer(\"timer name\");\n//          for (frame loop) \n//          {\n//              profiler.FrameStart(steady_clock::now());\n//   \n//              t0.start();\n//              // ... excecute profiled task\n//              t0.stop();\n//           \n//              profiler.frameStop(); // all timers have been stopped\n//              profiler.FrameResolve(); \n//          }\n//          float avg = t0.average();\n//          profiler.Terminate();\n//   \n//    \n// * Typical (interactive) profiling usage pattern:\n//   \n//          Timer<> t0 = profiler.InitTimer(\"timer name\");\n//          for (frame loop) \n//          {\n//              profiler.FrameStart(steady_clock::now());\n//   \n//              t0.start();\n//              // ... excecute profiled task\n//              t0.stop();\n//           \n//              // ... \n// \n//              profiler.frameStop(); // all timers have been stopped\n// \n//              // ...  \n// \n//              profiler.FrameSync(); //before any timer is polled\n// \n//              float ravg = t0.resolve().runningAverage();\n//          }\n//          profiler.Terminate();\n//\nclass Profiler\n{\n  public:\n    constexpr static size_t BENCH_FRAME_COUNT = 400;  \n\n    // returns the singleton Profiler\n    static Profiler& Get();\n\n    // force the immediate release all device resources\n    static void Terminate();\n\n    // frequency < 0 : profile every frame (benchmark mode)\n    // frequency == 0 : disable profiling\n    // frequency > 0 : records samples at the given pace (in Hz)\n    int recordingFrequency = -1;\n\n    // returns true if the Profiler is recording data for the current frame\n    bool IsRecording() const { return m_isRecording; }\n\n    // insert at the start of every frame (allows to pace sampling and skip\n    // some frames if the frame-rate is too high)\n    // \n    // note: the profiler will only monitor events on stream 0 if no dedicated \n    // streams are specified here. This can cause run-time exceptions if device\n    // timers are polled without host synchronization\n    void FrameStart( std::chrono::steady_clock::time_point time );\n\n    // insert after the last timer is stopped in the frame\n    void FrameEnd();\n\n    // benchmarks data for the frame\n    void FrameResolve();\n\n    // Generic profiling timer with benchmarking functionality\n    template <typename clock_type>\n    struct Timer : public Sampler<float, BENCH_FRAME_COUNT>, private clock_type\n    {\n        Timer( char const* name ) : Sampler( {.name = name} ) { }\n\n        using clock_type::Start;\n        using clock_type::Stop;\n\n        // note: user is responsible for device synchronization: use FrameSync()\n        Timer& Resolve();  // record duration if the timer was active\n        Timer& Profile();  // record duration or 0. if the timer was inactive\n    };\n\n    typedef Timer<StopwatchCPU> CPUTimer;\n    typedef Timer<StopwatchGPU> GPUTimer;\n\n    template <typename timer_type>\n    static inline timer_type& InitTimer( char const* name );\n\n  private:\n    Profiler() noexcept         = default;\n    Profiler( Profiler const& ) = delete;\n    Profiler& operator=( Profiler const& ) = delete;\n\n    std::chrono::steady_clock::time_point m_prevTime;\n\n    bool m_isRecording = false;\n\n  private:\n    std::vector<std::unique_ptr<CPUTimer>> m_cpuTimers;\n    std::vector<std::unique_ptr<GPUTimer>> m_gpuTimers;\n};\n\ntemplate <typename timer_type>\ninline timer_type& Profiler::InitTimer( char const* name )\n{\n    Profiler& profiler = Get();\n    assert( profiler.m_prevTime.time_since_epoch().count() == 0 );\n    if constexpr( std::is_same_v<timer_type, CPUTimer> )\n        return *profiler.m_cpuTimers.emplace_back( std::make_unique<CPUTimer>( name ) );\n    else if constexpr( std::is_same_v<timer_type, GPUTimer> )\n        return *profiler.m_gpuTimers.emplace_back( std::make_unique<GPUTimer>( name ) );\n}\n\nclass ScopedGPUTimer\n{\npublic:\n    ScopedGPUTimer(Profiler::Timer<StopwatchGPU>& timer, nvrhi::ICommandList *commandlist) : m_timer(timer) \n    {\n        m_timer.Start(commandlist);\n    }\n\n    ~ScopedGPUTimer()\n    {\n        m_timer.Stop();\n    }\n\nprivate:\n    Profiler::Timer<StopwatchGPU>& m_timer;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/profiler/sampler.h",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#pragma once\n\n#include <array>\n#include <string>\n\n// clang-format on\n\n// A generic data sampler with basic statistics functionality\n\n// default running avg window = 1 second @ 60Hz\ntemplate <typename T, size_t _size = 60>\nstruct Sampler : public std::array<T, _size>\n{\n    std::string name;\n\n    // values tracked in running circular buffer\n    T samples_sum = T( 0 );\n\n    // values tracked since most recent reset\n    size_t samples_count = 0;\n    T      latest        = {};\n    T      total         = T( 0 );\n    T      min           = std::numeric_limits<T>::max();\n    T      max           = std::numeric_limits<T>::lowest();\n\n    void PushBack( T sample );\n\n    void Reset();\n\n    T Median() const { return .5f * ( double( max ) - double( min ) ); }\n    T Average() const { return static_cast<T>( double( total ) / double( samples_count ) ); }\n    T RunningAverage() const\n    {\n        return static_cast<T>( double( samples_sum ) / double( std::min( samples_count, _size ) ) );\n    }\n\n    // current position in circular buffer\n    uint32_t Offset() const { return static_cast<uint32_t>( samples_count % _size ); }\n\n    void Print()\n    {\n        size_t n = std::min( _size, samples_count );\n        for(size_t i = 0; i < n; i++ )\n        {\n            std::printf( \"Benchmark: frame %d time %.4f ms\\n\", (int)i, (*this)[i]);\n        }\n    }\n};\n\ntemplate <typename T, size_t _size>\ninline void Sampler<T, _size>::PushBack( T sample )\n{\n    latest = sample;\n    total += latest;\n    min = std::min( latest, min );\n    max = std::max( latest, max );\n\n    samples_sum += sample;\n\n    if( samples_count < _size )\n        ( *this )[samples_count++] = sample;\n    else\n    {\n        T& oldest = ( *this )[samples_count++ % _size];\n        samples_sum -= oldest;\n        oldest = sample;\n    }\n}\n\ntemplate <typename T, size_t _size>\ninline void Sampler<T, _size>::Reset()\n{\n    samples_count = 0;\n    latest        = {};\n    samples_sum   = T( 0 );\n    min           = std::numeric_limits<T>::max();\n    max           = std::numeric_limits<T>::min();\n#if !defined( NDEBUG )\n    fill( T( 0 ) );\n#endif\n}"
  },
  {
    "path": "rtxmg/include/rtxmg/profiler/statistics.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#pragma once\n\n#include \"rtxmg/profiler/stopwatch.h\"\n#include \"rtxmg/profiler/profiler.h\"\n\n#include <nvrhi/utils.h>\n#include <donut/engine/DescriptorTableManager.h>\n#include <donut/core/math/math.h>\n\n#include <string>\n#include <map>\n#include <mutex>\n\n#include <implot.h>\n\nclass UserInterface;\n\nnamespace stats\n{\n    using CPUTimer = Profiler::CPUTimer;\n    using GPUTimer = Profiler::GPUTimer;\n    //\n    // SubD stats\n    //\n    struct TopologyMapStats\n    {\n        // hashmap\n        float  pslMean = 0.f;\n        size_t hashCount = 0;\n        size_t addressCount = 0;\n        float  loadFactor = 0.f;\n\n        // plans\n        uint32_t plansCount = 0;\n        size_t   plansByteSize = 0;\n\n        uint32_t regularFacePlansCount = 0;\n\n        uint32_t maxFaceSize = 0;\n        uint32_t sharpnessCount = 0;\n        float    sharpnessMax;\n\n        // patch points\n        uint32_t stencilCountMin = 0;\n        uint32_t stencilCountMax = 0;\n        float stencilCountAvg = 0;\n        std::vector<uint32_t> stencilCountHistogram;\n    };\n\n    struct SurfaceTableStats\n    {\n        std::string name;\n\n        size_t indexBufferSize = 0;\n        size_t vertCountBufferSize = 0;\n\n        size_t byteSize = 0;\n        size_t surfaceCount = 0;\n\n        uint32_t irregularFaceCount = 0;\n        uint32_t maxValence = 0;\n        uint32_t maxFaceSize = 0;\n        // | boundaries | stencils  | creases |\n        uint32_t holesCount = 0;            // |            |           |         |\n        uint32_t bsplineSurfaceCount = 0;   // |            |           |         |\n        uint32_t regularSurfaceCount = 0;   // |     x      |           |         |\n        uint32_t isolationSurfaceCount = 0; // |     X      |     X     |         |\n        uint32_t sharpSurfaceCount = 0;     // |     X      |     X     |    X    |\n\n        float sharpnessMax = 0.f;\n        uint32_t infSharpCreases = 0;\n\n        uint32_t stencilCountMin = ~uint32_t(0);\n        uint32_t stencilCountMax = 0;\n        float stencilCountAvg = 0;\n        std::vector<uint32_t> stencilCountHistogram;\n\n        std::vector<std::string> topologyRecommendations;\n\n        bool IsCatmarkTopology(float* ratio = nullptr) const\n        {\n            // guess if the user passed a triangles mesh (ie. not a subd model)\n            float _ratio = float(irregularFaceCount) / float(surfaceCount);\n            if (ratio)\n                *ratio = _ratio;\n            return _ratio < .25f;\n        }\n        void BuildTopologyRecommendations();\n\n        void BuildRecommendationsUI(ImFont *iconicFont) const;\n\n        void BuildUI(ImFont *iconicFont, ImPlotContext *plotContext, uint32_t imguiID) const;\n    };\n\n    //\n    // General stats\n    //\n\n    struct FrameSamplers\n    {\n        std::string name = \"Frame\";\n\n        Sampler<float, Profiler::BENCH_FRAME_COUNT> cpuFrameTime = { .name = \"CPU/frame (ms)\" };\n\n        GPUTimer& gpuFrameTime = Profiler::InitTimer<GPUTimer>(\"GPU/frame (ms)\");\n        GPUTimer& gpuRenderTime = Profiler::InitTimer<GPUTimer>(\"GPU/trace (ms)\");\n        GPUTimer& gpuDenoiserTime = Profiler::InitTimer<GPUTimer>(\"GPU/denoiser (ms)\");\n        \n        GPUTimer& hiZRenderTime = Profiler::InitTimer<GPUTimer>(\"GPU/hi-z (ms)\");\n        GPUTimer& zReprojectionTime = Profiler::InitTimer<GPUTimer>(\"GPU/zReprojection (ms)\");\n        GPUTimer& zRenderPassTime = Profiler::InitTimer<GPUTimer>(\"GPU/zRenderPass (ms)\");\n        GPUTimer& computeMotionVectorsTimer = Profiler::InitTimer<GPUTimer>(\"GPU/motion vectors (ms)\");\n        void BuildUI(ImFont *iconicFont, ImPlotContext *plotContext) const;\n    };\n    extern FrameSamplers frameSamplers;\n\n\n    struct ClusterAccelSamplers\n    {\n        std::string name = \"AccelBuilder\";\n\n        GPUTimer& clusterTilingTime = Profiler::InitTimer<GPUTimer>(\"GPU/Cluster Tiling (ms)\");\n        GPUTimer& fillClustersTime = Profiler::InitTimer<GPUTimer>(\"GPU/Fill Clusters (ms)\");\n        GPUTimer& buildClasTime = Profiler::InitTimer<GPUTimer>(\"GPU/CLAS build (ms)\");\n        GPUTimer& buildBlasTime = Profiler::InitTimer<GPUTimer>(\"GPU/BLAS build (ms)\");\n\n        Sampler<uint32_t> numClusters = { .name = \"Clusters count\", };\n        Sampler<uint32_t> numTriangles = { .name = \"Triangles count\", };\n        \n        donut::math::int2 renderSize = {};\n\n        void BuildUI(ImFont* iconicFont, ImPlotContext* plotContext) const;\n    };\n    extern ClusterAccelSamplers clusterAccelSamplers;\n\n    struct EvaluatorSamplers\n    {\n        std::string name = \"Subdivision Evaluator\";\n\n        TopologyMapStats topologyMapStats;\n\n        bool hasBadTopology = false;\n        bool m_topologyQualityButtonPressed = false;\n        size_t surfaceTablesByteSizeTotal = 0;\n\n        std::vector<SurfaceTableStats> surfaceTableStats;\n\n\n        // run-time evaluation\n\n        Sampler<uint32_t> numLimitSamples = { .name = \"Limit evaluations\", };\n\n        void BuildUI(ImFont* iconicFont, ImPlotContext* plotContext);\n    };\n    extern EvaluatorSamplers evaluatorSamplers;\n\n    struct MemUsageSamplers\n    {\n        std::string name = \"Memory\";\n\n        Sampler<size_t> blasSize = { .name = \"BLAS size\", };\n        Sampler<size_t> blasScratchSize = { .name = \"BLAS Scratch\", };\n        Sampler<size_t> clasSize = { .name = \"CLAS size\", };\n        Sampler<size_t> vertexBufferSize = { .name = \"Vertex Buffer\", };\n        Sampler<size_t> vertexNormalsBufferSize = { .name = \"Vertex Normals Buffer\", };\n        Sampler<size_t> clusterShadingDataSize = { .name = \"Cluster Data Buffer\", };\n\n        void BuildUI(ImFont* iconicFont, ImPlotContext* plotContext) const;\n    };\n    extern MemUsageSamplers memUsageSamplers;\n\n}  // end namespace stats\n"
  },
  {
    "path": "rtxmg/include/rtxmg/profiler/stopwatch.h",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n// clang-format off\n\n#include <cassert>\n#include <chrono>\n#include <cstdint>\n#include <optional>\n\n#include <nvrhi/nvrhi.h>\n\n// clang-format on\n\nclass StopwatchCPU\n{\npublic:\n    void Start();\n    void Stop();\n\n    std::optional<float> Elapsed();  // returns dt = stop - start\n    std::optional<float> Before(std::chrono::steady_clock::time_point t);\n    std::optional<float> After(std::chrono::steady_clock::time_point t);\n\nprivate:\n    using steady_clock = std::chrono::steady_clock;\n    using duration = std::chrono::duration<double, std::milli>;\n\n    steady_clock::time_point m_startTime;\n    steady_clock::time_point m_stopTime;\n};\n\nclass StopwatchGPU\n{\npublic:\n    void Start(nvrhi::ICommandList* commandList);\n    void Stop();\n\n    std::optional<float> Elapsed();      // returns dt = stop - start\n    std::optional<float> ElapsedAsync(); // returns dt = stop - start\nprivate:\n\n    void ProcessUnresolvedQueries();\n\n    static constexpr uint32_t kMaxInFlightQueries = 3;\n\n    nvrhi::DeviceHandle m_device;\n    std::array<nvrhi::TimerQueryHandle, kMaxInFlightQueries> m_timerQueries;\n    nvrhi::CommandListHandle m_commandList;\n    int32_t m_queryIndex = -1;\n    int32_t m_unresolvedQueryIndex = -1;\n    float m_lastDuration = 0.f;\n    bool m_hasLastDuration = false;\n\n    enum class State : uint8_t\n    {\n        uninitialized = 0,\n        reset,\n        ticking,\n        stopped\n    } state = State::uninitialized;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/box_extent.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once \n\n#include <donut/core/math/math.h>\n\nfloat MaxBoxExtent(const donut::math::box3& aabb);\n"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/camera.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include <donut/core/math/math.h>\n\nusing namespace donut::math;\n\n#include <array>\n#include <string>\n\nclass Camera\n{\n\npublic:\n    bool HasChanged() const { return m_changed; }\n\n    float3 GetDirection() const { return normalize(m_lookat - m_eye); }\n    void SetDirection(const float3& dir)\n    {\n        m_lookat = m_eye + length(m_lookat - m_eye) * dir;\n    }\n\n    void Translate(float3 const& v);\n    void Rotate(float yaw, float pitch, float roll);\n    void Roll(float speed);\n\n    void Dolly(float factor);\n    void Pan(float2 speed);\n    void Zoom(const float factor);\n\n    void Frame(box3 const& aabb);\n\n    // UVW forms an orthogonal, but not orthonormal basis!\n    std::array<float3, 3> const& GetBasis();\n\n    void Print() const;\n\n    float3 GetEye() const { return m_eye; }\n    float3 GetLookat() const { return m_lookat; }\n    float3 GetUp() const { return m_up; }\n\n    float GetFovY() const { return m_fovY; }\n    float GetAspectRatio() const { return m_aspectRatio; }\n    float GetZNear() const { return m_zNear; }\n    float GetZFar() const { return m_zFar; }\n\n    // These return column vectors but are stored in row_major memory wise\n    // Translation is in m[3][j]  \n    float4x4 GetViewMatrix() const;\n    float4x4 GetProjectionMatrix() const;\n    float4x4 GetViewProjectionMatrix() const;\n\n    void SetEye(float3 eye);\n    void SetLookat(float3 lookat);\n    void SetUp(float3 up);\n\n    void SetFovY(float fovy);\n    void SetAspectRatio(float ar);\n    void SetNear(float near);\n    void SetFar(float far);\n\n    void Set(std::string const& camc_string);\n\nprivate:\n    void ComputeBasis(float3& u, float3& v, float3& w) const;\n\n    std::array<float3, 3> m_basis = {};\n\n    float3 m_eye = float3(1.f);\n    float3 m_lookat = float3(0.f);\n    float3 m_up = float3(0.f, 1.f, 0.f);\n\n    float m_fovY = 35.f;\n    float m_aspectRatio = 1.f;\n    float m_zNear = 0.1f;\n    float m_zFar = 100.f;\n\n    bool m_changed = true;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/json.h",
    "content": "\n#pragma once\n\n// clang-format off\n\n#include <cassert>\n#include <filesystem>\n#include <string>\n\n#include <donut/core/math/math.h>\n\nusing namespace donut::math;\n\nnamespace Json\n{\n    class Value;\n}\n\nJson::Value readFile(const std::filesystem::path& m_filepath);\n\ntemplate <typename T> T read(const Json::Value& node, const T& defaultValue) { assert(false); return T{}; }\n\ntemplate <> std::string read<std::string>(const Json::Value& node, const std::string& defaultValue);\n\ntemplate <> bool read<bool>(const Json::Value& node, const bool& defaultValue);\n\ntemplate <> int8_t read<int8_t>(const Json::Value& node, const int8_t& defaultValue);\ntemplate <> int16_t read<int16_t>(const Json::Value& node, const int16_t& defaultValue);\ntemplate <> int32_t read<int32_t>(const Json::Value& node, const int32_t& defaultValue);\ntemplate <> int2 read<int2>(const Json::Value& node, const int2& defaultValue);\ntemplate <> int3 read<int3>(const Json::Value& node, const int3& defaultValue);\ntemplate <> int4 read<int4>(const Json::Value& node, const int4& defaultValue);\n\ntemplate <> uint8_t read<uint8_t>(const Json::Value& node, const uint8_t& defaultValue);\ntemplate <> uint16_t read<uint16_t>(const Json::Value& node, const uint16_t& defaultValue);\ntemplate <> uint32_t read<uint32_t>(const Json::Value& node, const uint32_t& defaultValue);\ntemplate <> uint2 read<uint2>(const Json::Value& node, const uint2& defaultValue);\ntemplate <> uint3 read<uint3>(const Json::Value& node, const uint3& defaultValue);\ntemplate <> uint4 read<uint4>(const Json::Value& node, const uint4& defaultValue);\n\ntemplate <> float read<float>(const Json::Value& node, const float& defaultValue);\ntemplate <> float2 read<float2>(const Json::Value& node, const float2& defaultValue);\ntemplate <> float3 read<float3>(const Json::Value& node, const float3& defaultValue);\ntemplate <> float4 read<float4>(const Json::Value& node, const float4& defaultValue);\n\ntemplate <> double read<double>(const Json::Value& node, const double& defaultValue);\ntemplate <> double2 read<double2>(const Json::Value& node, const double2& defaultValue);\ntemplate <> double3 read<double3>(const Json::Value& node, const double3& defaultValue);\ntemplate <> double4 read<double4>(const Json::Value& node, const double4& defaultValue);\n\ntemplate<typename T> void operator >> (const Json::Value& node, T& dest)\n{\n    dest = read<T>(node, dest);\n}\n// clang-format on\n#pragma once\n"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/model.h",
    "content": "#pragma once\n/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\nclass SubdivisionSurface;\n\n#include <donut/core/math/math.h>\n#include <donut/engine/SceneGraph.h>\n\nusing namespace donut::math;\n\nenum class GeometryType : uint8_t\n{\n    GEOMETRY_TYPE_UNKNOWN = 0,\n    GEOMETRY_TYPE_STRUCTURED = (1 << 0),\n    GEOMETRY_TYPE_UNSTRUCTURED = (1 << 1),\n};\n\nstruct Instance\n{\n    std::shared_ptr<donut::engine::MeshInstance> meshInstance;\n    affine3 localToWorld = affine3::identity();\n\n    box3 aabb;\n\n    float3 translation = { 0.f, 0.f, 0.f };\n    quat rotation = { 1.f, 0.f, 0.f, 0.f };\n    float3 scaling = { 1.f, 1.f, 1.f };\n\n    float scale = 1.0f; // For LOD traversal, computed from localToWorld\n    float radius = 0.f;\n    float edgelength = 0.f;\n    uint32_t meshID = ~uint32_t(0);\n\n    GeometryType geometryType = GeometryType::GEOMETRY_TYPE_UNKNOWN;\n\n    // Pointer to all clusters for this instance's resource\n    //  const lod::Cluster *const *d_unstructuredClusters = nullptr;\n\n    void Animate(float animTime, float animRate);\n\n    void UpdateLocalTransform();\n\n    void Lerp(Instance const& a, Instance const& b, float t);\n};\n\nstruct Model\n{\n    int2 frameRange = { std::numeric_limits<int>::max(),\n                       std::numeric_limits<int>::min() };\n    std::unique_ptr<SubdivisionSurface> subd;\n    std::vector<Instance> instances;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/obj_importer.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <filesystem>\n#include <memory>\n#include <optional>\n\n#include <nvrhi/utils.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/engine/DescriptorTableManager.h>\n\n#include \"rtxmg/scene/model.h\"\n\nnamespace donut::vfs\n{\n    class IFileSystem;\n}\n\nnamespace donut::engine\n{\n    struct SceneImportResult;\n    class TextureCache;\n    class SceneTypeFactory;\n} // namespace donut::engine\n\nnamespace tf\n{\n    class Executor;\n}\n\nnamespace fs = std::filesystem;\n\nclass TopologyCache;\n\nclass ObjImporter\n{\nprotected:\n    std::shared_ptr<donut::vfs::IFileSystem> m_fs;\n    std::shared_ptr<donut::engine::SceneTypeFactory> m_sceneTypeFactory;\n    std::shared_ptr<donut::engine::DescriptorTableManager> m_descriptorTableManager;\n    TopologyCache& m_topologyCache;\n\n    fs::path m_modelPath;\n    const fs::path& m_mediaPath;\n\npublic:\n    explicit ObjImporter(\n        std::shared_ptr<donut::vfs::IFileSystem> fs,\n        const fs::path& mediapath,\n        std::shared_ptr<donut::engine::SceneTypeFactory> sceneTypeFactory,\n        std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTableManager,\n        TopologyCache& topologyCache);\n\n    std::optional<Model> Load(const std::filesystem::path& fileName,\n        donut::engine::TextureCache& textureCache,\n        int2 frameRange, const Instance& parent,\n        nvrhi::ICommandList* commandList) const;\n\n    void SetModelPath(fs::path&& path) { m_modelPath = std::move(path); }\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/scene.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <json/json.h>\n\n#include \"rtxmg/scene/obj_importer.h\"\n\n#include \"rtxmg/subdivision/topology_map.h\"\n#include \"rtxmg/cluster_builder/cluster_accel_builder.h\"\n\n#include <donut/core/math/math.h>\n#include <donut/engine/Scene.h>\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/shaders/bindless.h>\n\nclass MaterialCache;\n\nusing namespace donut::engine;\nusing namespace donut::math;\n\nstruct View\n{\n    float3 position = { 0.f, 0.f, -1.f };\n    float3 lookat = { 0.f, 0.f, -1.f };\n    float3 up = { 1.f, 1.f, 1.f };\n    float fov = 35.f;\n};\n\nenum TextureType\n{\n    ALBEDO = 0,\n    ROUGHNESS,\n    SPECULAR,\n    DISPLACEMENT,\n    ENVMAP,\n    TEXTURE_TYPE_COUNT\n};\n\nclass RTXMGScene : public Scene\n{\npublic:\n    struct Attributes\n    {\n        std::string audio;\n        float audioStartTime = 0.f;\n\n        int2 frameRange = { std::numeric_limits<int>::max(),\n                           std::numeric_limits<int>::min() };\n        float frameRate = 0.f;\n\n        float averageInstanceScale = 0.f;\n        box3 aabb;\n    };\n\n    RTXMGScene(nvrhi::IDevice* device,\n        const fs::path& mediapath,\n        std::shared_ptr<CommonRenderPasses> commonPasses,\n        ShaderFactory& shaderFactory,\n        std::shared_ptr<donut::vfs::IFileSystem> fs,\n        std::shared_ptr<TextureCache> textureCache,\n        std::shared_ptr<DescriptorTableManager> descriptorTable,\n        std::shared_ptr<SceneTypeFactory> sceneTypeFactory,\n        int2 initialFrameRange, int isoLevelSharp, int isoLevelSmooth);\n\n    bool LoadWithExecutor(const std::filesystem::path& filename,\n        tf::Executor* executor) override;\n\n    const Attributes& GetAttributes() const { return m_attributes; }\n    void InsertModel(Model&& model);\n\n    const std::vector<std::unique_ptr<SubdivisionSurface>>&\n        GetSubdMeshes() const\n    {\n        return m_subdMeshes;\n    }\n\n    std::vector<Instance>& GetInstances()\n    {\n        return m_instances;\n    }\n\n    const std::vector<std::unique_ptr<TopologyMap const>>&\n        GetTopologyMaps() const\n    {\n        return m_topologyMaps;\n    }\n    std::span<Instance>       GetSubdMeshInstances();\n    std::span<Instance const> GetSubdMeshInstances() const;\n    uint32_t TotalSubdPatchCount() const;\n\n    const View* GetView() const { return m_view.get(); }\n\n    void Animate(float animTime, float animRate);\n    \n    nvrhi::SamplerHandle GetDisplacementSampler() const { return m_commonPasses->m_LinearWrapSampler; }\n\n    const Json::Value& GetSceneSettings() const { return m_sceneSettings; }\n    std::string& GetInputPath() { return m_inputPath; }\n\n    static fs::path ResolveMediapath(const fs::path& m_filepath, const fs::path& mediapath);\nprotected:\n    void LoadSceneFile(const std::filesystem::path& filename, std::unique_ptr<ObjImporter>& objImporter, nvrhi::ICommandList* commandList);\n\n\nprivate:\n    Attributes m_attributes;\n    Json::Value m_sceneSettings;\n    const fs::path& m_mediaPath;\n\n    int m_isoLevelSharp;\n    int m_isoLevelSmooth;\n\n    // starting viewpoint\n    std::unique_ptr<View> m_view;\n\n    std::vector<std::unique_ptr<TopologyMap const>> m_topologyMaps;\n    std::vector<std::unique_ptr<SubdivisionSurface>> m_subdMeshes;\n    std::vector<Instance> m_instances;\n    std::shared_ptr<donut::engine::CommonRenderPasses> m_commonPasses;\n    \n    std::string m_inputPath;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/scene/string_utils.h",
    "content": "//\n// Copyright (c) 2012-2016, NVIDIA CORPORATION. All rights reserved.\n//\n// NVIDIA CORPORATION and its licensors retain all intellectual property\n// and proprietary rights in and to this software, related documentation\n// and any modifications thereto. Any use, reproduction, disclosure or\n// distribution of this software and related documentation without an express\n// license agreement from NVIDIA CORPORATION is strictly prohibited.\n//\n\n#pragma once\n\n#include <cstdint>\n#include <filesystem>\n#include <memory>\n#include <string>\n\n#include <donut/core/math/math.h>\n\nusing namespace donut::math;\n\n// some 'fast' string parsing helpers : use safer std functions\n// instead wherever possible\n\ninline bool IsWhiteSpace(char c)\n{\n    return ((c == ' ') || (c == '\\t') || (c == '\\r'));\n}\n\ninline bool IsNewLine(char c) { return c == '\\n'; }\n\ninline bool IsDigit(char c) { return ((c >= '0') && (c <= '9')); }\n\ninline bool IsExponent(char c) { return ((c == 'e') || (c == 'E')); }\n\ninline char const* SkipWhiteSpace(char const* ptr)\n{\n    while (IsWhiteSpace(*ptr))\n        ++ptr;\n    return ptr;\n}\n\ninline char const* SkipLine(char const* ptr)\n{\n    while (!IsNewLine(*ptr++))\n        ;\n    return ptr;\n}\n\ninline char const* SkipWord(char const* ptr)\n{\n    while ((!IsWhiteSpace(*ptr)) && (*ptr != '\\n') && (*ptr != '\\0'))\n        ++ptr;\n    return ptr;\n}\n\ninline char const* SkipWords(char const* ptr, uint32_t count)\n{\n    for (uint32_t i = 0; i < count; ++i)\n    {\n        ptr = SkipWord(ptr);\n        ptr = SkipWhiteSpace(ptr);\n    }\n    return ptr;\n}\n\ninline char const* FindSubstring(char const* str, char const* substr,\n    uint32_t num)\n{\n    while ((*str != '\\0') && (num >= 0))\n    {\n        if (!std::memcmp(str, substr, num))\n            return str;\n        ++str;\n    }\n    return nullptr;\n}\n\nchar const* ParseInt(char const* ptr, int* value);\n\nchar const* ParseDouble(char const* ptr, double* value);\nchar const* ParseFloat(char const* ptr, float* value);\n\nchar const* ParseString(char const* ptr, std::string* value);\n\nstd::string ReadASCIIFile(char const* m_filepath);\nchar const* sgets(char* s, int size, char** stream);\n\n\nstd::unique_ptr<uint8_t[]> ReadBigFile(std::filesystem::path const& m_filepath,\n    uint64_t* size = nullptr);\n\nstd::istream& operator>>(std::istream& is, float3& v);\nstd::ostream& operator<<(std::ostream& os, float3& v);\nstd::ostream& operator<<(std::ostream& os, box3& v);\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/far.h",
    "content": "//\n//   Copyright 2013 Pixar\n//\n//   Licensed under the Apache License, Version 2.0 (the \"Apache License\")\n//   with the following modification; you may not use this file except in\n//   compliance with the Apache License and the following modification to it:\n//   Section 6. Trademarks. is deleted and replaced with:\n//\n//   6. Trademarks. This License does not grant permission to use the trade\n//      names, trademarks, service marks, or product names of the Licensor\n//      and its affiliates, except as required to comply with Section 4(c) of\n//      the License and to reproduce the content of the NOTICE file.\n//\n//   You may obtain a copy of the Apache License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the Apache License with the above modification is\n//   distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n//   KIND, either express or implied. See the Apache License for the specific\n//   language governing permissions and limitations under the Apache License.\n//\n\n#ifndef FAR_UTILS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define FAR_UTILS_H\n// clang-format off\n\n#include \"shape.h\"\n\n#include <opensubdiv/far/topologyRefinerFactory.h>\n#include <opensubdiv/far/primvarRefiner.h>\n#include <opensubdiv/far/types.h>\n\n#include <cstdio>\n// clang-format on\n\n//------------------------------------------------------------------------------\n\ninline Scheme\nConvertSdcTypeToShapeScheme(OpenSubdiv::Sdc::SchemeType sdcScheme)\n{\n\n    switch (sdcScheme)\n    {\n    case OpenSubdiv::Sdc::SCHEME_BILINEAR:\n        return kBilinear;\n    case OpenSubdiv::Sdc::SCHEME_CATMARK:\n        return kCatmark;\n    case OpenSubdiv::Sdc::SCHEME_LOOP:\n        return kLoop;\n    default:\n        printf(\"unknown Sdc::SchemeType : %d\\n\", (int)sdcScheme);\n        break;\n    }\n    return kCatmark;\n}\n\ninline OpenSubdiv::Sdc::SchemeType\nConvertShapeSchemeToSdcType(Scheme shapeScheme)\n{\n\n    switch (shapeScheme)\n    {\n    case kBilinear:\n        return OpenSubdiv::Sdc::SCHEME_BILINEAR;\n    case kCatmark:\n        return OpenSubdiv::Sdc::SCHEME_CATMARK;\n    case kLoop:\n        return OpenSubdiv::Sdc::SCHEME_LOOP;\n    default:\n        printf(\"unknown Shape Scheme : %d\\n\", (int)shapeScheme);\n        break;\n    }\n    return OpenSubdiv::Sdc::SCHEME_CATMARK;\n}\n\ninline OpenSubdiv::Sdc::SchemeType GetSdcType(Shape const& shape)\n{\n    return ConvertShapeSchemeToSdcType(shape.scheme);\n}\n\ninline OpenSubdiv::Sdc::Options GetSdcOptions(Shape const& shape)\n{\n    typedef OpenSubdiv::Sdc::Options Options;\n\n    Options result;\n\n    result.SetVtxBoundaryInterpolation(Options::VTX_BOUNDARY_EDGE_ONLY);\n    result.SetCreasingMethod(Options::CREASE_UNIFORM);\n    result.SetTriangleSubdivision(Options::TRI_SUB_CATMARK);\n\n    for (int i = 0; i < (int)shape.tags.size(); ++i)\n    {\n        auto const& t = shape.tags[i];\n\n        if (t.name == \"interpolateboundary\")\n        {\n            if ((int)t.intargs.size() != 1)\n            {\n                printf(\"expecting 1 integer for \\\"interpolateboundary\\\" tag n. %d\\n\",\n                    i);\n                continue;\n            }\n            switch (t.intargs[0])\n            {\n            case 0:\n                result.SetVtxBoundaryInterpolation(Options::VTX_BOUNDARY_NONE);\n                break;\n            case 1:\n                result.SetVtxBoundaryInterpolation(\n                    Options::VTX_BOUNDARY_EDGE_AND_CORNER);\n                break;\n            case 2:\n                result.SetVtxBoundaryInterpolation(Options::VTX_BOUNDARY_EDGE_ONLY);\n                break;\n            default:\n                printf(\"unknown interpolate boundary : %d\\n\", t.intargs[0]);\n                break;\n            }\n        }\n        else if (t.name == \"facevaryinginterpolateboundary\")\n        {\n            if ((int)t.intargs.size() != 1)\n            {\n                printf(\"expecting 1 integer for \\\"facevaryinginterpolateboundary\\\" tag \"\n                    \"n. %d\\n\",\n                    i);\n                continue;\n            }\n            switch (t.intargs[0])\n            {\n            case 0:\n                result.SetFVarLinearInterpolation(Options::FVAR_LINEAR_NONE);\n                break;\n            case 1:\n                result.SetFVarLinearInterpolation(Options::FVAR_LINEAR_CORNERS_ONLY);\n                break;\n            case 2:\n                result.SetFVarLinearInterpolation(Options::FVAR_LINEAR_CORNERS_PLUS1);\n                break;\n            case 3:\n                result.SetFVarLinearInterpolation(Options::FVAR_LINEAR_CORNERS_PLUS2);\n                break;\n            case 4:\n                result.SetFVarLinearInterpolation(Options::FVAR_LINEAR_BOUNDARIES);\n                break;\n            case 5:\n                result.SetFVarLinearInterpolation(Options::FVAR_LINEAR_ALL);\n                break;\n            default:\n                printf(\"unknown interpolate boundary : %d\\n\", t.intargs[0]);\n                break;\n            }\n        }\n        else if (t.name == \"facevaryingpropagatecorners\")\n        {\n            if ((int)t.intargs.size() == 1)\n            {\n                // no propagate corners in Options\n                assert(0);\n            }\n            else\n                printf(\"expecting single int argument for \"\n                    \"\\\"facevaryingpropagatecorners\\\"\\n\");\n        }\n        else if (t.name == \"creasemethod\")\n        {\n\n            if ((int)t.stringargs.size() == 0)\n            {\n                printf(\"the \\\"creasemethod\\\" tag expects a string argument\\n\");\n                continue;\n            }\n\n            if (t.stringargs[0] == \"normal\")\n            {\n                result.SetCreasingMethod(Options::CREASE_UNIFORM);\n            }\n            else if (t.stringargs[0] == \"chaikin\")\n            {\n                result.SetCreasingMethod(Options::CREASE_CHAIKIN);\n            }\n            else\n            {\n                printf(\"the \\\"creasemethod\\\" tag only accepts \\\"normal\\\" or \"\n                    \"\\\"chaikin\\\" as value (%s)\\n\",\n                    t.stringargs[0].c_str());\n            }\n        }\n        else if (t.name == \"smoothtriangles\")\n        {\n\n            if (shape.scheme != kCatmark)\n            {\n                printf(\"the \\\"smoothtriangles\\\" tag can only be applied to Catmark \"\n                    \"meshes\\n\");\n                continue;\n            }\n            if (t.stringargs[0] == \"catmark\")\n            {\n                result.SetTriangleSubdivision(Options::TRI_SUB_CATMARK);\n            }\n            else if (t.stringargs[0] == \"smooth\")\n            {\n                result.SetTriangleSubdivision(Options::TRI_SUB_SMOOTH);\n            }\n            else\n            {\n                printf(\"the \\\"smoothtriangles\\\" tag only accepts \\\"catmark\\\" or \"\n                    \"\\\"smooth\\\" as value (%s)\\n\",\n                    t.stringargs[0].c_str());\n            }\n        }\n    }\n\n    return result;\n}\n\n//------------------------------------------------------------------------------\n\nvoid InterpolateFVarData(OpenSubdiv::Far::TopologyRefiner& refiner,\n    Shape const& shape, std::vector<float>& fvarData);\n\n//------------------------------------------------------------------------------\n\ntemplate <class T>\nOpenSubdiv::Far::TopologyRefiner*\nInterpolateFarVertexData(Shape const& shape, int maxlevel,\n    std::vector<T>& data)\n{\n\n    typedef OpenSubdiv::Far::TopologyRefiner FarTopologyRefiner;\n    typedef OpenSubdiv::Far::TopologyRefinerFactory<Shape>\n        FarTopologyRefinerFactory;\n\n    // Far interpolation\n    FarTopologyRefiner* refiner = FarTopologyRefinerFactory::Create(\n        shape, FarTopologyRefinerFactory::Options(GetSdcType(shape),\n            GetSdcOptions(shape)));\n    assert(refiner);\n\n    FarTopologyRefiner::UniformOptions options(maxlevel);\n    options.fullTopologyInLastLevel = true;\n    refiner->RefineUniform(options);\n\n    // populate coarse mesh positions\n    data.resize(refiner->GetNumVerticesTotal());\n    for (int i = 0; i < refiner->GetLevel(0).GetNumVertices(); i++)\n    {\n        data[i].SetPosition(shape.verts[i * 3 + 0], shape.verts[i * 3 + 1],\n            shape.verts[i * 3 + 2]);\n    }\n\n    T* srcVerts = &data[0];\n    T* dstVerts = srcVerts + refiner->GetLevel(0).GetNumVertices();\n    OpenSubdiv::Far::PrimvarRefiner primvarRefiner(*refiner);\n\n    for (int i = 1; i <= refiner->GetMaxLevel(); ++i)\n    {\n        primvarRefiner.Interpolate(i, srcVerts, dstVerts);\n        srcVerts = dstVerts;\n        dstVerts += refiner->GetLevel(i).GetNumVertices();\n    }\n    return refiner;\n}\n\n//------------------------------------------------------------------------------\n\nnamespace OpenSubdiv {\nnamespace OPENSUBDIV_VERSION {\nnamespace Far {\n\ntemplate <>\ninline bool TopologyRefinerFactory<Shape>::resizeComponentTopology(\n    Far::TopologyRefiner& refiner, Shape const& shape)\n{\n    int nfaces = shape.GetNumFaces(), nverts = shape.GetNumVertices();\n\n    setNumBaseFaces(refiner, nfaces);\n    for (int i = 0; i < nfaces; ++i)\n    {\n\n        int nv = shape.nvertsPerFace[i];\n        setNumBaseFaceVertices(refiner, i, nv);\n    }\n\n    // Vertices and vert-faces and vert-edges\n    setNumBaseVertices(refiner, nverts);\n\n    return true;\n}\n\n//----------------------------------------------------------\ntemplate <>\ninline bool TopologyRefinerFactory<Shape>::assignComponentTopology(\n    Far::TopologyRefiner& refiner, Shape const& shape)\n{\n    { // Face relations:\n        int nfaces = getNumBaseFaces(refiner);\n\n        for (int i = 0, ofs = 0; i < nfaces; ++i)\n        {\n\n            Far::IndexArray dstFaceVerts = getBaseFaceVertices(refiner, i);\n\n            if (shape.isLeftHanded)\n            {\n                dstFaceVerts[0] = shape.faceverts[ofs++];\n                for (int j = dstFaceVerts.size() - 1; j > 0; --j)\n                {\n                    dstFaceVerts[j] = shape.faceverts[ofs++];\n                }\n            }\n            else\n            {\n                for (int j = 0; j < dstFaceVerts.size(); ++j)\n                {\n                    dstFaceVerts[j] = shape.faceverts[ofs++];\n                }\n            }\n        }\n    }\n    return true;\n}\n\n//----------------------------------------------------------\ntemplate <>\ninline bool TopologyRefinerFactory<Shape>::assignFaceVaryingTopology(\n    Far::TopologyRefiner& refiner, Shape const& shape)\n{\n    // UV layout (we only parse 1 channel)\n    if (!shape.faceuvs.empty())\n    {\n\n        int nfaces = getNumBaseFaces(refiner),\n            channel = createBaseFVarChannel(refiner, (int)shape.uvs.size() / 2);\n\n        for (int i = 0, ofs = 0; i < nfaces; ++i)\n        {\n\n            Far::IndexArray dstFaceUVs = getBaseFaceFVarValues(refiner, i, channel);\n\n            if (shape.isLeftHanded)\n            {\n                dstFaceUVs[0] = shape.faceuvs[ofs++];\n                for (int j = dstFaceUVs.size() - 1; j > 0; --j)\n                {\n                    dstFaceUVs[j] = shape.faceuvs[ofs++];\n                }\n            }\n            else\n            {\n                for (int j = 0; j < dstFaceUVs.size(); ++j)\n                {\n                    dstFaceUVs[j] = shape.faceuvs[ofs++];\n                }\n            }\n        }\n    }\n    return true;\n}\n\n//----------------------------------------------------------\ntemplate <>\ninline bool TopologyRefinerFactory<Shape>::assignComponentTags(\n    Far::TopologyRefiner& refiner, Shape const& shape)\n{\n    for (int i = 0; i < (int)shape.tags.size(); ++i)\n    {\n        Shape::tag const& t = shape.tags[i];\n\n        if (t.name == \"crease\")\n        {\n\n            for (int j = 0; j < (int)t.intargs.size() - 1; j += 2)\n            {\n\n                OpenSubdiv::Far::Index edge =\n                    findBaseEdge(refiner, t.intargs[j], t.intargs[j + 1]);\n                if (edge == OpenSubdiv::Far::INDEX_INVALID)\n                {\n                    printf(\"cannot find edge for crease tag (%d,%d)\\n\", t.intargs[j],\n                        t.intargs[j + 1]);\n                    return false;\n                }\n                else\n                {\n                    int nfloat = (int)t.floatargs.size();\n                    setBaseEdgeSharpness(\n                        refiner, edge,\n                        std::max(0.0f, ((nfloat > 1) ? t.floatargs[j] : t.floatargs[0])));\n                }\n            }\n        }\n        else if (t.name == \"corner\")\n        {\n\n            for (int j = 0; j < (int)t.intargs.size(); ++j)\n            {\n                int vertex = t.intargs[j];\n                if (vertex < 0 || vertex >= getNumBaseVertices(refiner))\n                {\n                    printf(\"cannot find vertex for corner tag (%d)\\n\", vertex);\n                    return false;\n                }\n                else\n                {\n                    int nfloat = (int)t.floatargs.size();\n                    setBaseVertexSharpness(\n                        refiner, vertex,\n                        std::max(0.0f, ((nfloat > 1) ? t.floatargs[j] : t.floatargs[0])));\n                }\n            }\n        }\n    }\n\n    { \n        // Hole tags\n        for (int i = 0; i < (int)shape.tags.size(); ++i)\n        {\n            Shape::tag const& t = shape.tags[i];\n            if (t.name == \"hole\")\n            {\n                for (int j = 0; j < (int)t.intargs.size(); ++j)\n                {\n                    setBaseFaceHole(refiner, t.intargs[j], true);\n                }\n            }\n        }\n    }\n    return true;\n}\n\ntemplate <>\ninline void TopologyRefinerFactory<Shape>::reportInvalidTopology(\n    TopologyRefinerFactory::TopologyError /* errCode */, char const* msg,\n    Shape const& /* shape */)\n{\n    Warning(msg);\n}\n\n} // namespace Far\n\n} // namespace OPENSUBDIV_VERSION\n} // namespace OpenSubdiv\n\n//------------------------------------------------------------------------------\n\n#endif /* FAR_UTILS_H */\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/osd_ports/tmr/nodeDescriptor.h",
    "content": "//\n//   Copyright 2016 Nvidia\n//\n//   Licensed under the Apache License, Version 2.0 (the \"Apache License\")\n//   with the following modification; you may not use this file except in\n//   compliance with the Apache License and the following modification to it:\n//   Section 6. Trademarks. is deleted and replaced with:\n//\n//   6. Trademarks. This License does not grant permission to use the trade\n//      names, trademarks, service marks, or product names of the Licensor\n//      and its affiliates, except as required to comply with Section 4(c) of\n//      the License and to reproduce the content of the NOTICE file.\n//\n//   You may obtain a copy of the Apache License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the Apache License with the above modification is\n//   distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n//   KIND, either express or implied. See the Apache License for the specific\n//   language governing permissions and limitations under the Apache License.\n//\n\n#ifndef OSD_PORTS_TMR_NODE_DESCRIPTOR_H\n#define OSD_PORTS_TMR_NODE_DESCRIPTOR_H\n\n#include \"rtxmg/subdivision/osd_ports/tmr/types.h\"\n\nstruct NodeDescriptor\n{\n\n    /// \\brief Set bitfields for REGULAR nodes\n    ///\n    ///  Field         | Bits | Content\n    ///  --------------|:----:|---------------------------------------------------\n    ///  type          | 2    | NodeType\n    ///  single crease | 1    | Whether the patch is of \"single crease\" type\n    ///  depth         | 4    | level of isolation of the patch\n    ///  boundary      | 5    | boundary edge mask encoding\n    ///  v             | 10   | log2 value of u parameter at first patch corner\n    ///  u             | 10   | log2 value of v parameter at first patch corner\n    void SetRegular(bool singleCrease, uint16_t depth, uint16_t boundary, uint16_t u, uint16_t v);\n\n    /// \\brief Set bitfields for END nodes\n    ///\n    ///  Field         | Bits | Content\n    ///  --------------|:----:|---------------------------------------------------\n    ///  type          | 2    | NodeType\n    ///  (unused)      | 1    | \n    ///  depth         | 4    | level of isolation of the patch\n    ///  boundary      | 5    | boundary edge mask encoding\n    ///  v             | 10   | log2 value of u parameter at first patch corner\n    ///  u             | 10   | log2 value of v parameter at first patch corner\n    void SetEnd(uint16_t depth, uint16_t boundary, uint16_t u, uint16_t v);\n\n    /// \\brief Set bitfields for RECURSIVE nodes\n    ///\n    ///  Field         | Bits | Content\n    ///  --------------|:----:|---------------------------------------------------\n    ///  type          | 2    | NodeType\n    ///  has end-cap   | 1    | whether the patch has an end-cap\n    ///  depth         | 4    | level of isolation of the patches\n    ///  (unused)      | 5    | \n    ///  v             | 10   | log2 value of u parameter at first patch corner\n    ///  u             | 10   | log2 value of v parameter at first patch corner\n    void SetRecursive(uint16_t depth, uint16_t u, uint16_t v, bool hasEndcap);\n\n    /// \\brief Set bitfields for TERMINAL nodes\n    ///\n    ///  Field         | Bits | Content\n    ///  --------------|:----:|---------------------------------------------------\n    ///  type          | 2    | NodeType\n    ///  has end-cap   | 1    | whether the patch has an end-cap\n    ///  depth         | 4    | level of isolation of the patches\n    ///  (unused)      | 1    |  \n    ///  evIndex       | 4    | local index of the extraordinary vertex\n    ///  v             | 10   | log2 value of u parameter at first patch corner\n    ///  u             | 10   | log2 value of v parameter at first patch corner\n    void SetTerminal(uint16_t depth, uint16_t evIndex, uint16_t u, uint16_t v, bool hasEndcap);\n\n    void Clear() { field0 = 0; }\n\n    //\n    // Generic accessors\n    //\n\n    // The following accessors decode bitfields shared by all the nodes.\n\n    NodeType GetType() { return (NodeType)unpack(field0, 2, 0); }\n\n    /// \\brief Returns the depth of the node in the tree, which corresponds to the\n    /// isolation level of a sub-patch\n    uint32_t GetDepth() { return unpack(field0, 4, 3); }\n\n    /// \\brief Returns the log2 value of the u parameter at the top left corner of\n    /// the patch\n    uint32_t GetU() { return unpack(field0, 10, 12); }\n\n    /// \\brief Returns the log2 value of the v parameter at the top left corner of\n    /// the patch\n    uint32_t GetV() { return unpack(field0, 10, 22); }\n\n    /// \\brief Returns the fraction of normalized parametric space covered by the\n    /// sub-patch.\n    float GetParamFraction(bool regularFace);\n\n    /// \\brief Maps the (u,v) parameterization from coarse to refined\n    /// The (u,v) pair is mapped from the coarse face parameterization to\n    /// the refined face parameterization\n    void MapCoarseToRefined(inout float u, inout float v, bool regularFace);\n\n    /// \\brief Maps the (u,v) parameterization from refined to coarse\n    /// The (u,v) pair is mapped from the refined face parameterization to\n    /// the coarse face parameterization\n    void MapRefinedToCoarse(inout float u, inout float v, bool regularFace);\n\n    //\n    // Type-specific accessors\n    // \n\n    // The following accessors decode bitfields that are specific to certain types\n    // of nodes only. Behavior is otherwise 'undefined': proceed with care !\n\n    /// \\brief Returns the boundary edge encoding mask (see Far::PatchParam)\n    /// (REGULAR and END node only)\n    uint32_t GetBoundaryMask() { return unpack(field0, 5, 7); }\n\n    /// \\brief Returns the number of boundary edges in the sub-patch (-1 for invalid mask)\n    /// (REGULAR and END node only)\n    uint32_t GetBoundaryCount();\n\n    /// \\brief Returns local index of the extraordinary vertex \n    /// (TERMINAL node only)\n    uint32_t GetEvIndex() { return unpack(field0, 4, 8); }\n\n    /// \\brief True if the node has a fall-back irregular patch that can be used for\n    /// dynamic isolation \n    /// (TERMINAL or RECURSIVE node only)\n    bool HasEndcap() { return unpack(field0, 1, 2) != 0; }\n\n    /// \\brief Returns true if the node has a 'sharpness' value\n    /// (REGULAR node only)\n    bool HasSharpness() { return unpack(field0, 1, 2) != 0; }\n\n    uint32_t field0;\n};\n\ninline NodeDescriptor MakeNodeDescriptor(uint32_t value)\n{\n    NodeDescriptor desc;\n    desc.field0 = value;\n    return desc;\n}\n\ninline void NodeDescriptor::SetRegular(bool singleCrease, uint16_t depth, uint16_t boundary, uint16_t u, uint16_t v)\n{\n    field0 = pack(v, 10, 22) |\n        pack(u, 10, 12) |\n        pack(boundary, 5, 7) |\n        pack(depth, 4, 3) |\n        pack(singleCrease, 1, 2) |\n        pack(uint16_t(NodeType::NODE_REGULAR), 2, 0);\n}\ninline void NodeDescriptor::SetEnd(uint16_t depth, uint16_t boundary, uint16_t u, uint16_t v)\n{\n    field0 = pack(v, 10, 22) |\n        pack(u, 10, 12) |\n        pack(boundary, 5, 7) |\n        pack(depth, 4, 3) |\n        // pack(unused, 1, 3);\n        pack(uint16_t(NodeType::NODE_END), 2, 0);\n}\ninline void NodeDescriptor::SetRecursive(uint16_t depth, uint16_t u, uint16_t v, bool hasEndcap)\n{\n    field0 = pack(v, 10, 22) |\n        pack(u, 10, 12) |\n        // pack(unused, 5, 7);\n        pack(depth, 4, 3) |\n        pack(hasEndcap, 1, 2) |\n        pack(uint16_t(NodeType::NODE_RECURSIVE), 2, 0);\n}\ninline void NodeDescriptor::SetTerminal(uint16_t depth, uint16_t evIndex, uint16_t u, uint16_t v, bool hasEndcap)\n{\n    field0 = pack(v, 10, 22) |\n        pack(u, 10, 12) |\n        pack(evIndex, 4, 8) |\n        // pack(unused, 1, 7);\n        pack(depth, 4, 3) |\n        pack(hasEndcap, 1, 2) |\n        pack(uint16_t(NodeType::NODE_TERMINAL), 2, 0);\n}\n\ninline uint32_t NodeDescriptor::GetBoundaryCount()\n{\n    return countbits(GetBoundaryMask());\n}\n\ninline float NodeDescriptor::GetParamFraction(bool regularFace)\n{\n    uint32_t depth = regularFace ? GetDepth() : GetDepth() - 1;\n    return 1.0f / float(1U << depth);\n}\n\ninline void NodeDescriptor::MapCoarseToRefined(inout float u, inout float v, bool regularFace)\n{\n    float frac = GetParamFraction(regularFace);\n    float pu = (float)GetU() * frac;\n    float pv = (float)GetV() * frac;\n    u = (u - pu) / frac;\n    v = (v - pv) / frac;\n}\n\ninline void NodeDescriptor::MapRefinedToCoarse(inout float u, inout float v, bool regularFace)\n{\n    float frac = GetParamFraction(regularFace);\n    float pu = (float)GetU() * frac;\n    float pv = (float)GetV() * frac;\n    u = u * frac + pu;\n    v = v * frac + pv;\n}\n\n#endif  // OSD_PORTS_TMR_NODE_DESCRIPTOR_H"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/osd_ports/tmr/subdivisionNode.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef OSD_PORTS_TMR_SUBDIVISION_NODE_H\n#define OSD_PORTS_TMR_SUBDIVISION_NODE_H\n\n#include \"rtxmg/subdivision/osd_ports/tmr/types.h\"\n#include \"rtxmg/subdivision/osd_ports/tmr/nodeDescriptor.h\"\n\nstruct SubdivisionNode\n{\n    StructuredBuffer<uint32_t> m_subpatchTrees;\n    StructuredBuffer<Index> m_patchPoints;\n    int m_nodeOffset;\n    int m_treeOffset;\n    int m_patchPointsOffset; // global offset m_patchPoints\n\n    static int maxIsolationLevel() { return 10; }\n\n    // patch points\n    static int catmarkRegularPatchSize() { return 16; };\n    static int catmarkTerminalPatchSize() { return 25; };\n    static int loopRegularPatchSize() { return 12; };\n\n    // node sizes (in 'ints', not bytes)\n    static int regularNodeSize(bool singleCrease) { return singleCrease ? 3 : 2; }\n    static int endCapNodeSize() { return 2; }\n    static int terminalNodeSize() { return 3; }\n    static int recursiveNodeSize() { return 6; }\n\n    static int getNumChildren(NodeType type)\n    {\n        switch (type)\n        {\n        case NodeType::NODE_TERMINAL: return 1;\n        case NodeType::NODE_RECURSIVE: return 4;\n        default: return 0;\n        }\n    }\n\n    static int rootNodeOffset() { return 14; }\n\n    // internal node offsets in tree array\n    int descriptorOffset() { return m_nodeOffset; }\n    int sharpnessOffset() { return m_nodeOffset + 2; }\n    int patchPointsOffset() { return m_nodeOffset + 1; }\n    int childOffset(int childIndex) { return m_nodeOffset + 2 + childIndex; }\n\n    float GetSharpness()\n    {\n        return asfloat(m_subpatchTrees[m_treeOffset + sharpnessOffset()]);\n    }\n\n    SubdivisionNode GetChild(int childIndex)\n    {\n        SubdivisionNode child;\n        child.m_subpatchTrees = m_subpatchTrees;\n        child.m_patchPoints = m_patchPoints;\n        child.m_nodeOffset = m_subpatchTrees[m_treeOffset + childOffset(childIndex)];\n        child.m_treeOffset = m_treeOffset;\n        child.m_patchPointsOffset = m_patchPointsOffset;\n        return child;\n    }\n\n    NodeDescriptor GetDesc()\n    {\n        return MakeNodeDescriptor(m_subpatchTrees[m_treeOffset + descriptorOffset()]);\n    }\n\n    int GetPatchPointBase()\n    {\n        return m_subpatchTrees[m_treeOffset + patchPointsOffset()];\n    }\n\n    Index GetPatchPoint(\n        int pointIndex,\n        int quadrant,\n        uint16_t maxLevel)\n    {\n        int offset = GetPatchPointBase();\n        if (offset == INDEX_INVALID)\n        {\n            return INDEX_INVALID;\n        }\n\n        NodeDescriptor desc = GetDesc();\n        switch (desc.GetType())\n        {\n        case NODE_REGULAR:\n        case NODE_END:\n            offset += pointIndex;\n            break;\n        case NODE_RECURSIVE:\n            offset = (desc.GetDepth() >= maxLevel) && desc.HasEndcap() ? offset + pointIndex : INDEX_INVALID;\n            break;\n        case NODE_TERMINAL:\n            // Unsupported, uses quadrant\n            break;\n        default:\n            break;\n        }\n        return m_patchPoints[m_patchPointsOffset + offset];\n    }\n};\n\n#endif // OSD_PORTS_TMR_SUBDIVISION_NODE_H"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/osd_ports/tmr/surfaceDescriptor.h",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#ifndef OSD_PORTS_TMR_SURFACE_DESCRIPTOR_H\n#define OSD_PORTS_TMR_SURFACE_DESCRIPTOR_H\n\n#include \"types.h\"\n\nenum class Domain : uint16_t\n{\n    Tri = 0,\n    Quad,\n    Quad_Subface,\n};\n\n///\n///  \\brief Linear Surface descriptor\n///\n/// Specialized descriptor for linearly interpolated surfaces. Linear surfaces\n/// do not require subdivision plans or other external data and can be evaluated\n/// directly from the SurfaceTable.\n/// \n/// (held by SurfaceTable)\n/// \n/// Encoding:\n/// \n///  field0        | Bits | Content\n///  --------------|:----:|---------------------------------------------------\n///  face m_size     | 16   | number of control points in the face\n///  subface index | 16   | index of the quad sub-face (or invalid index = 0xFF)\n/// \n\nstruct LinearSurfaceDescriptor\n{\n    void Set(unsigned int firstPoint, uint16_t faceSize, uint16_t quadSubface = ~uint16_t(0));\n\n    void SetNoLimit() { field0 = 0; firstControlPoint = ~uint32_t(0); };\n    bool HasLimit() { return GetFaceSize() != 0; }\n\n    uint16_t GetFaceSize() { return uint16_t(unpack(field0, 16, 0)); }\n    LocalIndex GetQuadSubfaceIndex() { return LocalIndex(unpack(field0, 16, 16)); }\n\n    Index GetPatchPoint(int pointIndex);\n    static Index GetPatchPoint(int pointInex, uint16_t faceSize, LocalIndex subfaceIndex);\n\n    static Domain getDomain(uint16_t faceSize, LocalIndex subfaceIndex);\n    Domain GetDomain() { return getDomain(GetFaceSize(), GetQuadSubfaceIndex()); }\n\n    uint32_t field0;\n    uint32_t firstControlPoint;\n};\n\ninline void LinearSurfaceDescriptor::Set(\n    unsigned int firstPoint, uint16_t faceSize, LocalIndex quadSubface)\n{\n    field0 = pack(faceSize, 16, 0) |\n        pack(quadSubface, 16, 16);\n    firstControlPoint = firstPoint;\n}\n\ninline Index LinearSurfaceDescriptor::GetPatchPoint(int pointIndex, uint16_t faceSize, LocalIndex subfaceIndex)\n{\n    if (subfaceIndex == LOCAL_INDEX_INVALID)\n    {\n        assert(pointIndex < faceSize);\n        return pointIndex;\n    }\n    else\n    {\n        assert(pointIndex < 4);\n        // patch point indices layout (N = faceSize) :\n        // [ N control points ] \n        // [ 1 face-point ] \n        // [ N edge-points ]\n        int N = faceSize;\n        switch (pointIndex)\n        {\n        case 0: return subfaceIndex;\n        case 1: return N + 1 + subfaceIndex; // edge-point after\n        case 2: return N; // central face-point\n        case 3: return N + (subfaceIndex > 0 ? subfaceIndex : N);\n        }\n    }\n    return INDEX_INVALID;\n}\n\ninline Index LinearSurfaceDescriptor::GetPatchPoint(int pointIndex)\n{\n    return GetPatchPoint(pointIndex, GetFaceSize(), GetQuadSubfaceIndex());\n}\n\ninline Domain LinearSurfaceDescriptor::getDomain(uint16_t faceSize, LocalIndex subfaceIndex)\n{\n    if (subfaceIndex == LOCAL_INDEX_INVALID)\n    {\n        if (faceSize == 4)\n        {\n            return Domain::Quad;\n        }\n        else\n        {\n            return Domain::Tri;\n        }\n    }\n    return Domain::Quad_Subface;\n}\n\n///\n///  \\brief Surface descriptor\n///\n/// Aggregates pointers into multiple sets of data that need to be assembled in\n/// order to evaluate the limit surface for the face of a mesh:\n/// \n///   - the indices of the 1-ring set of control points around the face\n///   - a pointer to the SubdivisionPlan with all the topological information\n///     (composed of an index to a TopologyMap, and the index of the Plan itself\n///     within that map)\n///   - a subset of flags affecting the evaluation of the surface.\n/// \n/// (held by SurfaceTable)\n/// \n/// Encoding:\n/// \n///  field0              | Bits | Content\n///  --------------------|:----:|---------------------------------------------------\n///  has limit           | 1    | limit surface cannot be evaluated if false (implies\n///                      |      | other fields are expected to be set to 0)\n///  param rotation      | 2    | parametric rotation of the subdivision plan\n///  edges adjacency     | 4    | per-edge bits set: true if one or more surfaces\n///                      |      | adjacent to that edge are irregular (the edge is a\n///                      |      | T-junction) ; always false if the surface is irregular\n///  topology map        | 5    | index of topology map (optional)\n///  plan index          | 20   | index of the plan within the topology map selected\n///  \n\nstruct SurfaceDescriptor\n{\n    static const uint32_t kMaxMapIndex = (1 << 5) - 1;\n    static const uint32_t kMaxPlanIndex = (1 << 20) - 1;\n\n    void SetNoLimit() { field0 = 0; firstControlPoint = ~uint32_t(0); };\n    bool HasLimit() { return unpack(field0, 1, 0); }\n\n    void Set(unsigned int firstPoint, unsigned int planIndex, uint16_t rotation, uint16_t adjacency, unsigned int mapIndex = 0);\n\n    uint16_t GetParametricRotation() { return (uint16_t)unpack(field0, 2, 1); }\n\n    uint16_t GetEdgeAdjacencyBits() { return (uint16_t)unpack(field0, 4, 3); }\n    bool GetEdgeAdjacencyBit(uint16_t edgeIndex) { uint16_t edgebits = (uint16_t)unpack(field0, 4, 3); return (edgebits >> edgeIndex) & 0x1; }\n\n    unsigned int GetTopologyMapIndex() { return unpack(field0, 5, 7); }\n    unsigned int GetSubdivisionPlanIndex() { return unpack(field0, 20, 12); }\n\n    uint32_t field0;\n    uint32_t firstControlPoint;\n};\n\ninline void SurfaceDescriptor::Set(\n    unsigned int firstPoint, unsigned int planIndex, uint16_t rotation, uint16_t adjacency, unsigned int mapIndex)\n{\n    assert(planIndex < kMaxPlanIndex && rotation < 4 && mapIndex < kMaxMapIndex);\n\n    field0 = pack(true, 1, 0) |\n        pack(rotation, 2, 1) |\n        pack(adjacency, 4, 3) |\n        pack(mapIndex, 5, 7) |\n        pack(planIndex, 20, 12);\n\n    firstControlPoint = firstPoint;\n}\n\n#endif /* OSD_PORTS_TMR_SURFACE_DESCRIPTOR_H */"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/osd_ports/tmr/treeDescriptor.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef OSD_PORTS_TMR_TREE_DESCRIPTOR_H\n#define OSD_PORTS_TMR_TREE_DESCRIPTOR_H\n\n#include \"rtxmg/subdivision/osd_ports/tmr/types.h\"\n\nstruct TreeDescriptorHLSL\n{\n    StructuredBuffer<uint32_t> m_subpatchTrees;\n    uint32_t m_treeOffset;\n\n    static uint32_t const NumPatchPointsOffset = 2;\n\n    bool IsRegularFace()\n    {\n        return unpack(m_subpatchTrees[m_treeOffset], 1, 0) != 0;\n    }\n\n    uint32_t GetFaceSize()\n    {\n        return unpack(m_subpatchTrees[m_treeOffset], 16, 16);\n    }\n\n    uint32_t GetSubfaceIndex()\n    {\n        return unpack(m_subpatchTrees[m_treeOffset], 16, 0);\n    }\n\n    uint32_t GetNumControlPoints()\n    {\n        return unpack(m_subpatchTrees[m_treeOffset], 16, 16);\n    }\n\n    uint32_t GetNumPatchPoints(uint16_t level)\n    {\n        return m_subpatchTrees[m_treeOffset + NumPatchPointsOffset + level];\n    }\n};\n\n#endif // OSD_PORTS_TMR_TREE_DESCRIPTOR_H"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/osd_ports/tmr/types.h",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#ifndef OSD_PORTS_TMR_TYPES_H\n#define OSD_PORTS_TMR_TYPES_H\n\n#ifdef __cplusplus\n// Calm visual studio down about \"missing types\" when trying to highlight HLSL files\n#include <cstdint>\nusing std::uint16_t;\nusing std::uint32_t;\nusing std::uint64_t;\n#else\n\n\ninline float quantize(float a, uint32_t nbits)\n{\n    nbits = 32 - nbits;\n    int mask = (1u << (32 - nbits)) - 1;\n    return asfloat(asint(a) & ~mask);\n}\n\ninline float3 quantize(float3 v, uint32_t nbits)\n{\n    return float3(quantize(v.x, nbits), quantize(v.y, nbits), quantize(v.z, nbits));\n}\n\n#define assert(x)\n\n#endif\n\ntypedef int       Index;\ntypedef uint16_t  LocalIndex;\n\nstatic const LocalIndex LOCAL_INDEX_INVALID = ~LocalIndex(0);\nstatic const Index INDEX_INVALID = -1;\n\nstatic const uint32_t kMaxIsolationLevel = 10;\n\ninline uint32_t pack(uint32_t value, uint32_t width, uint32_t offset)\n{\n    return (uint32_t)((value & ((1U << width) - 1)) << offset);\n}\n\ninline uint32_t unpack(uint32_t value, uint32_t width, uint32_t offset)\n{\n    return (uint32_t)((value >> offset) & ((1U << width) - 1));\n}\n\n\nenum SchemeType\n{\n    SCHEME_BILINEAR = 0,\n    SCHEME_CATMARK,\n    SCHEME_LOOP\n};\n\nenum EndCapType\n{\n    ENDCAP_NONE = 0,             ///< no endcap\n    ENDCAP_BILINEAR_BASIS,       ///< use bilinear quads (4 cp) as end-caps\n    ENDCAP_BSPLINE_BASIS,        ///< use BSpline basis patches (16 cp) as end-caps\n    ENDCAP_GREGORY_BASIS,        ///< use Gregory basis patches (20 cp) as end-caps\n};\n\nenum NodeType\n{\n    NODE_REGULAR = 0,\n    NODE_RECURSIVE = 1,\n    NODE_TERMINAL = 2,\n    NODE_END = 3,\n};\n\nenum PatchDescriptorType\n{\n    NON_PATCH = 0,     ///< undefined\n\n    POINTS,            ///< points (useful for cage drawing)\n    LINES,             ///< lines  (useful for cage drawing)\n\n    QUADS,             ///< 4-sided quadrilateral (bilinear)\n    TRIANGLES,         ///< 3-sided triangle\n\n    LOOP,              ///< regular triangular patch for the Loop scheme\n\n    REGULAR,           ///< regular B-Spline patch for the Catmark scheme\n    GREGORY,\n    GREGORY_BOUNDARY,\n    GREGORY_BASIS,\n    GREGORY_TRIANGLE\n};\n\n\n#endif // OSD_PORTS_TMR_TYPES_H"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/patch_param.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n/// \\brief Patch parameterization\n///\n/// Topological refinement splits coarse mesh faces into refined faces.\n///\n/// This patch parameterzation describes the relationship between one\n/// of these refined faces and its corresponding coarse face. It is used\n/// both for refined faces that are represented as full limit surface\n/// parametric patches as well as for refined faces represented as simple\n/// triangles or quads. This parameterization is needed to interpolate\n/// primvar data across a refined face.\n///\n/// The U,V and refinement level parameters describe the scale and offset\n/// needed to map a location on the patch between levels of refinement.\n/// The encoding of these values exploits the quad-tree organization of\n/// the faces produced by subdivision. We encode the U,V origin of the\n/// patch using two 10-bit integer values and the refinement level as\n/// a 4-bit integer. This is sufficient to represent up through 10 levels\n/// of refinement.\n///\n/// Special consideration must be given to the refined faces resulting from\n/// irregular coarse faces. We adopt a convention similar to Ptex texture\n/// mapping and define the parameterization for these faces in terms of the\n/// regular faces resulting from the first topological splitting of the\n/// irregular coarse face.\n///\n/// When computing the basis functions needed to evaluate the limit surface\n/// parametric patch representing a refined face, we also need to know which\n/// edges of the patch are interpolated boundaries. These edges are encoded\n/// as a boundary bitmask identifying the boundary edges of the patch in\n/// sequential order starting from the first vertex of the refined face.\n///\n/// A sparse topological refinement (like feature adaptive refinement) can\n/// produce refined faces that are adjacent to faces at the next level of\n/// subdivision. We identify these transitional edges with a transition\n/// bitmask using the same encoding as the boundary bitmask.\n///\n/// For triangular subdivision schemes we specify the parameterization using\n/// a similar method. Alternate triangles at a given level of refinement\n/// are parameterized from their opposite corners and encoded as occupying\n/// the opposite diagonal of the quad-tree hierarchy. The third barycentric\n/// coordinate is dependent on and can be derived from the other two\n/// coordinates. This encoding also takes inspiration from the Ptex\n/// texture mapping specification.\n///\n/// Bitfield layout :\n///\n///  Field0     | Bits | Content\n///  -----------|:----:|------------------------------------------------------\n///  faceId     | 28   | the faceId of the patch\n///  transition | 4    | transition edge mask encoding\n///\n///  Field1     | Bits | Content\n///  -----------|:----:|------------------------------------------------------\n///  level      | 4    | the subdivision level of the patch\n///  nonquad    | 1    | whether patch is refined from a non-quad face\n///  regular    | 1    | whether patch is regular\n///  unused     | 1    | unused\n///  boundary   | 5    | boundary edge mask encoding\n///  v          | 10   | log2 value of u parameter at first patch corner\n///  u          | 10   | log2 value of v parameter at first patch corner\n///\n/// Note : the bitfield is not expanded in the struct due to differences in how\n///        GPU & CPU compilers pack bit-fields and endian-ness.\n///\n/*!\n    \\verbatim\n    Quad Patch Parameterization\n\n    (0,1)                           (1,1)\n    +-------+-------+---------------+\n    |       |       |               |\n    |   L2  |   L2  |               |\n    |0,3    |1,3    |               |\n    +-------+-------+       L1      |\n    |       |       |               |\n    |   L2  |   L2  |               |\n    |0,2    |1,2    |1,1            |\n    +-------+-------+---------------+\n    |               |               |\n    |               |               |\n    |               |               |\n    |       L1      |       L1      |\n    |               |               |\n    |               |               |\n    |0,0            |1,0            |\n    +---------------+---------------+\n    (0,0)                           (1,0)\n    \\endverbatim\n*/\n/*!\n    \\verbatim\n    Triangle Patch Parameterization\n\n    (0,1)                           (1,1)  (0,1,0)\n    +-------+-------+---------------+       +\n    | \\     | \\     | \\             |       | \\\n    |L2 \\   |L2 \\   |   \\           |       |   \\\n    |0,3  \\ |1,3  \\ |     \\         |       | L2  \\\n    +-------+-------+       \\       |       +-------+\n    | \\     | \\     |   L1    \\     |       | \\  L2 | \\\n    |L2 \\   |L2 \\   |           \\   |       |   \\   |   \\\n    |0,2  \\ |1,2  \\ |1,1          \\ |       | L2  \\ | L2  \\\n    +-------+-------+---------------+       +-------+-------+\n    | \\             | \\             |       | \\             | \\\n    |   \\           |   \\           |       |   \\           |   \\\n    |     \\         |     \\         |       |     \\    L1   |     \\\n    |       \\       |       \\       |       |       \\       |       \\\n    |   L1    \\     |   L1    \\     |       |   L1    \\     |   L1    \\\n    |           \\   |           \\   |       |           \\   |           \\\n    |0,0          \\ |1,0          \\ |       |             \\ |             \\\n    +---------------+---------------+       +---------------+---------------+\n    (0,0)                           (1,0)  (0,0,1)                         (1,0,0)\n    \\endverbatim\n*/\n\n#include \"rtxmg/subdivision/osd_ports/tmr/types.h\"\n\nstruct PatchParam\n{\n    /// \\brief Sets the values of the bit fields\n    ///\n    /// @param faceid face index\n    ///\n    /// @param u value of the u parameter for the first corner of the face\n    /// @param v value of the v parameter for the first corner of the face\n    ///\n    /// @param depth subdivision level of the patch\n    /// @param nonquad true if the root face is not a quad\n    ///\n    /// @param boundary 5-bits identifying boundary edges (and verts for tris)\n    /// @param transition 4-bits identifying transition edges\n    ///\n    /// @param regular whether the patch is regular\n    ///\n    void Set(Index faceid, uint16_t u, uint16_t v,\n        uint16_t depth, bool nonquad,\n        uint16_t boundary, uint16_t transition,\n        bool regular = false);\n\n    /// \\brief Resets everything to 0\n    void Clear() { field0 = field1 = 0; }\n\n    /// \\brief Returns the faceid\n    Index GetFaceId() { return Index(unpack(field0, 28, 0)); }\n\n    /// \\brief Returns the log2 value of the u parameter at\n    /// the first corner of the patch\n    uint16_t GetU() { return (uint16_t)unpack(field1, 10, 22); }\n\n    /// \\brief Returns the log2 value of the v parameter at\n    /// the first corner of the patch\n    uint16_t GetV() { return (uint16_t)unpack(field1, 10, 12); }\n\n    /// \\brief Returns the transition edge encoding for the patch.\n    uint16_t GetTransition() { return (uint16_t)unpack(field0, 4, 28); }\n\n    /// \\brief Returns the boundary edge encoding for the patch.\n    uint16_t GetBoundary() { return (uint16_t)unpack(field1, 5, 7); }\n\n    /// \\brief True if the parent base face is a non-quad\n    bool NonQuadRoot() { return (unpack(field1, 1, 4) != 0); }\n\n    /// \\brief Returns the level of subdivision of the patch\n    uint16_t GetDepth() { return (uint16_t)unpack(field1, 4, 0); }\n\n    /// \\brief Returns the fraction of unit parametric space covered by this face.\n    float GetParamFraction();\n\n    /// \\brief A (u,v) pair in the fraction of parametric space covered by this\n    /// face is mapped into a normalized parametric space.\n    ///\n    /// @param u  u parameter\n    /// @param v  v parameter\n    ///\n    void Normalize(inout float u, inout float v);\n    void NormalizeTriangle(inout float u, inout float v);\n\n    /// \\brief A (u,v) pair in a normalized parametric space is mapped back into the\n    /// fraction of parametric space covered by this face.\n    ///\n    /// @param u  u parameter\n    /// @param v  v parameter\n    ///\n    void Unnormalize(inout float u, inout float v);\n    void UnnormalizeTriangle(inout float u, inout float v);\n\n    /// \\brief Returns if a triangular patch is parametrically rotated 180 degrees\n    bool IsTriangleRotated();\n\n    /// \\brief Returns whether the patch is regular\n    bool IsRegular() { return (unpack(field1, 1, 5) != 0); }\n\n    unsigned int field0 : 32;\n    unsigned int field1 : 32;\n\n    unsigned int pack(unsigned int value, int width, int offset)\n    {\n        return (unsigned int)((value & ((1U << width) - 1)) << offset);\n    }\n\n    unsigned int unpack(unsigned int value, int width, int offset)\n    {\n        return (unsigned int)((value >> offset) & ((1U << width) - 1));\n    }\n};\n\nvoid PatchParam::Set(Index faceid, uint16_t u, uint16_t v,\n    uint16_t depth, bool nonquad,\n    uint16_t boundary, uint16_t transition,\n    bool regular)\n{\n    field0 = pack(faceid, 28, 0) |\n        pack(transition, 4, 28);\n\n    field1 = pack(u, 10, 22) |\n        pack(v, 10, 12) |\n        pack(boundary, 5, 7) |\n        pack(regular, 1, 5) |\n        pack(nonquad, 1, 4) |\n        pack(depth, 4, 0);\n}\n\nfloat PatchParam::GetParamFraction()\n{\n    return 1.0f / (float)(1U << (GetDepth() - NonQuadRoot()));\n}\n\nvoid PatchParam::Normalize(inout float u, inout float v)\n{\n\n    float fracInv = (float)(1.0f / GetParamFraction());\n\n    u = u * fracInv - (float)GetU();\n    v = v * fracInv - (float)GetV();\n}\n\nvoid PatchParam::Unnormalize(inout float u, inout float v)\n{\n    float frac = (float)GetParamFraction();\n\n    u = (u + (float)GetU()) * frac;\n    v = (v + (float)GetV()) * frac;\n}\n\nbool PatchParam::IsTriangleRotated()\n{\n    return (GetU() + GetV()) >= (1U << GetDepth());\n}\n\nvoid PatchParam::NormalizeTriangle(inout float u, inout float v)\n{\n    if (IsTriangleRotated())\n    {\n        float fracInv = (float)(1.0f / GetParamFraction());\n\n        int depthFactor = 1U << GetDepth();\n        u = (float)(depthFactor - GetU()) - (u * fracInv);\n        v = (float)(depthFactor - GetV()) - (v * fracInv);\n    }\n    else\n    {\n        Normalize(u, v);\n    }\n}\n\nvoid PatchParam::UnnormalizeTriangle(inout float u, inout float v)\n{\n\n    if (IsTriangleRotated())\n    {\n        float frac = GetParamFraction();\n\n        int depthFactor = 1U << GetDepth();\n        u = ((float)(depthFactor - GetU()) - u) * frac;\n        v = ((float)(depthFactor - GetV()) - v) * frac;\n    }\n    else\n    {\n        Unnormalize(u, v);\n    }\n}\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/segmented_vector.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n// clang-format off\n\n#include <vector>\n\n// clang-format off\n\ntemplate <typename T>\nstruct segmented_vector\n{\n    std::vector<T>        elements;      // element vector\n    std::vector<uint32_t> offsets{ 0 };  // offset vector first element 0\n    std::vector<uint32_t> sizes;         // segment sizes\n\n    template <typename U>  // a container\n    void Append(const U& segment)\n    {\n        sizes.push_back(static_cast<uint32_t>(segment.size()));\n        offsets.push_back(offsets.back() + sizes.back());\n        elements.insert(elements.end(), segment.begin(), segment.end());\n    }\n\n    void Append(const T* a_elements, uint32_t n_elements)\n    {\n        sizes.push_back(n_elements);\n        offsets.push_back(offsets.back() + sizes.back());\n        elements.insert(elements.end(), &a_elements[0], &a_elements[n_elements]);\n    }\n\n    void Reserve(size_t n)\n    {\n        offsets.reserve(n);\n        sizes.reserve(n);\n    }\n\n    T* Data() { return elements.data(); }\n    size_t Size() { return elements.size(); }\n};\n\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/shape.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <donut/core/math/math.h>\n\n#include <chrono>\n#include <filesystem>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include <opensubdiv/far/types.h>\n\nenum Scheme { kBilinear = 0, kCatmark, kLoop };\n\nstruct Shape\n{\n    // Note: increment the version to trigger cache rebuild.\n    static constexpr std::chrono::system_clock::duration version{ 6 };\n\n    // full(er) spec here: http://paulbourke.net/dataformats/mtl/\n    // and here: https://en.wikipedia.org/wiki/Wavefront_.obj_file\n    struct material\n    {\n        std::string name;\n\n        float ka[3] = { 0.f, 0.f, 0.f }; // ambient\n        float kd[3] = { .8f, .8f, .8f }; // diffuse\n        float ks[3] = { 0.f, 0.f, 0.f }; // specular\n        float ke[3] = { 0.f, 0.f, 0.f }; // emissive\n        float ns = 0.f;                // specular exponent\n        float ni = 0.f;        // optical density (1.0=no refraction, glass=1.5)\n        float sharpness = 0.f; // reflection sharpness\n        float tf[3] = { 0.f, 0.f, 0.f }; // transmission filter\n        float d = 0.f;                 // dissolve factor (1.0 = opaque)\n        int illum = 4;                 // illumination model\n        float bm = 1.f;                // bump multipler\n        float bb = 0.f;                // bump bias\n\n        std::string map_ka;   // ambient\n        std::string map_kd;   // diffuse\n        std::string map_ks;   // specular\n        std::string map_bump; // bump\n\n        // MTL extensions (exported by Blender & others)\n        float Pr = 0.8f;     // roughness\n        float Pm = 0.f;     // metallic\n        float Ps = 0.f;     // sheen\n        float Pc = 0.f;     // clearcoat thickness\n        float Pcr = 0.f;    // clearcoat roughness\n        float Ke = 0.f;     // emissive\n        float aniso = 0.f;  // anisotropy\n        float anisor = 0.f; // anisotropy rotation\n\n        std::string map_ke;  // emissive\n        std::string map_pr;  // roughness\n        std::string map_pm;   // metalness\n        std::string map_rma; // roughness / metalness / ambient occlusion\n        std::string map_orm; // alt version of rma\n\n        unsigned int udim = 0;\n    };\n\n    int FindMaterial(char const* name);\n\n    std::string mtllib;\n    std::vector<unsigned short> mtlbind;\n\n    // contiguous set of face that share the same material\n    struct Subshape\n    {\n        size_t startFaceIndex = 0;\n        int mtlBind = -1;\n    };\n\n    std::vector<Subshape> subshapes;\n    std::vector<uint16_t> faceToSubshapeIndex;\n    std::vector<std::unique_ptr<material>> mtls;\n\n    struct tag\n    {\n        std::string name;\n        std::vector<int> intargs;\n        std::vector<float> floatargs;\n        std::vector<std::string> stringargs;\n\n        static bool ParseTag(char const* stream, tag* t);\n        std::string GenTag() const;\n    };\n    std::vector<tag> tags;\n\n    // read from/write to cache\n    void WriteShape(const std::string& objFile) const;\n    bool ReadShape(const std::string& objFile);\n\n    uint32_t GetNumVertices() const { return (int)verts.size(); }\n    uint32_t GetNumFaces() const { return (int)nvertsPerFace.size(); }\n    int GetFVarWidth() const { return HasUV() ? 2 : 0; }\n    bool HasUV() const { return !(uvs.empty() || faceuvs.empty()); }\n\n    std::filesystem::path filepath;\n\n    std::vector<int> nvertsPerFace;\n    std::vector<int> faceverts;\n    std::vector<int> faceuvs;\n    std::vector<int> facenormals;\n\n    Scheme scheme = kCatmark;\n\n    bool isLeftHanded = false;\n\n    // Vertex attributes\n    std::vector<donut::math::float3> verts;\n    std::vector<donut::math::float2> uvs;\n    std::vector<donut::math::float3> normals; // unused for subd's\n\n    donut::math::box3 aabb;\n\n    // catmark cube shape\n    static std::unique_ptr<Shape> DefaultShape();\n\n    // load a single obj file\n    static std::unique_ptr<Shape>\n        LoadObjFile(const std::filesystem::path& pathStr, bool parseMaterials = true, bool requireUVs = true);\n\n    std::string                   capslib;\n// TODO: Capsules\n//     CapsuleCache                  capsules;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/subdivision_eval.hlsli",
    "content": "/*\n* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n*\n* Permission is hereby granted, free of charge, to any person obtaining a\n* copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation\n* the rights to use, copy, modify, merge, publish, distribute, sublicense,\n* and/or sell copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in\n* all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n* DEALINGS IN THE SOFTWARE.\n*/\n\n#ifndef SUBDIVISION_EVAL_HLSLI // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define SUBDIVISION_EVAL_HLSLI\n\n#include \"rtxmg/subdivision/osd_ports/tmr/surfaceDescriptor.h\"\n#include \"rtxmg/subdivision/subdivision_plan_hlsl.h\"\n#include \"rtxmg/subdivision/vertex.h\"\n\n#ifndef SHADER_DEBUG\n#define SHADER_DEBUG(x) \n#endif\n\n// SURFACE_TYPE\n#define SURFACE_TYPE_PUREBSPLINE 0\n#define SURFACE_TYPE_REGULARBSPLINE 1\n#define SURFACE_TYPE_LIMIT 2\n#define SURFACE_TYPE_ALL 3\n\n#ifndef SURFACE_TYPE\n#define SURFACE_TYPE SURFACE_TYPE_ALL\n#endif\n\n#ifdef PATCH_POINTS_WRITEABLE // defined in the shader\n#define VERTEX_PATCH_POINTS_TYPE RWStructuredBuffer<float3>\n#define TEXCOORD_PATCH_POINTS_TYPE RWStructuredBuffer<float2>\n#else\n#define VERTEX_PATCH_POINTS_TYPE StructuredBuffer<float3>\n#define TEXCOORD_PATCH_POINTS_TYPE StructuredBuffer<float2>\n#endif\n\nstatic const Index kPureBSplinePatchPointIndices[kPatchSize] = { 6, 7, 8, 9, 5, 0, 1, 10, 4, 3, 2, 11, 15, 14, 13, 12 };\n    \nstatic const uint32_t kNumWaveSurfaceUVSamples = 8;\nstatic const float2 kWaveSurfaceUVSamples[kNumWaveSurfaceUVSamples] =\n{\n    { 0, 0 },\n    { 0.5, 0 },\n    { 1, 0 },\n    { 1, 0.5 },\n    { 1, 1 },\n    { 0.5, 1 },\n    { 0, 1 },\n    { 0, 0.5 }\n};\n\n// A small tolerance for the *sine of the angle* squared\nstatic const float kParallelEpsilonRadians = 0.001745f; // 0.1 degrees\n\nstatic const float kParallelCosSquared = (1.0f - kParallelEpsilonRadians * kParallelEpsilonRadians);\n\nbool IsParallel(float3 t0, float3 t1)\n{\n    // cos(theta) = dot(t0, t1) / (|t0| * |t1|)\n    // cos(theta)^2 = dot(t0, t1)^2 / (|t0|^2 * |t1|^2)\n    // cos(theta)^2 > kParallelCosSquared\n    // dot(t0, t1)^2 > kParallelCosSquared * |t0|^2 * |t1|^2\n\n    float dotTangents = dot(t0, t1);\n    return (dotTangents * dotTangents) > kParallelCosSquared * dot(t0, t0) * dot(t1, t1);\n}\n\nstruct SubdivisionEvaluatorHLSL\n{\n    uint32_t m_surfaceIndex;\n    uint16_t m_isolationLevel;\n    \n    StructuredBuffer<SurfaceDescriptor> m_surfaceDescriptors;\n    \n    // SubdivisionPlanContext\n    StructuredBuffer<SubdivisionPlanHLSL> m_plans;\n    StructuredBuffer<uint32_t> m_subpatchTrees;\n    StructuredBuffer<Index> m_vertexPatchPointIndices;\n    StructuredBuffer<float> m_stencilMatrix;\n    \n    // Subdivision Surface data\n    StructuredBuffer<Index> m_vertexControlPointIndices;\n    StructuredBuffer<float3> m_vertexControlPoints; // position data for control points\n    StructuredBuffer<uint32_t> m_vertexPatchPointsOffsets; // offsets into m_vertexPatchPoints\n    VERTEX_PATCH_POINTS_TYPE m_vertexPatchPoints; // position data for patch level, computed from stencil matrix and control points\n    \n    SurfaceDescriptor GetSurfaceDesc()\n    {\n        return m_surfaceDescriptors[m_surfaceIndex];\n    }\n    \n    SubdivisionPlanContext GetPlan()\n    {\n        SubdivisionPlanContext context = (SubdivisionPlanContext)0;\n        context.m_data = m_plans[GetSurfaceDesc().GetSubdivisionPlanIndex()];\n        context.m_subpatchTrees = m_subpatchTrees;\n        context.m_patchPoints = m_vertexPatchPointIndices;\n        context.m_stencilMatrix = m_stencilMatrix;\n        return context;\n    }\n    \n    bool IsPureBSplinePatch()\n    {\n        return GetSurfaceDesc().GetSubdivisionPlanIndex() == 0;\n    }\n\n    bool IsBSplinePatch()\n    {\n        return GetPlan().IsBSplinePatch(m_isolationLevel);\n    }\n    \n    \n#if defined(GROUP_SHARED_CONTROL_POINTS)\n    void PrefetchPatchControlPoints(uint32_t iWave, uint32_t iLane)\n    {\n        SurfaceDescriptor desc = GetSurfaceDesc();\n        \n        if (iLane < kPatchSize)\n        {\n            Index patchPointIndex = kPureBSplinePatchPointIndices[iLane];\n            Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n            GROUP_SHARED_CONTROL_POINTS(iWave, iLane) = m_vertexControlPoints[cpi];\n        }\n    }\n    \n    LimitFrame EvaluatePureBsplinePatchGroupShared(uint32_t iWave, float2 uv)\n    {\n        float aPtWeights[kPatchSize];\n        float aDuWeights[kPatchSize];\n        float aDvWeights[kPatchSize];\n\n        EvalBasisBSpline(uv, aPtWeights, aDuWeights, aDvWeights,\n                                0, // boundary mask\n                                0.0f, // sharpness\n                                true // pure bspline\n        );\n        \n        LimitFrame limit;\n        limit.Clear();\n        \n        for (int iWeight = 0; iWeight < kPatchSize; ++iWeight)\n        {\n            limit.AddWithWeight(GROUP_SHARED_CONTROL_POINTS(iWave, iWeight), aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n        }\n        \n        return limit;\n    }\n#endif\n    \n    LimitFrame EvaluatePureBsplinePatch(float2 uv)\n    {\n        float aPtWeights[kPatchSize];\n        float aDuWeights[kPatchSize];\n        float aDvWeights[kPatchSize];\n\n        EvalBasisBSpline(uv, aPtWeights, aDuWeights, aDvWeights,\n                                0, // boundary mask\n                                0.0f, // sharpness\n                                true // pure bspline\n        );\n\n        SurfaceDescriptor desc = GetSurfaceDesc();\n        \n        LimitFrame limit;\n        limit.Clear();\n        \n        for (int iWeight = 0; iWeight < kPatchSize; ++iWeight)\n        {\n            Index patchPointIndex = kPureBSplinePatchPointIndices[iWeight];\n            Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n            limit.AddWithWeight(m_vertexControlPoints[cpi], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n        }\n        \n        return limit;\n    }\n\n    LimitFrame EvaluateBsplinePatch(float2 uv)\n    {\n        float aPtWeights[kPatchSize];\n        float aDuWeights[kPatchSize];\n        float aDvWeights[kPatchSize];\n\n        uint16_t quadrant = 0;\n    \n        SubdivisionPlanContext plan = GetPlan();\n        \n        SubdivisionNode node = plan.EvaluateBasis(uv, aPtWeights, aDuWeights, aDvWeights, quadrant, m_isolationLevel);\n\n        SurfaceDescriptor desc = GetSurfaceDesc();\n\n        LimitFrame limit;\n        limit.Clear();\n\n        for (int iWeight = 0; iWeight < kPatchSize; ++iWeight)\n        {\n            Index patchPointIndex = node.GetPatchPoint(iWeight, quadrant, m_isolationLevel);\n            Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n            limit.AddWithWeight(m_vertexControlPoints[cpi], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n        }\n        \n        return limit;\n    }\n    \n    LimitFrame EvaluateLimitSurface(float2 uv)\n    {\n        uint16_t quadrant = 0;\n        float aPtWeights[kPatchSize];\n        float aDuWeights[kPatchSize];\n        float aDvWeights[kPatchSize];\n\n        SubdivisionPlanContext plan = GetPlan();\n        SubdivisionNode node = plan.EvaluateBasis(uv, aPtWeights, aDuWeights, aDvWeights, quadrant, m_isolationLevel);\n\n        const uint16_t numControlPoints = plan.m_data.numControlPoints;\n        \n        LimitFrame limit;\n        limit.Clear();\n\n        for (int iWeight = 0; iWeight < kPatchSize; ++iWeight)\n        {\n            Index patchPointIndex = node.GetPatchPoint(iWeight, quadrant, m_isolationLevel);\n            int index = patchPointIndex - numControlPoints;\n            uint32_t supportOffset = m_vertexPatchPointsOffsets[m_surfaceIndex];\n            limit.AddWithWeight(m_vertexPatchPoints[supportOffset + index], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n        }\n        \n        return limit;\n    }\n    \n    LimitFrame Evaluate(float2 uv)\n    {\n        if (IsPureBSplinePatch())\n            return EvaluatePureBsplinePatch(uv);\n        else if (IsBSplinePatch())\n            return EvaluateBsplinePatch(uv);\n        else\n            return EvaluateLimitSurface(uv);\n    }\n    \n    float3 GetPureBsplinePatchPoint(uint32_t i, uint32_t j)\n    {\n        int iWeight = 4 * j + i;\n        Index patchPointIndex = kPureBSplinePatchPointIndices[iWeight];\n\n        SurfaceDescriptor desc = GetSurfaceDesc();\n        Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n        return m_vertexControlPoints[cpi];\n    }\n    \n    // Wave-parallel pure b-spline patch evaluator for up to 8 sample locations.\n    //\n    // Inputs:\n    //    uvs - a span of up to 8 sample locations\n    //    subd - subd object\n    //\n    // Returns:\n    //    limits - a span of LimitFrames with enough space to store the resulting limits (same a uvs.m_size()).\n    //\n    LimitFrame WaveEvaluatePureBsplinePatch8(uint32_t iLane)\n    {\n        float2 uv = kWaveSurfaceUVSamples[iLane % kNumWaveSurfaceUVSamples];\n        float sWeights = CubicBSplineWeight(uv.x, iLane / kNumWaveSurfaceUVSamples); // weights for s-direction in wave w_s_0, ..., w_s_0, w_s_1, ..., w_s_1, ... w_s_3, ... w_s_3\n        float dsWeights = CubicBSplineDerivativeWeight(uv.x, iLane / kNumWaveSurfaceUVSamples); // weights for s_direction derivative, wave layout as for sWeights\n\n        LimitFrame limit;\n        limit.Clear();\n        for (uint32_t j = 0; j < 4; ++j)\n        {\n            float tWeights = CubicBSplineWeight(uv.y, j);\n            float dtWeights = CubicBSplineDerivativeWeight(uv.y, j);\n            float3 vtx = GetPureBsplinePatchPoint(iLane / kNumWaveSurfaceUVSamples, j);\n\n            limit.p += sWeights * tWeights * vtx; // point\n            limit.deriv1 += dsWeights * tWeights * vtx; // deriv1\n            limit.deriv2 += sWeights * dtWeights * vtx; // deriv2\n        }\n\n        limit.p += WaveReadLaneAt(limit.p, WaveGetLaneIndex() + 8);\n        limit.p += WaveReadLaneAt(limit.p, WaveGetLaneIndex() + 16);\n        limit.deriv1 += WaveReadLaneAt(limit.deriv1, WaveGetLaneIndex() + 8);\n        limit.deriv1 += WaveReadLaneAt(limit.deriv1, WaveGetLaneIndex() + 16);\n        limit.deriv2 += WaveReadLaneAt(limit.deriv2, WaveGetLaneIndex() + 8);\n        limit.deriv2 += WaveReadLaneAt(limit.deriv2, WaveGetLaneIndex() + 16);\n        \n        return limit;\n    }\n\n    LimitFrame WaveEvaluateBsplinePatch(uint32_t iLane)\n    {\n        const float2 st = kWaveSurfaceUVSamples[iLane % kNumWaveSurfaceUVSamples];\n\n        LimitFrame limit;\n        limit.Clear();\n        SubdivisionPlanContext plan = GetPlan();\n        SubdivisionNode rootNode = plan.GetRootNode();\n\n        NodeDescriptor nodeDesc = rootNode.GetDesc();\n        float sharpness = nodeDesc.HasSharpness() ? rootNode.GetSharpness() : 0.f;\n\n        PatchParam param;\n        param.Set(INDEX_INVALID, (uint16_t)nodeDesc.GetU(), (uint16_t)nodeDesc.GetV(), 0, false, (uint16_t)nodeDesc.GetBoundaryMask(), 0, true);\n        const int boundaryMask = param.GetBoundary();\n\n        float sWeights[4], tWeights[4], dsWeights[4], dtWeights[4], unused[4];\n        EvalBSplineCurve(st.x, sWeights, dsWeights, unused, true, false);\n        EvalBSplineCurve(st.y, tWeights, dtWeights, unused, true, false);\n\n        if (boundaryMask)\n        {\n            if (sharpness > 0.0f)\n                AdjustCreases(st, sWeights, tWeights, dsWeights, dtWeights, boundaryMask, sharpness);\n            else\n                AdjustBoundaries(st, sWeights, tWeights, dsWeights, dtWeights, boundaryMask);\n        }\n\n        SurfaceDescriptor desc = GetSurfaceDesc();\n\n        const int iPatchPtBase = rootNode.GetPatchPointBase();\n\n        const uint32_t i = iLane / 8;\n        float w_t_i = tWeights[i];\n        float w_dt_i = dtWeights[i];\n\n        for (int j = 0; j < 4; ++j)\n        {\n            const int iWeight = 4 * i + j;\n            Index patchPointIndex = m_vertexPatchPointIndices[plan.m_data.patchPointsOffset + iPatchPtBase + iWeight];\n            Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n            float3 vtx = m_vertexControlPoints[cpi]; // regular face\n\n            limit.p += (w_t_i * sWeights[j]) * vtx;\n            limit.deriv1 += (w_t_i * dsWeights[j]) * vtx;\n            limit.deriv2 += (w_dt_i * sWeights[j]) * vtx;\n        }\n        limit.p += WaveReadLaneAt(limit.p, WaveGetLaneIndex() + 8);\n        limit.p += WaveReadLaneAt(limit.p, WaveGetLaneIndex() + 16);\n        limit.deriv1 += WaveReadLaneAt(limit.deriv1, WaveGetLaneIndex() + 8);\n        limit.deriv1 += WaveReadLaneAt(limit.deriv1, WaveGetLaneIndex() + 16);\n        limit.deriv2 += WaveReadLaneAt(limit.deriv2, WaveGetLaneIndex() + 8);\n        limit.deriv2 += WaveReadLaneAt(limit.deriv2, WaveGetLaneIndex() + 16);\n\n        return limit;\n        \n    }\n\n#ifdef PATCH_POINTS_WRITEABLE\n    void WaveEvaluatePatchPoints(uint32_t iLane, uint32_t numLanes)\n    {\n        SurfaceDescriptor desc = GetSurfaceDesc();\n        // desc.firstControlPoint is the offset to the first control point for this surface in vertexControlPointIndices\n\n        SubdivisionPlanContext plan = GetPlan();\n        const uint32_t numPatchPoints = plan.GetTreeDescriptor().GetNumPatchPoints(m_isolationLevel);\n\n        uint32_t globalPatchPointOffset = m_vertexPatchPointsOffsets[m_surfaceIndex];\n        for (int iPatchPoint = iLane; iPatchPoint < numPatchPoints; iPatchPoint += numLanes)  // advance wave\n        {\n            float3 patchPoint = float3(0, 0, 0);\n            for (int i = 0; i < plan.m_data.numControlPoints; ++i)\n            {\n                float3 controlPoint = m_vertexControlPoints[m_vertexControlPointIndices[desc.firstControlPoint + i]];\n                patchPoint += m_stencilMatrix[plan.m_data.stencilMatrixOffset + iPatchPoint * plan.m_data.numControlPoints + i] * controlPoint;\n            }\n            m_vertexPatchPoints[globalPatchPointOffset + iPatchPoint] = patchPoint;\n        }\n    }\n#endif\n\n    float3 CalculateLimitFrameNormal(LimitFrame limit)\n    {\n        // Primary method: compute normal from surface derivatives\n        float3 t0 = limit.deriv1;\n        float3 t1 = limit.deriv2;\n        \n        if (!IsParallel(t0, t1))\n        {\n            return normalize(cross(t0, t1));\n        }\n        else\n        {\n            // Fallback: compute normal from the 1-ring of the patch (center 3 points)\n            // Other points on the 2-ring can be zero if they are on a boundary.\n            SurfaceDescriptor desc = GetSurfaceDesc();\n            \n            Index cpi0 = m_vertexControlPointIndices[desc.firstControlPoint + 0];\n            Index cpi1 = m_vertexControlPointIndices[desc.firstControlPoint + 1];\n            Index cpi2 = m_vertexControlPointIndices[desc.firstControlPoint + 2];\n            \n            float3 p0 = m_vertexControlPoints[cpi0];\n            float3 p1 = m_vertexControlPoints[cpi1];\n            float3 p2 = m_vertexControlPoints[cpi2];\n\n            t0 = p1 - p0;\n            t1 = p2 - p0;\n            \n            if (!IsParallel(t0, t1))\n            {\n                return normalize(cross(t0, t1));\n            }\n            else\n            {\n                // Ultimate fallback: use up vector\n                return float3(0, 0, 1);\n            }\n        }\n    }\n};\n\nstruct DynamicSubdivisionEvaluatorHLSL : SubdivisionEvaluatorHLSL\n{\n    StructuredBuffer<float3> m_vertexControlPointsPrev;\n\n    void EvaluatePureBsplinePatch(out LimitFrame limit, out LimitFrame limitPrev, float2 uv)\n    {\n        float aPtWeights[kPatchSize];\n        float aDuWeights[kPatchSize];\n        float aDvWeights[kPatchSize];\n\n        EvalBasisBSpline(uv, aPtWeights, aDuWeights, aDvWeights,\n                                    0, // boundary mask\n                                    0.0f, // sharpness\n                                    true // pure bspline\n        );\n\n        SurfaceDescriptor desc = GetSurfaceDesc();\n\n        limit.Clear();\n        limitPrev.Clear();\n\n        for (int iWeight = 0; iWeight < kPatchSize; ++iWeight)\n        {\n            Index patchPointIndex = kPureBSplinePatchPointIndices[iWeight];\n            Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n            limit.AddWithWeight(m_vertexControlPoints[cpi], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n            limitPrev.AddWithWeight(m_vertexControlPointsPrev[cpi], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n        }\n    }\n\n\n    void EvaluateBsplinePatch(out LimitFrame limit, out LimitFrame limitPrev, float2 uv)\n    {\n        float aPtWeights[kPatchSize];\n        float aDuWeights[kPatchSize];\n        float aDvWeights[kPatchSize];\n\n        uint16_t quadrant = 0;\n\n        SubdivisionPlanContext plan = GetPlan();\n        SubdivisionNode node = plan.EvaluateBasis(uv, aPtWeights, aDuWeights, aDvWeights, quadrant, m_isolationLevel);\n\n        SurfaceDescriptor desc = GetSurfaceDesc();\n\n        limit.Clear();\n        limitPrev.Clear();\n\n        for (int iWeight = 0; iWeight < kPatchSize; ++iWeight)\n        {\n            Index patchPointIndex = node.GetPatchPoint(iWeight, quadrant, m_isolationLevel);\n            Index cpi = m_vertexControlPointIndices[desc.firstControlPoint + patchPointIndex];\n            limit.AddWithWeight(m_vertexControlPoints[cpi], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n            limitPrev.AddWithWeight(m_vertexControlPointsPrev[cpi], aPtWeights[iWeight], aDuWeights[iWeight], aDvWeights[iWeight]);\n        }\n    }\n\n    void EvaluateLimitSurface(out LimitFrame limit, out LimitFrame limitPrev, float2 uv)\n    {\n        uint16_t quadrant = 0;\n        const uint32_t numPatchPoints = 16;\n        float   aPtWeights[numPatchPoints];\n        float   aDuWeights[numPatchPoints];\n        float   aDvWeights[numPatchPoints];\n\n        SubdivisionPlanContext plan = GetPlan();\n        SubdivisionNode node = plan.EvaluateBasis(uv, aPtWeights, aDuWeights, aDvWeights, quadrant, m_isolationLevel);\n\n        limit.Clear();\n        limitPrev.Clear();\n\n        const int numControlPoints = plan.m_data.numControlPoints;\n        SurfaceDescriptor desc = GetSurfaceDesc();\n\n        for (int iPoint = 0; iPoint < numPatchPoints; ++iPoint)\n        {\n            Index patchPointIndex = node.GetPatchPoint(iPoint, quadrant, m_isolationLevel);\n            //assert(patchPointIndex >= numControlPoints);  // not a regular bspline surface\n\n            int stencilMatrixRow = plan.m_data.stencilMatrixOffset + (patchPointIndex - numControlPoints) * numControlPoints;\n            // Build patch iPoint and accumulate it\n            float3 patchPoint0 = 0.0f;\n            for (int i = 0; i < numControlPoints; ++i)\n            {\n                patchPoint0 +=\n                    m_vertexControlPoints[m_vertexControlPointIndices[desc.firstControlPoint + i]] *\n                    m_stencilMatrix[stencilMatrixRow + i];\n            }\n            limit.AddWithWeight(patchPoint0, aPtWeights[iPoint], aDuWeights[iPoint], aDvWeights[iPoint]);\n\n\n            float3 patchPoint1 = 0.0f;\n            for (int i = 0; i < numControlPoints; ++i)\n            {\n                patchPoint1 +=\n                    m_vertexControlPointsPrev[m_vertexControlPointIndices[desc.firstControlPoint + i]] *\n                    m_stencilMatrix[stencilMatrixRow + i];\n            }\n            limitPrev.AddWithWeight(patchPoint1, aPtWeights[iPoint], aDuWeights[iPoint], aDvWeights[iPoint]);\n        }\n    }\n\n    void Evaluate(out LimitFrame limit, out LimitFrame limitPrev, float2 uv)\n    {\n        if (IsPureBSplinePatch())\n        {\n            EvaluatePureBsplinePatch(limit, limitPrev, uv);\n        }\n        else if (IsBSplinePatch())\n        {\n            EvaluateBsplinePatch(limit, limitPrev, uv);\n        }\n        else\n        {\n            EvaluateLimitSurface(limit, limitPrev, uv);\n        }\n    }\n\n    LimitFrame EvaluatePrev(float2 uv)\n    {\n        LimitFrame dummy;\n        LimitFrame limit;\n        limit.Clear();\n        Evaluate(dummy, limit, uv);\n        return limit;\n    }\n};\n\nstruct TexcoordEvaluatorHLSL\n{\n    StructuredBuffer<LinearSurfaceDescriptor> m_surfaceDescriptors;\n    StructuredBuffer<Index> m_texcoordControlPointIndices;\n    StructuredBuffer<uint32_t> m_texcoordPatchPointsOffsets;\n    TEXCOORD_PATCH_POINTS_TYPE m_texcoordPatchPoints;\n    StructuredBuffer<float2> m_texcoordControlPoints;\n    \n    void EvalLinearBasis(out float weights[4], out float duWeights[4], out float dvWeights[4], float2 uv)\n    {\n        float u = uv.x;\n        float v = uv.y;\n        \n        weights[0] = (1.0f - u) * (1.0f - v);\n        weights[1] = u * (1.0f - v);\n        weights[2] = u * v;\n        weights[3] = (1.0f - u) * v;\n\n        duWeights[0] = (-1.0f + v);\n        duWeights[1] = (1.0f - v);\n        duWeights[2] = v;\n        duWeights[3] = -v;\n\n        dvWeights[0] = (-1.0f + u);\n        dvWeights[1] = (-u);\n        dvWeights[2] = u;\n        dvWeights[3] = (1.0f - u);\n    }\n\n    Index GetPatchPoint(int pointIndex, uint16_t faceSize, LocalIndex subfaceIndex)\n    {\n        if (subfaceIndex == LOCAL_INDEX_INVALID)\n        {\n            assert(pointIndex < faceSize);\n            return pointIndex;\n        }\n        else\n        {\n            assert(pointIndex < 4);\n            // patch point indices layout (N = faceSize) :\n            // [ N control points ]\n            // [ 1 face-point ]\n            // [ N edge-points ]\n            int N = faceSize;\n            switch (pointIndex)\n            {\n                case 0:\n                    return subfaceIndex;\n                case 1:\n                    return N + 1 + subfaceIndex; // edge-point after\n                case 2:\n                    return N; // central face-point\n                case 3:\n                    return N + (subfaceIndex > 0 ? subfaceIndex : N);\n            }\n        }\n        return INDEX_INVALID;\n    }\n    \n#ifdef PATCH_POINTS_WRITEABLE\n    void WaveEvaluateTexCoordPatchPoints(uint32_t iLane, uint32_t iSurface)\n    {\n        LinearSurfaceDescriptor desc = m_surfaceDescriptors[iSurface];\n        LocalIndex subface = desc.GetQuadSubfaceIndex();\n\n        if (subface == LOCAL_INDEX_INVALID)\n            return;\n\n        uint16_t faceSize = desc.GetFaceSize();\n        float2 center = (iLane < faceSize) ?\n            m_texcoordControlPoints[m_texcoordControlPointIndices[desc.firstControlPoint + iLane]] : \n            float2(0, 0);\n\n        for (int offset = 16; offset > 0; offset /= 2)\n        {\n            center.x += WaveReadLaneAt(center.x, WaveGetLaneIndex() + offset);\n            center.y += WaveReadLaneAt(center.y, WaveGetLaneIndex() + offset);\n        }\n\n        if (iLane == 0)\n        {\n            m_texcoordPatchPoints[m_texcoordPatchPointsOffsets[iSurface] + 0] = center / faceSize;\n        }\n\n        if (iLane < faceSize)\n        {\n            float2 a = m_texcoordControlPoints[m_texcoordControlPointIndices[desc.firstControlPoint + iLane]];\n            float2 b = m_texcoordControlPoints[m_texcoordControlPointIndices[desc.firstControlPoint + (iLane + 1) % faceSize]];\n            m_texcoordPatchPoints[m_texcoordPatchPointsOffsets[iSurface] + iLane + 1] = 0.5f * (a + b);\n        }\n    }\n#endif\n    \n    TexCoordLimitFrame EvaluateLinearSubd(float2 uv, uint32_t iSurface)\n    {\n        TexCoordLimitFrame limit;\n        limit.Clear();\n    \n        LinearSurfaceDescriptor desc = m_surfaceDescriptors[iSurface];\n\n        const uint16_t faceSize = desc.GetFaceSize();\n        LocalIndex subface = desc.GetQuadSubfaceIndex();\n\n        float pointWeights[4], duWeights[4], dvWeights[4];\n        EvalLinearBasis(pointWeights, duWeights, dvWeights, uv);\n        \n        for (int k = 0; k < 4; ++k)\n        {\n            Index patchPointIndex = GetPatchPoint(k, faceSize, subface);\n                \n            if (patchPointIndex < faceSize)\n            {\n                limit.AddWithWeight(m_texcoordControlPoints[m_texcoordControlPointIndices[desc.firstControlPoint + patchPointIndex]], pointWeights[k], duWeights[k], dvWeights[k]);\n            }\n            else\n            {\n                uint32_t supportOffset = m_texcoordPatchPointsOffsets[iSurface];\n                limit.AddWithWeight(m_texcoordPatchPoints[supportOffset + patchPointIndex - faceSize], pointWeights[k], duWeights[k], dvWeights[k]);\n            }\n        }\n        \n        return limit;\n    }\n};\n\n#endif // SUBDIVISION_EVAL_HLSLI"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/subdivision_plan_hlsl.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#pragma once\n\n#include \"rtxmg/subdivision/osd_ports/tmr/types.h\"\n\n#ifndef __cplusplus\n#include \"rtxmg/subdivision/osd_ports/tmr/treeDescriptor.h\"\n#include \"rtxmg/subdivision/osd_ports/tmr/nodeDescriptor.h\"\n#include \"rtxmg/subdivision/osd_ports/tmr/subdivisionNode.h\"\n#include \"patch_param.h\"\n\nenum class SingleCreaseDynamicIsolation : uint16_t { SHARP = 0, SMOOTH = 1, };\nstatic const SingleCreaseDynamicIsolation kSingleCreaseDynamicIsolation = SingleCreaseDynamicIsolation::SMOOTH;\nstatic const float kMinFloat = 1.17549435e-38f;\n// translators for Tmr => Far types\ninline PatchDescriptorType\nRegularBasisType(SchemeType scheme)\n{\n    switch (scheme)\n    {\n    case SCHEME_CATMARK: return REGULAR;\n    case SCHEME_LOOP: return LOOP;\n    default:\n        break;\n    }\n    return NON_PATCH;\n}\n\ninline PatchDescriptorType\nIrregularBasisType(SchemeType scheme, EndCapType endcap)\n{\n\n    if (scheme == SCHEME_CATMARK)\n    {\n        switch (endcap)\n        {\n        case ENDCAP_BILINEAR_BASIS: return QUADS;\n        case ENDCAP_BSPLINE_BASIS: return REGULAR;\n        case ENDCAP_GREGORY_BASIS: return GREGORY_BASIS;\n        default:\n            break;\n        }\n    }\n    else if (scheme == SCHEME_LOOP)\n    {\n        switch (endcap)\n        {\n        case ENDCAP_BILINEAR_BASIS: return TRIANGLES;\n        case ENDCAP_BSPLINE_BASIS: return LOOP;\n        case ENDCAP_GREGORY_BASIS: return GREGORY_TRIANGLE;\n        default:\n            break;\n        }\n    }\n    return NON_PATCH;\n}\n\n// both Loop & Catmark quadrant traversals expect Z-curve winding\n// (see subdivisionPlanBuilder for details)\ninline void TraverseCatmark(inout float u, inout float v, inout uint16_t quadrant)\n{\n    if (u >= 0.5f)\n    {\n        quadrant ^= 1;\n        u = 1.0f - u;\n    }\n    if (v >= 0.5f)\n    {\n        quadrant ^= 2;\n        v = 1.0f - v;\n    }\n    u *= 2.0f;\n    v *= 2.0f;\n}\n\n\n// note: Z-winding of triangle faces rotates sub-domains every subdivision level,\n// but the center face is always at index (2)\n//\n//                0,1                                    0,1\n//                 *                                      *\n//               /   \\                                  /   \\\n//              /     \\                                /  3  \\\n//             /       \\                              /       \\\n//            /         \\           ==>        0,0.5 . ------- . 0.5,0.5\n//           /           \\                          /   \\ 2 /   \\\n//          /             \\                        /  0  \\ /  1  \\\n//         * ------------- *                      * ----- . ----- *\n//      0,0                 1,0                0,0      0.5,0      1,0\n\ninline uint16_t TraverseLoop(float median, inout float u, inout float v, inout bool rotated)\n{\n    if (!rotated)\n    {\n        if (u >= median)\n        {\n            u -= median;\n            return 1;\n        }\n        if (v >= median)\n        {\n            v -= median;\n            return 3;\n        }\n        if ((u + v) >= median)\n        {\n            rotated = true;\n            return 2;\n        }\n    }\n    else\n    {\n        if (u < median)\n        {\n            v -= median;\n            return 1;\n        }\n        if (v < median)\n        {\n            u -= median;\n            return 3;\n        }\n        u -= median;\n        v -= median;\n        if ((u + v) < median)\n        {\n            rotated = true;\n            return 2;\n        }\n    }\n    return 0;\n}\n\n//\n//  Cubic BSpline curve basis evaluation:\n//\ninline void EvalBSplineCurve(float t, out float wP[4], out float wDP[4], out float wDP2[4], bool calcD1, bool calcD2)\n{\n    float const one6th = (float)(1.0 / 6.0);\n\n    float t2 = t * t;\n    float t3 = t * t2;\n\n    wP[0] = one6th * (1.0f - 3.0f * (t - t2) - t3);\n    wP[1] = one6th * (4.0f - 6.0f * t2 + 3.0f * t3);\n    wP[2] = one6th * (1.0f + 3.0f * (t + t2 - t3));\n    wP[3] = one6th * (t3);\n\n    if (calcD1)\n    {\n        wDP[0] = -0.5f * t2 + t - 0.5f;\n        wDP[1] = 1.5f * t2 - 2.0f * t;\n        wDP[2] = -1.5f * t2 + t + 0.5f;\n        wDP[3] = 0.5f * t2;\n    }\n    if (calcD2)\n    {\n        wDP2[0] = -t + 1.0f;\n        wDP2[1] = 3.0f * t - 2.0f;\n        wDP2[2] = -3.0f * t + 1.0f;\n        wDP2[3] = t;\n    }\n}\n\n\ninline float mix(float s1, float s2, float t)\n{\n    return ((float)1.0 - t) * s1 + t * s2;\n}\n\n\ninline void ComputeMixedCreaseMatrix(float sharp1, float sharp2, float t, float tInf, out float m[16])\n{\n    float s1 = (float)exp2(sharp1), s2 = (float)exp2(sharp2);\n\n    float sOver3 = mix(s1, s2, t) / float(3), oneOverS1 = (float)1 / s1, oneOverS2 = (float)1 / s2,\n        oneOver6S = mix(oneOverS1, oneOverS2, t) / (float)6, sSqr = mix(s1 * s1, s2 * s2, t);\n\n    float A = -sSqr + sOver3 * (float)5.5 + oneOver6S - (float)1.0, B = sOver3 + oneOver6S + (float)0.5,\n        C = sOver3 - oneOver6S * (float)2.0 + (float)1.0, E = sOver3 + oneOver6S - (float)0.5,\n        F = -sOver3 * (float)0.5 + oneOver6S;\n\n    m[0] = (float)1.0;\n    m[1] = A * tInf;\n    m[2] = (float)-2.0 * A * tInf;\n    m[3] = A * tInf;\n    m[4] = (float)0.0;\n    m[5] = mix((float)1.0, B, tInf);\n    m[6] = (float)-2.0 * E * tInf;\n    m[7] = E * tInf;\n    m[8] = (float)0.0;\n    m[9] = F * tInf;\n    m[10] = mix((float)1.0, C, tInf);\n    m[11] = F * tInf;\n    m[12] = (float)0.0;\n    m[13] = mix((float)-1.0, E, tInf);\n    m[14] = mix((float)2.0, -(float)2.0 * E, tInf);\n    m[15] = B * tInf;\n}\n\n// compute the \"crease matrix\" for modifying basis weights at parametric\n// location 't', given a sharpness value (see Matthias Niessner derivation\n// for 'single crease' regular patches)\ninline void ComputeCreaseMatrix(float sharpness, float t, out float m[16])\n{\n\n    float sharpFloor = (float)floor(sharpness), sharpCeil = sharpFloor + 1, sharpFrac = sharpness - sharpFloor;\n\n    float creaseWidthFloor = (float)1.0 - exp2(-sharpFloor), creaseWidthCeil = (float)1.0 - exp2(-sharpCeil);\n\n    // we compute the matrix for both the floor and ceiling of\n    // the sharpness value, and then interpolate between them\n    // as needed.\n    float tA = (t > creaseWidthCeil) ? sharpFrac : (float)0.0, tB = (float)0.0;\n    if (t > creaseWidthFloor)\n        tB = (float)1.0 - sharpFrac;\n    if (t > creaseWidthCeil)\n        tB = (float)1.0;\n\n    ComputeMixedCreaseMatrix(sharpFloor, sharpCeil, tA, tB, m);\n}\n\ninline void swap(inout float a, inout float b)\n{\n    float t = a;\n    a = b;\n    b = t;\n}\n\ninline void FlipMatrix(inout float m[16])\n{\n    swap(m[0], m[15]);\n    swap(m[1], m[14]);\n    swap(m[2], m[13]);\n    swap(m[3], m[12]);\n    swap(m[4], m[11]);\n    swap(m[5], m[10]);\n    swap(m[6], m[9]);\n    swap(m[7], m[8]);\n}\n\ninline void FlipMatrix(float a[16], out float m[16])\n{\n    m[0] = a[15];\n    m[1] = a[14];\n    m[2] = a[13];\n    m[3] = a[12];\n    m[4] = a[11];\n    m[5] = a[10];\n    m[6] = a[9];\n    m[7] = a[8];\n    m[8] = a[7];\n    m[9] = a[6];\n    m[10] = a[5];\n    m[11] = a[4];\n    m[12] = a[3];\n    m[13] = a[2];\n    m[14] = a[1];\n    m[15] = a[0];\n}\n\n// v x m (column major)\ninline void ApplyMatrix(inout float v[4], float m[16])\n{\n    float r[4];\n    r[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];\n    r[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];\n    r[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];\n    r[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];\n    v[0] = r[0];\n    v[1] = r[1];\n    v[2] = r[2];\n    v[3] = r[3];\n}\n\nvoid EvalBasisBSpline(float2 st, out float wP[16], out float wDs[16], out float wDt[16], int boundaryMask, float sharpness, bool PURE_BSPLINE)\n{\n    float sWeights[4], tWeights[4], dsWeights[4], dtWeights[4], unused[4];\n\n    EvalBSplineCurve(st.x, sWeights, dsWeights, unused, true, false);\n    EvalBSplineCurve(st.y, tWeights, dtWeights, unused, true, false);\n\n    if ((boundaryMask != 0 && sharpness > (float)0.0) && !PURE_BSPLINE)\n    {\n        float m[16], mflip[16];\n        if (boundaryMask & 1)\n        {\n            ComputeCreaseMatrix(sharpness, (float)1.0 - st.y, m);\n            FlipMatrix(m, mflip);\n            ApplyMatrix(tWeights, mflip);\n            ApplyMatrix(dtWeights, mflip);\n        }\n        if (boundaryMask & 2)\n        {\n            ComputeCreaseMatrix(sharpness, st.x, m);\n            ApplyMatrix(sWeights, m);\n            ApplyMatrix(dsWeights, m);\n        }\n        if (boundaryMask & 4)\n        {\n            ComputeCreaseMatrix(sharpness, st.y, m);\n            ApplyMatrix(tWeights, m);\n            ApplyMatrix(dtWeights, m);\n        }\n        if (boundaryMask & 8)\n        {\n            ComputeCreaseMatrix(sharpness, (float)1.0 - st.x, m);\n            FlipMatrix(m, mflip);\n            ApplyMatrix(sWeights, mflip);\n            ApplyMatrix(dsWeights, mflip);\n        }\n    }\n\n    for (int i = 0; i < 4; ++i)\n    {\n        for (int j = 0; j < 4; ++j)\n        {\n            wP[4 * i + j] = sWeights[j] * tWeights[i];\n            wDs[4 * i + j] = dsWeights[j] * tWeights[i];\n            wDt[4 * i + j] = sWeights[j] * dtWeights[i];\n        }\n    }\n}\n\n//\n//  Weight adjustments to account for phantom end points:\n//\ninline void AdjustBSplineBoundaryWeights(int boundary, inout float w[16])\n{\n\n    if ((boundary & 1) != 0)\n    {\n        for (int i = 0; i < 4; ++i)\n        {\n            w[i + 8] -= w[i + 0];\n            w[i + 4] += w[i + 0] * 2.0f;\n            w[i + 0] = 0.0f;\n        }\n    }\n    if ((boundary & 2) != 0)\n    {\n        for (int i = 0; i < 16; i += 4)\n        {\n            w[i + 1] -= w[i + 3];\n            w[i + 2] += w[i + 3] * 2.0f;\n            w[i + 3] = 0.0f;\n        }\n    }\n    if ((boundary & 4) != 0)\n    {\n        for (int i = 0; i < 4; ++i)\n        {\n            w[i + 4] -= w[i + 12];\n            w[i + 8] += w[i + 12] * 2.0f;\n            w[i + 12] = 0.0f;\n        }\n    }\n    if ((boundary & 8) != 0)\n    {\n        for (int i = 0; i < 16; i += 4)\n        {\n            w[i + 2] -= w[i + 0];\n            w[i + 1] += w[i + 0] * 2.0f;\n            w[i + 0] = 0.0f;\n        }\n    }\n}\n\ninline void AdjustBSplineBoundaryTop(inout float w[4])\n{\n    w[1] -= w[3];\n    w[2] += w[3] * 2.0f;\n    w[3] = 0.0f;\n}\n\ninline void AdjustBSplineBoundaryBottom(inout float w[4])\n{\n    w[2] -= w[0];\n    w[1] += w[0] * 2.0f;\n    w[0] = 0.0f;\n}\n\ninline void AdjustBoundaries(float2 st, inout float sWeights[4], inout float tWeights[4], inout float dsWeights[4], inout float dtWeights[4], int boundaryMask)\n{\n    if ((boundaryMask & 1) != 0)\n    {\n        AdjustBSplineBoundaryBottom(tWeights);\n        AdjustBSplineBoundaryBottom(dtWeights);\n    }\n    if ((boundaryMask & 4) != 0)\n    {\n        AdjustBSplineBoundaryTop(tWeights);\n        AdjustBSplineBoundaryTop(dtWeights);\n    }\n    if ((boundaryMask & 2) != 0)\n    {\n        AdjustBSplineBoundaryTop(sWeights);\n        AdjustBSplineBoundaryTop(dsWeights);\n    }\n    if ((boundaryMask & 8) != 0)\n    {\n        AdjustBSplineBoundaryBottom(sWeights);\n        AdjustBSplineBoundaryBottom(dsWeights);\n    }\n}\n\n// compute the \"crease matrix\" for modifying basis weights at parametric\n// location 't', given a sharpness value (see Matthias Niessner derivation\n// for 'single crease' regular patches)\ninline void ComputeCreaseMatrixTop(float sharpness, float t, out float m[16])\n{\n\n    float sharpFloor = (float)floor(sharpness), sharpCeil = sharpFloor + 1, sharpFrac = sharpness - sharpFloor;\n    float creaseWidthFloor = (float)1.0 - exp2(-sharpFloor), creaseWidthCeil = (float)1.0 - exp2(-sharpCeil);\n\n    // we compute the matrix for both the floor and ceiling of\n    // the sharpness value, and then interpolate between them\n    // as needed.\n    float tA = (t > creaseWidthCeil) ? sharpFrac : 0.0f;\n    float tB = 0.0f;\n    if (t > creaseWidthFloor)\n        tB = 1.0f - sharpFrac;\n    if (t > creaseWidthCeil)\n        tB = 1.0f;\n\n    ComputeMixedCreaseMatrix(sharpFloor, sharpCeil, tA, tB, m);\n}\n\ninline void ComputeCreaseMatrixBottom(float sharpness, float t, out float m[16])\n{\n    ComputeCreaseMatrixTop(sharpness, 1.0f - t, m);\n    FlipMatrix(m);\n}\n\ninline void AdjustCreases(float2 st, inout float sWeights[4], inout float tWeights[4], inout float dsWeights[4], inout float dtWeights[4], int boundaryMask, float sharpness)\n{\n    if (boundaryMask & 1)\n    {\n        float m[16];\n        ComputeCreaseMatrixBottom(sharpness, st.y, m);\n        ApplyMatrix(tWeights, m);\n        ApplyMatrix(dtWeights, m);\n    }\n    if (boundaryMask & 4)\n    {\n        float m[16];\n        ComputeCreaseMatrixTop(sharpness, st.y, m);\n        ApplyMatrix(tWeights, m);\n        ApplyMatrix(dtWeights, m);\n    }\n    if (boundaryMask & 2)\n    {\n        float m[16];\n        ComputeCreaseMatrixTop(sharpness, st.x, m);\n        ApplyMatrix(sWeights, m);\n        ApplyMatrix(dsWeights, m);\n    }\n    if (boundaryMask & 8)\n    {\n        float m[16];\n        ComputeCreaseMatrixBottom(sharpness, st.x, m);\n        ApplyMatrix(sWeights, m);\n        ApplyMatrix(dsWeights, m);\n    }\n}\n\n\n\ninline  void BoundBasisBSpline(int boundary, inout float wP[16], inout float wDs[16], inout float wDt[16])\n{\n    AdjustBSplineBoundaryWeights(boundary, wP);\n    AdjustBSplineBoundaryWeights(boundary, wDs);\n    AdjustBSplineBoundaryWeights(boundary, wDt);\n}\n\n\n//\n//  Higher level basis evaluation functions that deal with parameterization and\n//  boundary issues (reflected in PatchParam) for all patch types:\n//\ninline bool EvaluatePatchBasisNormalized(PatchDescriptorType  patchType,\n    PatchParam param,\n    float2                             st,\n    out float                          wP[16],\n    out float                          wDs[16],\n    out float                          wDt[16],\n    float                              sharpness)\n{\n    int boundaryMask = param.GetBoundary();\n\n    bool hasPoints = false;\n    if (patchType == REGULAR)\n    {\n        hasPoints = true;\n        EvalBasisBSpline(st, wP, wDs, wDt, boundaryMask, sharpness, false);\n        if (boundaryMask && (sharpness == float(0)))\n        {\n            BoundBasisBSpline(boundaryMask, wP, wDs, wDt);\n        }\n    }\n    return hasPoints;\n}\n\n\ninline void EvaluatePatchBasis(PatchDescriptorType patchType,\n    PatchParam param,\n    float2                             st,\n    out float                          wP[16],\n    out float                          wDs[16],\n    out float                          wDt[16],\n    float                              sharpness = 0)\n{\n    float derivSign = 1.0f;\n\n    if ((patchType == LOOP) || (patchType == GREGORY_TRIANGLE)\n        || (patchType == TRIANGLES))\n    {\n        param.NormalizeTriangle(st.x, st.y);\n        if (param.IsTriangleRotated())\n        {\n            derivSign = -1.0f;\n        }\n    }\n    else\n    {\n        param.Normalize(st.x, st.y);\n    }\n\n    bool hasPoints = EvaluatePatchBasisNormalized(patchType, param, st, wP, wDs, wDt, sharpness);\n    float d1Scale = derivSign * (float)(1U << param.GetDepth());\n\n    if (hasPoints)\n    {\n        for (int i = 0; i < 16; ++i)\n        {\n            wDs[i] *= d1Scale;\n            wDt[i] *= d1Scale;\n        }\n    }\n    \n}\n\n#endif\n\nstruct SubdivisionPlanHLSL\n{\n    static const SchemeType kScheme = SCHEME_CATMARK;\n    static const EndCapType kEndCap = ENDCAP_BSPLINE_BASIS;\n\n    // scheme, endcap used to be dynamically defined\n    SchemeType GetScheme() { return kScheme; }\n    EndCapType GetEndCap() { return kEndCap; }\n\n    uint16_t numControlPoints;\n    uint16_t coarseFaceSize;\n    int16_t  coarseFaceQuadrant;  // locates a surface within a non-quad parent face\n\n    uint32_t treeOffset; // offset into m_subpatchTrees;\n    uint32_t treeSize; // size of elements in m_subpatchTrees\n\n    uint32_t patchPointsOffset; // index into m_patchPoints\n    uint32_t patchPointsSize; // size of elements in m_patchPoints\n\n    // Stencil matrix for computing patch points from control points:\n    // - columns contain 1 scalar weight per control point of the 1-ring\n    // - rows contain a stencil of weights for each patch point\n    uint32_t stencilMatrixOffset; // index into m_stencilMatrix\n    uint32_t stencilMatrixSize; // size of elements in m_stencilMatrix\n\n    // member variables are not referenced (see GetScheme(), GetEndCap()) but are left in here\n    // in case the caller wants to implement a dynamic path\n    SchemeType scheme;\n    EndCapType endCap;\n};\n\n#ifndef __cplusplus\n\nfloat\ncomputeSingleCreaseSharpness(SubdivisionNode n, NodeDescriptor desc, uint16_t depth, uint16_t level)\n{\n    if (kSingleCreaseDynamicIsolation == SingleCreaseDynamicIsolation::SHARP)\n    {\n        return desc.HasSharpness() ? n.GetSharpness() : 0.0f;\n    }\n    else if (kSingleCreaseDynamicIsolation == SingleCreaseDynamicIsolation::SMOOTH)\n    {\n        if (desc.HasSharpness())\n        {\n            float sharpness = n.GetSharpness();\n            \n            // single-crease patches require a non-null boundary mask and sharpness > 0.f\n            // std::numeric_limits::min() ensures EvalBasisBSpline() evaluates the crease\n            // matrix\n            return max(kMinFloat, min(sharpness, float(level) - float(depth)));\n        }\n        return 0.f;\n    }\n\n    // Impossible path\n    return 0.f;\n}\n\nstruct SubdivisionPlanContext\n{\n    SubdivisionPlanHLSL m_data;\n\n    StructuredBuffer<uint32_t> m_subpatchTrees;\n    StructuredBuffer<Index> m_patchPoints; // indices into the stencil matrix weights\n    StructuredBuffer<float> m_stencilMatrix; \n\n    TreeDescriptorHLSL GetTreeDescriptor()\n    {\n        TreeDescriptorHLSL treeDescriptor;\n        treeDescriptor.m_subpatchTrees = m_subpatchTrees;\n        treeDescriptor.m_treeOffset = m_data.treeOffset;\n        return treeDescriptor;\n    }\n\n    bool IsBSplinePatch(uint16_t level)\n    {\n        return GetTreeDescriptor().GetNumPatchPoints(level) == 0;\n    }\n\n    SubdivisionNode GetRootNode()\n    {\n        SubdivisionNode node;\n        node.m_subpatchTrees = m_subpatchTrees;\n        node.m_patchPoints = m_patchPoints;\n        node.m_nodeOffset = SubdivisionNode::rootNodeOffset();\n        node.m_treeOffset = m_data.treeOffset;\n        node.m_patchPointsOffset = m_data.patchPointsOffset;\n        return node;\n    }\n\n    SubdivisionNode GetNode(float2 uv, inout uint16_t quadrant, uint16_t level)\n    {\n        // start at root node\n        SubdivisionNode node = GetRootNode();\n        NodeDescriptor desc = node.GetDesc();\n        NodeType type = desc.GetType();\n        bool isIrregular = !(GetTreeDescriptor().IsRegularFace());\n        bool rotated = false;\n        float median = 0.5f;\n\n        while (type == NODE_RECURSIVE)\n        {\n            if (desc.GetDepth() == level)\n            {\n                break;\n            }\n            switch (m_data.GetScheme())\n            {\n            case SCHEME_CATMARK:\n                TraverseCatmark(uv.x, uv.y, quadrant);\n                break;\n            case SCHEME_LOOP:\n                quadrant = TraverseLoop(median, uv.x, uv.y, rotated);\n                break;\n            default:\n                // TODO: SCHEME_BILINEAR NOT HANDLED\n                break;\n            }\n\n            node = node.GetChild(quadrant);\n            desc = node.GetDesc();\n            type = desc.GetType();\n\n            median *= 0.5f;\n        }\n        return node;\n    }\n\n    SubdivisionNode EvaluateBasis(float2 st, out float wP[16], out float wDs[16], out float wDt[16], out uint16_t subpatch, uint16_t level)\n    {\n        PatchDescriptorType regularBasis = RegularBasisType(m_data.GetScheme());\n        PatchDescriptorType irregularBasis = IrregularBasisType(m_data.GetScheme(), m_data.GetEndCap());\n\n        bool isIrregular = !(GetTreeDescriptor().IsRegularFace());\n\n        uint16_t quadrant = 0;\n        SubdivisionNode node = GetNode(st, quadrant, level);\n\n        NodeDescriptor desc = node.GetDesc();\n\n        NodeType nodeType = desc.GetType();\n        uint16_t depth = (uint16_t)desc.GetDepth();\n\n        bool dynamicIsolation = (nodeType == NODE_RECURSIVE) && (depth >= level) && desc.HasEndcap();\n\n        uint16_t u = (uint16_t)desc.GetU();\n        uint16_t v = (uint16_t)desc.GetV();\n\n        PatchParam param;\n\n        if (dynamicIsolation)\n        {\n            param.Set(INDEX_INVALID, u, v, depth, isIrregular, 0, 0, true);\n            EvaluatePatchBasis(irregularBasis, param, st, wP, wDs, wDt);\n        }\n        else\n        {\n            param.Set(INDEX_INVALID, u, v, depth, isIrregular, (uint16_t)desc.GetBoundaryMask(), 0, true);\n            switch (nodeType)\n            {\n            case NODE_REGULAR:\n            {\n                float sharpness = computeSingleCreaseSharpness(node, desc, depth, level);\n                EvaluatePatchBasis(regularBasis, param, st, wP, wDs, wDt, sharpness);\n                break;\n            }\n\n            case NODE_END:\n                EvaluatePatchBasis(irregularBasis, param, st, wP, wDs, wDt);\n                break;\n\n            case NODE_TERMINAL:\n            case NODE_RECURSIVE:\n                // not handled\n                break;\n            }\n        }\n        subpatch = quadrant;\n        return node;\n    }\n};\n#endif\n\n\n#ifndef __cplusplus\n// clang-format off\nstatic const float kBSplineWeights[] = {\n    // cubic b-spline weight matrix\n    1 / 6.0f, -3 / 6.0f,  3 / 6.0f, -1 / 6.0f,\n    4 / 6.0f,  0 / 6.0f, -6 / 6.0f,  3 / 6.0f,\n    1 / 6.0f,  3 / 6.0f,  3 / 6.0f, -3 / 6.0f,\n    0 / 6.0f,  0 / 6.0f,  0 / 6.0f,  1 / 6.0f,\n};\n// clang-format on\n\ninline float CubicBSplineWeight(float t, uint32_t index)\n{\n    float result = 0.0f;\n    float factor = 1.0f;\n\n    for (uint32_t i = 0; i < 4; ++i)\n    {\n        result += kBSplineWeights[4 * index + i] * factor;\n        factor *= t;\n    }\n    return result;\n}\n\n// clang-format off\nstatic const float kDerivativeWeights[] = {\n    // cubic b-spline derivative weight matrix\n    -1 / 2.0f,  2 / 2.0f, -1 / 2.0f,\n     0 / 2.0f, -4 / 2.0f,  3 / 2.0f,\n     1 / 2.0f,  2 / 2.0f, -3 / 2.0f,\n     0 / 2.0f,  0 / 2.0f,  1 / 2.0f\n};\n// clang-format on\n\ninline float CubicBSplineDerivativeWeight(float t, uint32_t index)\n{\n    float result = 0.0f;\n    float factor = 1.0f;\n\n    for (uint32_t i = 0; i < 3; ++i)\n    {\n        result += kDerivativeWeights[3 * index + i] * factor;\n        factor *= t;\n    }\n    return result;\n}\n\n#endif"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/subdivision_surface.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <donut/core/math/math.h>\n#include <donut/engine/ShaderFactory.h>\n#include <donut/engine/DescriptorTableManager.h>\n#include <nvrhi/utils.h>\n#include <opensubdiv/tmr/surfaceTable.h>\n\nusing namespace donut::math;\n\nstruct Shape;\nclass TopologyCache;\nstruct TopologyMap;\n\nclass SubdivisionSurface\n{\npublic:\n    // hashes the mesh topology into a TopologyCache and\n    // initializes the device-side data structures corresponding to the\n    // Tmr::SurfaceTables for 'vertex' and 'face-vayring' data (position &\n    // texcoords).\n    SubdivisionSurface(TopologyCache& topologyCache, std::unique_ptr<Shape> shape,\n        const std::vector<std::unique_ptr<Shape>>& keyFrames,\n        std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTableManager,\n        nvrhi::ICommandList* commandList);\n\n    bool HasAnimation() const;\n    uint32_t NumKeyframes() const;\n\n    void Animate(float animTime, float frameRate);\n\n    uint32_t NumVertices() const;\n    uint32_t SurfaceCount() const;\n\n    // AABBs are in object-space !\n    std::vector<box3> m_aabbKeyframes;\n    box3 m_aabb;\n\n    // Animation space\n    int m_f0 = 0;\n    int m_f1 = 0;\n    float m_dt = 0.f;\n    float m_frameOffset = 0.0f;\n\npublic:\n    struct SurfaceTableDeviceData\n    {\n        nvrhi::BufferHandle surfaceDescriptors;\n        nvrhi::BufferHandle controlPointIndices;\n\n        nvrhi::BufferHandle patchPoints;\n        nvrhi::BufferHandle patchPointsOffsets;\n    };\n\n    //\n    // 'vertex' limit interpolation surface-table ; see :\n    // https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#vertex-and-varying-data\n    //\n    SurfaceTableDeviceData m_vertexDeviceData;\n\n    std::vector<nvrhi::BufferHandle> m_positionKeyframeBuffers;\n    nvrhi::BufferHandle m_positionsBuffer;\n    nvrhi::BufferHandle m_positionsPrevBuffer;\n\n    //\n    // 'face-varying' (texcoords) limit interpolation surface-table ; see :\n    // https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#face-varying-data-and-topology\n    //\n    SurfaceTableDeviceData m_texcoordDeviceData;\n    nvrhi::BufferHandle m_texcoordsBuffer;\n    nvrhi::BufferHandle m_surfaceToGeometryIndexBuffer;\n    nvrhi::BufferHandle m_topologyQualityBuffer;\n\n    donut::engine::DescriptorHandle m_vertexSurfaceDescriptorDescriptor;\n    donut::engine::DescriptorHandle m_vertexControlPointIndicesDescriptor;\n    donut::engine::DescriptorHandle m_positionsDescriptor;\n    donut::engine::DescriptorHandle m_positionsPrevDescriptor;\n    donut::engine::DescriptorHandle m_surfaceToGeometryIndexDescriptor;   \n\n    // Surface Types\n    enum class SurfaceType : uint32_t\n    {\n        PureBSpline,\n        RegularBSpline,\n        Limit,\n        NoLimit,\n        Count\n    };\n    std::array<uint32_t, size_t(SurfaceType::Count)> m_surfaceOffsets;\n    uint32_t m_surfaceCount = 0;\n    \n    bool m_hasDisplacementMaterial = false;\n    donut::engine::DescriptorHandle m_topologyQualityDescriptor;\n\npublic:\n    Shape const* GetShape() const { return m_shape.get(); }\n    TopologyMap const* GetTopologyMap() const { return m_topology_map; }\n\nprotected:\n    TopologyMap const* m_topology_map = nullptr;\n\n    void InitDeviceData(nvrhi::ICommandList* commandList);\n\n    std::unique_ptr<Shape> m_shape;\n\n    std::unique_ptr<const OpenSubdiv::Tmr::SurfaceTable> m_surface_table;\n    std::unique_ptr<const OpenSubdiv::Tmr::LinearSurfaceTable>\n        m_texcoord_surface_table;\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/topology_cache.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n// clang-format off\n#pragma once\n\n#include <cstdint>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <vector>\n\n#include \"rtxmg/subdivision/topology_map.h\"\n// clang-format on\n\n// Thread-safe cache for Tmr::TopologyMap\n//\n// The topology cache collects a set of topology maps used to hash the topology\n// of subD surfaces in a scene based on 'traits' (subdivision rules). Once all\n// the meshes have been parsed, the topology maps can be serialized and hoisted\n// in device memory.\n//\n// note: ownership of the host-side transient Tmr::TopologyyMaps is passed on to\n// the device container (::TopologyMap) so that we can still support a CPU code\n// path.\n//\n\nclass TopologyCache\n{\npublic:\n    struct Options\n    {\n        // see Tmr::SubdivisionPlanBuilder::Options for details\n        uint8_t const isoLevelSharp = 6;\n        uint8_t const isoLevelSmooth = 3;\n        bool const useTerminalNodes = false;\n    } const options;\n\n    TopologyCache(Options const& options);\n\n    TopologyMap& get(uint8_t traits);\n\n    TopologyMap const& Get(uint8_t traits) const { return this->Get(traits); }\n\n    bool Empty() const;\n\n    size_t Size() const;\n\n    void Clear();\n\n    // note: all hashing must be completed before hoisting maps into device memory\n    // !\n    std::vector<std::unique_ptr<TopologyMap const>>\n        InitDeviceData(std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTable, \n            nvrhi::ICommandList* commandList, bool keepHostData = true);\n\nprivate:\n    mutable std::mutex m_mtx;\n\n    std::map<uint8_t, std::unique_ptr<TopologyMap>> m_topologyMaps;\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/topology_map.h",
    "content": "//\n// Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n// clang-format off\n#pragma once\n\n#include <opensubdiv/version.h>\n\n#include <cstdint>\n#include <span>\n\n#include <nvrhi/utils.h>\n#include <donut/engine/DescriptorTableManager.h>\n// clang-format on\n\nnamespace OpenSubdiv::OPENSUBDIV_VERSION::Tmr\n{\n    class TopologyMap;\n}\nnamespace stats\n{\n    struct TopologyMapStats;\n}\n\nstruct TopologyMap\n{\n    nvrhi::BufferHandle subpatchTreesArraysBuffer;\n    nvrhi::BufferHandle patchPointIndicesArraysBuffer;\n    nvrhi::BufferHandle stencilMatrixArraysBuffer;\n    nvrhi::BufferHandle plansBuffer;\n\n    donut::engine::DescriptorHandle subpatchTreesDescriptor;\n    donut::engine::DescriptorHandle patchPointIndicesDescriptor;\n    donut::engine::DescriptorHandle stencilMatrixDescriptor;\n    donut::engine::DescriptorHandle plansDescriptor;\n\n    std::unique_ptr<OpenSubdiv::OPENSUBDIV_VERSION::Tmr::TopologyMap>\n        aTopologyMap;\n\n    TopologyMap(const TopologyMap& other) = delete;\n    TopologyMap(TopologyMap&& other) = delete;\n\n    TopologyMap(std::unique_ptr<OpenSubdiv::OPENSUBDIV_VERSION::Tmr::TopologyMap>\n        atopologyMap);\n\n    void InitDeviceData(std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTable, nvrhi::ICommandList* commandList, bool keepHostData = true);\n\n    // statistics\n\n    struct SubdivisionPlanStats\n    {\n        uint32_t plansCount = 0;\n        size_t plansByteSize = 0;\n\n        uint32_t regularFacePlansCount = 0; // plans created without quadrangulation\n\n        uint32_t maxFaceSize = 0;\n        uint32_t sharpnessCount = 0;\n        float sharpnessMax = 0.f;\n\n        uint32_t stencilCountMin = ~uint32_t(0);\n        uint32_t stencilCountMax = 0;\n        float stencilCountAvg = 0.f;\n        std::vector<uint32_t> stencilCountHistogram;\n    };\n\n    static stats::TopologyMapStats ComputeStatistics(\n        const OpenSubdiv::OPENSUBDIV_VERSION::Tmr::TopologyMap& topologyMap,\n        int histogramSize = 50);\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/subdivision/vertex.h",
    "content": "//\n//   Copyright 2024 Nvidia\n//\n//   Licensed under the Apache License, Version 2.0 (the \"Apache License\")\n//   with the following modification; you may not use this file except in\n//   compliance with the Apache License and the following modification to it:\n//   Section 6. Trademarks. is deleted and replaced with:\n//\n//   6. Trademarks. This License does not grant permission to use the trade\n//      names, trademarks, service marks, or product names of the Licensor\n//      and its affiliates, except as required to comply with Section 4(c) of\n//      the License and to reproduce the content of the NOTICE file.\n//\n//   You may obtain a copy of the Apache License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the Apache License with the above modification is\n//   distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n//   KIND, either express or implied. See the Apache License for the specific\n//   language governing permissions and limitations under the Apache License.\n//\n\n#pragma once\n\n#ifdef __cplusplus\n#include <donut/core/math/math.h>\n#include <cstdint>\n\nusing namespace donut::math;\n#endif\n\nstatic const uint32_t kPatchSize = 16;\n\nstruct LimitFrame\n{\n    float3 p;\n    float3 deriv1;\n    float3 deriv2;\n\n    void Clear()\n    {\n        p = float3(0, 0, 0);\n        deriv1 = float3(0, 0, 0);\n        deriv2 = float3(0, 0, 0);\n    }\n\n    void AddWithWeight(float3 src,\n        float weight, float d1Weight, float d2Weight)\n    {\n        p += weight * src;\n        deriv1 += d1Weight * src;\n        deriv2 += d2Weight * src;\n    }\n};\n\n// Texture coordinate with partial derivs w.r.t the parametric U and V directions of the surface\nstruct TexCoordLimitFrame\n{\n    float2 uv;\n    float2 deriv1;  // (dST/du)\n    float2 deriv2;  // (dST/du)\n\n    void Clear()\n    {\n        uv = float2(0,0);\n        deriv1 = float2(0,0);\n        deriv2 = float2(0,0);\n    }\n\n    void AddWithWeight(float2 src, float weight, float du_weight, float dv_weight)\n    {\n        uv += weight * src;\n        deriv1 += du_weight * src;\n        deriv2 += dv_weight * src;\n    }\n};"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/box3.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef RTXMG_BOX3_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RTXMG_BOX3_H\n\nstruct Box3\n{\n    // Alignment for constant buffer\n    float3 m_min; \n    float pad0;\n    float3 m_max; \n    float pad1;\n\n    void Init()\n    {\n        m_min = float3(1e37f, 1e37f, 1e37f);\n        m_max = float3(-1e37f, -1e37f, -1e37f);\n        pad0 = 0; pad1 = 0;\n    }\n\n    void Init(float3 v0, float3 v1, float3 v2)\n    {\n        m_min = min(v0, min(v1, v2));\n        m_max = max(v0, max(v1, v2));\n    }\n\n    void Include(float3 p)\n    {\n        m_min = min(m_min, p);\n        m_max = max(m_max, p);\n    }\n\n    float3 Extent()\n    {\n        return m_max - m_min;\n    }\n\n    bool Valid()\n    {\n        return m_min.x <= m_max.x &&\n            m_min.y <= m_max.y &&\n            m_min.z <= m_max.z;\n    }\n\n#ifdef __cplusplus\n    Box3(const donut::math::box3& b)\n    {\n        m_min = b.m_mins;\n        m_max = b.m_maxs;\n    }\n\n    Box3()\n    {\n        Init();\n    }\n#endif\n};\n\n#if defined(__cplusplus)\nstatic_assert(sizeof(Box3) % 16 == 0);\n#elif defined(TARGET_D3D12)\n_Static_assert(sizeof(Box3) % 16 == 0, \"Must be 16 byte aligned for constant buffer\");\n#endif\n\n#endif // RTXMG_BOX3_H"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/buffer.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <donut/core/log.h>\n#include <donut/core/math/math.h>\n\n#include <nvrhi/utils.h>\n#include <vector>\n\n#include <rtxmg/utils/vectorlog.h>\n\nnvrhi::BufferDesc GetGenericDesc(size_t nElements, uint32_t elementSize, const char* name, nvrhi::Format format = nvrhi::Format::UNKNOWN);\nnvrhi::BufferDesc GetReadbackDesc(const nvrhi::BufferDesc& desc);\n\ninline nvrhi::BufferHandle CreateBuffer(const nvrhi::BufferDesc& desc, nvrhi::IDevice* device)\n{\n    nvrhi::BufferHandle buffer = device->createBuffer(desc);\n    return buffer;\n}\n\ninline nvrhi::BufferHandle CreateBuffer(size_t nElements, uint32_t elementSize, const char* name, nvrhi::IDevice *device, nvrhi::Format format = nvrhi::Format::UNKNOWN)\n{\n    return CreateBuffer(GetGenericDesc(nElements, elementSize, name, format), device);\n}\n\ntemplate <typename T>\nnvrhi::BufferHandle\nCreateAndUploadBuffer(const std::vector<T>& data, const nvrhi::BufferDesc &desc,\n    nvrhi::ICommandList* commandList)\n{\n    assert(desc.byteSize > 0);\n    nvrhi::BufferHandle buffer = commandList->getDevice()->createBuffer(desc);\n\n    size_t minSize = std::min(desc.byteSize, data.size() * sizeof(T));\n    if (minSize > 0)\n    {\n        commandList->writeBuffer(buffer, data.data(), minSize);\n    }\n    return buffer;\n}\n\ntemplate <typename T>\nnvrhi::BufferHandle\nCreateAndUploadBuffer(const std::vector<T>& data, const char* name,\n    nvrhi::ICommandList* commandList, nvrhi::Format format = nvrhi::Format::UNKNOWN)\n{\n    nvrhi::BufferDesc desc = GetGenericDesc(data.size(), sizeof(T), name, format);\n    return CreateAndUploadBuffer(data, desc, commandList);\n}\n\nvoid DownloadBuffer(nvrhi::IBuffer* src, void* dest, nvrhi::IBuffer* staging, bool async, nvrhi::ICommandList* commandList);\n\ntemplate <typename T>\nvoid DownloadBuffer(nvrhi::IBuffer* src, std::vector<T>& vec, nvrhi::IBuffer* staging, bool async, nvrhi::ICommandList* commandList)\n{\n    size_t nelems = src->getDesc().byteSize / sizeof(T);\n    vec.clear();\n    vec.resize(nelems);\n\n    DownloadBuffer(src, (void*)&vec[0], staging, async, commandList);\n}\n\ntemplate<typename T>\nclass RTXMGBuffer\n{\nprotected:\n    nvrhi::BufferHandle m_buffer;\n    nvrhi::BufferHandle m_readbackBuffer;\npublic:\n    typedef vectorlog::OutputLambda<T>::Type OutputLambdaType;\n    typedef T ElementType;\n     \n    operator nvrhi::BufferHandle() const { return m_buffer; }\n    operator nvrhi::IBuffer*() { return m_buffer.Get(); }\n    operator nvrhi::IBuffer&() { return *m_buffer.Get(); }\n    nvrhi::BufferHandle GetBuffer() const { return m_buffer; }\n\n    size_t GetElementBytes() const { return sizeof(T); }\n    uint32_t GetNumElements() const { return m_buffer ? uint32_t(m_buffer->getDesc().byteSize / sizeof(T)) : 0; }\n    size_t GetBytes() const { return m_buffer ? m_buffer->getDesc().byteSize : 0u; }\n    nvrhi::GpuVirtualAddress GetGpuVirtualAddress() const { return m_buffer->getGpuVirtualAddress(); }\n    void Release() { m_buffer = nullptr; }\n\n    void Create(const nvrhi::BufferDesc& desc, nvrhi::IDevice* device)\n    {\n        if (GetBytes() >= desc.byteSize)\n        {\n            // reuse\n            return;\n        }\n        m_buffer = device->createBuffer(desc);\n    }\n\n    void Create(size_t nElements, const char* name, nvrhi::IDevice* device, nvrhi::Format format = nvrhi::Format::UNKNOWN)\n    {\n        auto desc = GetGenericDesc(nElements, sizeof(T), name, format);\n        Create(desc, device);\n    }\n\n    RTXMGBuffer() {}\n    RTXMGBuffer(const nvrhi::BufferDesc& desc, nvrhi::IDevice* device)\n    {\n        Create(desc, device);\n    }\n\n    void Upload(const std::vector<T>& data, nvrhi::ICommandList* commandList) const\n    {\n        if (data.size() > 0)\n        {\n            commandList->writeBuffer(m_buffer, data.data(), data.size() * sizeof(T));\n        }\n    }\n\n    void UploadElement(const T& data, uint32_t index, nvrhi::ICommandList* commandList) const\n    {\n        commandList->writeBuffer(m_buffer, &data, sizeof(T), index * sizeof(T));\n    }\n\n    std::vector<T> Download(nvrhi::ICommandList* commandList, bool async = false)\n    {\n        auto readBackDesc = GetReadbackDesc(m_buffer->getDesc());\n        if (!m_readbackBuffer || readBackDesc.byteSize > m_readbackBuffer->getDesc().byteSize)\n        {\n            m_readbackBuffer = commandList->getDevice()->createBuffer(readBackDesc);\n        }\n        \n        std::vector<T> outData;\n        DownloadBuffer<T>(m_buffer, outData, m_readbackBuffer, async, commandList);\n\n        return outData;\n    }\n\n    void OutputStream(nvrhi::ICommandList* commandList, OutputLambdaType outputElementLambda, std::ostream* optOutputStream = nullptr, vectorlog::FormatOptions options = {})\n    {\n        auto data = Download(commandList);\n\n        if (options.header)\n        {\n            std::stringstream ss;\n\n            const nvrhi::BufferDesc& desc = m_buffer->getDesc();\n            size_t numElements = data.size();\n\n            ss << desc.debugName << \"<\" << typeid(T).name() << \">\"\n                << \"(n:\" << data.size() << \" bytes: \" << data.size() * sizeof(data[0]) << \")\"\n                << \" printing \" << options.startIndex << \" to \" << (options.startIndex + options.count);\n\n            vectorlog::EndLine(ss, optOutputStream);\n        }\n        \n        vectorlog::OutputStream(data, outputElementLambda, optOutputStream, options);\n    }\n\n    void OutputStream(nvrhi::ICommandList* commandList, std::ostream& outputStream, vectorlog::FormatOptions options = {})\n    {\n        OutputStream(commandList, vectorlog::OutputElement<T>, &outputStream, options);\n    }\n\n    void Log(nvrhi::ICommandList* commandList, OutputLambdaType outputElementLambda, vectorlog::FormatOptions options = {})\n    {\n        OutputStream(commandList, outputElementLambda, nullptr, options);\n    }\n\n    void Log(nvrhi::ICommandList* commandList, vectorlog::FormatOptions options = {})\n    {\n        Log(commandList, vectorlog::OutputElement<T>, options);\n    }\n};\n"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/constants.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef RTXMG_CONSTANTS_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RTXMG_CONSTANTS_H\n\n/* scalar functions used in vector functions */\n#ifndef M_PIf\n#define M_PIf       3.14159265358979323846f\n#endif\n#ifndef M_PI_2f\n#define M_PI_2f     1.57079632679489661923f\n#endif\n#ifndef M_1_PIf\n#define M_1_PIf     0.318309886183790671538f\n#endif\n\n#ifndef FLT_MIN\n#define FLT_MIN         1.175494351e-38\n#endif \n\n#define PI_OVER_2 (.5f * M_PIf)\n#define PI_OVER_4 (.25f * M_PIf)\n#define TWO_PI (2.f * M_PIf)\n#define INV_2PI (.5f * M_1_PIf)\n\n#define DEBUG_SURFACE -1\n\n#endif/* RTXMG_CONSTANTS_H */"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/debug.h",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#ifndef RTXMG_DEBUG_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define RTXMG_DEBUG_H \n\n#include \"rtxmg/utils/constants.h\"\n#include \"rtxmg/utils/buffer.h\"\n\nint GetUniqueFileIndex(const char* baseName, const char* extension);\nvoid WriteTexToCSV(nvrhi::ICommandList* commandList, nvrhi::ITexture* tex, char const filename[]);\nvoid WriteBufferToCSV(nvrhi::ICommandList* commandList, RTXMGBuffer<float>& buf, char const filename[], int width, int height);\n\n#define logassert(condition, message, ...) if (!(condition)) { donut::log::fatal(message, ##__VA_ARGS__); assert(false); }\n\n#endif /* RTXMG_DEBUG_H */"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/formatters.h",
    "content": "/*\n * Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n // Follows the ImPlotFormatter signature\nint HumanFormatter(double value, char* buff, int bufsize, void* = nullptr);\nint MetricFormatter(double value, char* buff, int bufsize, void* data);\nint MegabytesFormatter(double value, char* buff, int bufsize, void* = nullptr);\nint MemoryFormatter(double value, char* buff, int bufsize, void* = nullptr);\n\ntemplate<typename T>\ninline int HumanFormatter(T value, char* buff, int bufsize, void* = nullptr)\n{\n    return HumanFormatter(static_cast<double>(value), buff, bufsize);\n}\n\ntemplate<typename T>\ninline int MetricFormatter(T value, char* buff, int bufsize, void* = nullptr)\n{\n    return MetricFormatter(static_cast<double>(value), buff, bufsize);\n}\n\ntemplate<typename T>\ninline int MegabytesFormatter(T value, char* buff, int bufsize, void* = nullptr)\n{\n    return MegabytesFormatter(static_cast<double>(value), buff, bufsize);\n}\n\ntemplate<typename T>\ninline int MemoryFormatter(T value, char* buff, int bufsize, void* = nullptr)\n{\n    return MemoryFormatter(static_cast<double>(value), buff, bufsize);\n}\n"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/shader_debug.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef SHADER_DEBUG_H // using instead of \"#pragma once\" due to https://github.com/microsoft/DirectXShaderCompiler/issues/3943\n#define SHADER_DEBUG_H\n\n#define ENABLE_SHADER_DEBUG 0\n\n#ifdef __cplusplus\n#include <ostream>\n#else\n#endif\n\n // Debug pixel\nstruct ShaderDebugElement\n{\n    enum PayloadType : uint\n    {\n        PayloadType_None,\n        PayloadType_Uint,\n        PayloadType_Uint2,\n        PayloadType_Uint3,\n        PayloadType_Uint4,\n        PayloadType_Int,\n        PayloadType_Int2,\n        PayloadType_Int3,\n        PayloadType_Int4,\n        PayloadType_Float,\n        PayloadType_Float2,\n        PayloadType_Float3,\n        PayloadType_Float4\n    };\n\n    uint4 uintData;\n    float4 floatData;\n    uint payloadType;\n    uint lineNumber;\n    uint2 pad0;\n\n\n#ifdef __cplusplus\n    static bool OutputLambda(std::ostream& ss, const ShaderDebugElement& e)\n    {\n        if (e.payloadType == ShaderDebugElement::PayloadType_None)\n            return false;\n\n        ss << \"[Line:\" << std::dec << e.lineNumber << \"] \";\n\n        if (e.payloadType >= ShaderDebugElement::PayloadType_Float &&\n            e.payloadType <= ShaderDebugElement::PayloadType_Float4)\n        {\n            uint32_t numVectorElements = (e.payloadType - uint32_t(ShaderDebugElement::PayloadType_Float)) + 1;\n\n            ss << std::setprecision(12) << e.floatData.data()[0];\n            for (uint32_t i = 1; i < numVectorElements; i++)\n                ss << \", \" << e.floatData.data()[i];\n        }\n        else if (e.payloadType >= ShaderDebugElement::PayloadType_Int &&\n            e.payloadType <= ShaderDebugElement::PayloadType_Int4)\n        {\n            uint32_t numVectorElements = (e.payloadType - uint32_t(ShaderDebugElement::PayloadType_Int)) + 1;\n            ss << std::dec << static_cast<int>(e.uintData.data()[0]) << std::hex << \"(0x\" << e.uintData.data()[0] << \")\";\n            for (uint32_t i = 1; i < numVectorElements; i++)\n                ss << \", \" << std::dec << static_cast<int>(e.uintData.data()[i]) << std::hex << \"(0x\" << e.uintData.data()[i] << \")\";\n        }\n        else if (e.payloadType >= ShaderDebugElement::PayloadType_Uint &&\n            e.payloadType <= ShaderDebugElement::PayloadType_Uint4)\n        {\n            uint32_t numVectorElements = (e.payloadType - uint32_t(ShaderDebugElement::PayloadType_Uint)) + 1;\n            ss << std::dec << e.uintData.data()[0] << std::hex << \"(0x\" << e.uintData.data()[0] << \")\";\n            for (uint32_t i = 1; i < numVectorElements; i++)\n                ss << \", \" << std::dec << e.uintData.data()[i] << std::hex << \"(0x\" << e.uintData.data()[i] << \")\";\n        }\n\n        return true;\n    }\n#endif\n};\n\n#ifndef __cplusplus\n#if ENABLE_SHADER_DEBUG\nstruct ShaderDebugger\n{\n    RWStructuredBuffer<ShaderDebugElement> output;\n    uint3 predicateID;\n    uint3 currentID;\n\n    uint AllocateSlot()\n    {\n        uint bufferSize, bufferStride;\n        output.GetDimensions(bufferSize, bufferStride);\n        uint maxSize = bufferSize - 1;\n\n        uint result;\n        InterlockedAdd(output[0].payloadType, 1, result);\n        return (result % maxSize) + 1;\n    }\n\n    void _ShaderDebug(float4 value, uint lineNumber, uint payloadType, bool checkPredicate)\n    {\n        if (!checkPredicate || all(predicateID == currentID))\n        {\n            ShaderDebugElement element = (ShaderDebugElement)0;\n            element.payloadType = payloadType;\n            element.lineNumber = lineNumber;\n            element.floatData = value;\n            element.uintData = 0;\n            output[AllocateSlot()] = element;\n        }\n    }\n    void _ShaderDebug(uint4 value, uint lineNumber, uint payloadType, bool checkPredicate)\n    {\n        if (!checkPredicate || all(predicateID == currentID))\n        {\n            ShaderDebugElement element = (ShaderDebugElement)0;\n            element.payloadType = payloadType;\n            element.lineNumber = lineNumber;\n            element.floatData = 0.f;\n            element.uintData = value;\n            output[AllocateSlot()] = element;\n        }\n    }\n\n    void ShaderDebug(uint4 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(value, lineNumber, ShaderDebugElement::PayloadType_Uint4, checkPredicate);\n    }\n    void ShaderDebug(uint3 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(uint4(value, 0), lineNumber, ShaderDebugElement::PayloadType_Uint3, checkPredicate);\n    }\n    void ShaderDebug(uint2 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(uint4(value, 0, 0), lineNumber, ShaderDebugElement::PayloadType_Uint2, checkPredicate);\n    }\n    void ShaderDebug(uint value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(uint4(value, 0, 0, 0), lineNumber, ShaderDebugElement::PayloadType_Uint, checkPredicate);\n    }\n\n    void ShaderDebug(int4 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(value, lineNumber, ShaderDebugElement::PayloadType_Int4, checkPredicate);\n    }\n    void ShaderDebug(int3 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(uint4(value, 0), lineNumber, ShaderDebugElement::PayloadType_Int3, checkPredicate);\n    }\n    void ShaderDebug(int2 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(uint4(value, 0, 0), lineNumber, ShaderDebugElement::PayloadType_Int2, checkPredicate);\n    }\n    void ShaderDebug(int value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(uint4(value, 0, 0, 0), lineNumber, ShaderDebugElement::PayloadType_Int, checkPredicate);\n    }\n\n    void ShaderDebug(float4 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(value, lineNumber, ShaderDebugElement::PayloadType_Float4, checkPredicate);\n    }\n    void ShaderDebug(float3 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(float4(value, 0), lineNumber, ShaderDebugElement::PayloadType_Float3, checkPredicate);\n    }\n    void ShaderDebug(float2 value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(float4(value, 0, 0), lineNumber, ShaderDebugElement::PayloadType_Float2, checkPredicate);\n    }\n    void ShaderDebug(float value, uint lineNumber, bool checkPredicate = true)\n    {\n        _ShaderDebug(float4(value, 0, 0, 0), lineNumber, ShaderDebugElement::PayloadType_Float, checkPredicate);\n    }\n};\n\nstatic ShaderDebugger g_ShaderDebugger;\n\nstatic void InitShaderDebugger(RWStructuredBuffer<ShaderDebugElement> output, uint3 predicateID, uint3 currentID)\n{\n    g_ShaderDebugger.output = output;\n    g_ShaderDebugger.predicateID = predicateID;\n    g_ShaderDebugger.currentID = currentID;\n}\n\nstatic void InitShaderDebugger(RWStructuredBuffer<ShaderDebugElement> output, uint2 predicateID, uint2 currentID)\n{\n    InitShaderDebugger(output, uint3(predicateID, 0), uint3(currentID, 0));\n}\n\nstatic void InitShaderDebugger(RWStructuredBuffer<ShaderDebugElement> output, uint predicateID, uint currentID)\n{\n    InitShaderDebugger(output, uint3(predicateID, 0, 0), uint3(currentID, 0, 0));\n}\n\n#define SHADER_DEBUG(value) g_ShaderDebugger.ShaderDebug(value, __LINE__)\n#define SHADER_DEBUG_FORCE(value) g_ShaderDebugger.ShaderDebug(value, __LINE__, false)\n#define SHADER_DEBUG_INIT(outputBuffer, predicateID, currentID) InitShaderDebugger(outputBuffer, predicateID, currentID)\n\n#else\n#define SHADER_DEBUG(value) \n#define SHADER_DEBUG_FORCE(value)\n#define SHADER_DEBUG_INIT(outputBuffer, predicateID, currentID)\n#endif\n\n#endif // __cplusplus\n\n#endif /* SHADER_DEBUG_H */"
  },
  {
    "path": "rtxmg/include/rtxmg/utils/vectorlog.h",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#pragma once\n\n#include <donut/core/log.h>\n\n#include <iomanip>\n#include <sstream>\n#include <typeinfo>\n\ntemplate<int N>\ninline std::ostream& operator<<(std::ostream& ss, const donut::math::vector<float, N>& e)\n{\n    ss << \"{\";\n    ss << std::setprecision(12) << e.data()[0];\n    for (uint32_t i = 1; i < donut::math::vector<float, N>::DIM; i++)\n        ss << \", \" << e.data()[i];\n    ss << \"}\";\n    return ss;\n}\n\ntemplate<int N>\ninline std::ostream& operator<<(std::ostream& ss, const donut::math::vector<uint32_t, N>& e)\n{\n    ss << \"{\";\n    ss << std::dec << e.data()[0] << std::hex << \"(0x\" << e.data()[0] << \")\";\n    for (uint32_t i = 1; i < donut::math::vector<uint32_t, N>::DIM; i++)\n        ss << \", \" << std::dec << e.data()[i] << std::hex << \"(0x\" << e.data()[i] << \")\";\n    ss << \"}\";\n    return ss;\n}\n\nnamespace vectorlog\n{\n    template<typename T>\n    struct OutputLambda\n    {\n        typedef const std::function<bool(std::ostream& ss, const T& e)>& Type;\n    };\n\n    struct FormatOptions\n    {\n        bool wrap = true;\n        bool header = true; // automatic header of elements\n        bool elementIndex = true; // output index of the elemnt\n\n        // specify a range\n        size_t startIndex = 0;\n        size_t count = 64;\n    };\n\n    template<typename T>\n    static bool OutputElement(std::ostream& ss, const T& e)\n    {\n        if (std::is_same<T, float>::value)\n            ss << std::setprecision(4) << e;\n        else if (std::is_same<T, uint64_t>::value)\n            ss << std::hex << \"0x\" << e;\n        else\n            ss << e;\n\n        return true;\n    }\n\n\n    static void EndLine(std::stringstream& ss, std::ostream* optOutputStream)\n    {\n        if (optOutputStream)\n        {\n            (*optOutputStream) << ss.str() << std::endl;\n        }\n        else\n        {\n            donut::log::info(ss.str().c_str());\n            ss.str(\"\");\n            ss.clear();\n        }\n    }\n\n    template<typename T>\n    static void OutputStream(const std::vector<T>& data, typename OutputLambda<T>::Type outputElementLambda, std::ostream* optOutputStream, const FormatOptions &options)\n    {\n        if (options.startIndex >= data.size())\n            return;\n\n        std::stringstream ss;\n\n        auto begin = data.begin();\n        auto iter = begin + options.startIndex;\n        auto end = begin + std::min(size_t(options.startIndex + options.count), data.size());\n        uint32_t numDigits = (end - iter) > 0 ? uint32_t(ceilf(log10f(float(end - iter)))) : 1;\n        auto outputIndex = [&ss, &options, &numDigits](size_t index)\n            {\n                if (options.elementIndex)\n                {\n                    ss << \"[\" << std::dec << std::setw(numDigits) << index << \"] \";\n                }\n            };\n\n        if (options.wrap)\n        {\n            std::ostream::pos_type startLength = ss.tellp();\n\n            bool continueOutput = true;\n            if (iter != end)\n            {\n                outputIndex(iter - begin);\n                continueOutput = outputElementLambda(ss, *iter++);\n            }\n            while (iter != end && continueOutput)\n            {\n                std::ostream::pos_type currentLength = ss.tellp() - startLength;\n                if (currentLength > 80u)\n                {\n                    EndLine(ss, optOutputStream);\n                    startLength = ss.tellp();\n                }\n                else\n                {\n                    ss << \", \";\n                }\n                outputIndex(iter - begin);\n                continueOutput = outputElementLambda(ss, *iter++);\n            }\n\n            std::ostream::pos_type currentLength = ss.tellp() - startLength;\n            if (currentLength > 0u)\n            {\n                EndLine(ss, optOutputStream);\n                startLength = ss.tellp();\n            }\n        }\n        else\n        {\n            bool continueOutput = true;\n            while (iter != end && continueOutput)\n            {\n                outputIndex(iter - begin);\n                continueOutput = outputElementLambda(ss, *iter++);\n                EndLine(ss, optOutputStream);\n            }\n        }\n    }\n\n    template<typename T>\n    static void Log(const std::vector<T>& data, typename OutputLambda<T>::Type outputElementLambda, FormatOptions options = {})\n    {\n        OutputStream(data, outputElementLambda, nullptr, options);\n    }\n\n    template<typename T>\n    static void Log(const std::vector<T>& data, FormatOptions options = {})\n    {\n        OutputStream(data, vectorlog::OutputElement<T>, nullptr, options);\n    }\n}\n\n"
  },
  {
    "path": "rtxmg/profiler/CMakeLists.txt",
    "content": "set(lib profiler)\n\nfile(GLOB sources \"*.cpp\" \"../include/rtxmg/${lib}/*.h\")\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include\"\n)\ntarget_link_libraries(${lib} donut_engine imgui implot osd_lite_static)\nset_target_properties(${lib} PROPERTIES FOLDER RTXMG)\n"
  },
  {
    "path": "rtxmg/profiler/gui.cpp",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#include <imgui_internal.h>\n#include <implot.h>\n#include <implot_internal.h>\n\n#include <cassert>\n#include <cmath>\n#include <type_traits>\n#include <variant>\n\n#include <donut/core/math/math.h>\n\n#include \"rtxmg/profiler/gui.h\"\n#include \"rtxmg/utils/formatters.h\"\n\nusing namespace donut::math;\n\n\n// clang-format on\n\n// expects a [0, 1] normalized value \nstatic ImVec4 heatmapColor( float value )\n{\n    static float3 colors[] = { {0.f, 1.f, 0.f}, { 1., 1.f, 0.f}, { 1.f, 0.f, 0.f } };\n\n    uint8_t i0 = 0;\n    uint8_t i1 = 0;\n    float m_fp = 0.f;\n\n    if( value <= 0.f )\n        return ImVec4( .5f, .5f, .5f, 1.f );\n    else if( value >= 1.f )\n        i0 = i1 = (uint8_t) std::size( colors ) - 1;\n    else\n    {\n        m_fp = value * ( std::size( colors ) - 1 );\n        i0 = (uint8_t)std::floor( m_fp );\n        i1 = i0 + 1;\n        m_fp = m_fp - float( i0 );\n    }\n\n    float3 c = colors[i0] + m_fp * ( colors[i1] - colors[i0] );\n    return ImVec4( c.x, c.y, c.z, 1.f );\n}\n\nvoid ProfilerGUI::BuildControllerUI( ImFont* iconicFont, ImPlotContext *plotContext )\n{\n    ImGui::SetNextWindowPos(controllerWindow.pos, ImGuiCond_Always, controllerWindow.pivot);\n    ImGui::SetNextWindowSize(controllerWindow.size);\n    ImGui::SetNextWindowBgAlpha(.65f);\n\n    ImGui::Begin(\"ProfilerController\", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar);\n\n    ImVec2 itemSize = ImGui::GetItemRectSize();\n    char buf[50];\n    if (HumanFormatter(static_cast<double>(desiredTris), buf, sizeof(buf)))\n        ImGui::Text(\"Tris %s\", buf);\n    else\n        ImGui::Text(\"Too many !\");\n\n    ImGui::PushFont(iconicFont);\n\n    bool buttonState = displayGraphWindow;\n    if (buttonState)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.f, 0.f, 0.f, 1.f));\n    if (ImGui::Button((char const*)(u8\"\\ue0ae\" \"## controller button\"), { 0.f, itemSize.y }))\n    {\n        displayGraphWindow = !buttonState;\n    }\n    if (buttonState)\n        ImGui::PopStyleColor();\n\n    ImGui::PopFont();\n\n    ImGui::SameLine(32.f);\n\n    if (fps >= 0)\n        ImGui::Text(\"FPS   % 5d\", fps);\n    else\n        ImGui::Text(\"FPS    ----\");\n\n    controllerWindow.size = ImGui::GetWindowSize();\n\n    ImGui::End();\n\n    ImPlot::SetCurrentContext(plotContext);\n}\n\nvoid ProfilerGUI::BuildFrequencySelectorUI()\n{\n    Profiler& profiler = Profiler::Get();\n\n    int rate = 0;\n\n    if (profiler.recordingFrequency >= 120)\n        rate = 5;\n    else if (profiler.recordingFrequency >= 60)\n        rate = 4;\n    else if (profiler.recordingFrequency >= 30)\n        rate = 3;\n    else if (profiler.recordingFrequency >= 10)\n        rate = 2;\n    else if (profiler.recordingFrequency >= 1)\n        rate = 1;\n\n    ImVec2 size = ImGui::GetWindowSize();\n\n    ImGui::SameLine(size[0] - (64 + 10));\n    ImGui::PushItemWidth(64);\n    if (ImGui::Combo(\"##SamplingFrequency\", &rate, \"---Hz\\0001Hz\\00010Hz\\00030Hz\\00060Hz\\000120Hz\\0\"))\n    {\n        switch (rate)\n        {\n            case 0: profiler.recordingFrequency = -1; break;\n            case 1: profiler.recordingFrequency = 1; break;\n            case 2: profiler.recordingFrequency = 10; break;\n            case 3: profiler.recordingFrequency = 30; break;\n            case 4: profiler.recordingFrequency = 60; break;\n            case 5: profiler.recordingFrequency = 120; break;\n        }\n    }\n    if (ImGui::IsItemHovered())\n        ImGui::SetTooltip(\n            \"Profiling rate: frequency (in Hertz) at which samples are recorded each second\\n\"\n            \"or unconstrained records every frame.\");\n}\n\n"
  },
  {
    "path": "rtxmg/profiler/profiler.cpp",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n\n#include \"rtxmg/profiler/profiler.h\"\n#include \"rtxmg/profiler/stopwatch.h\"\n\n\n#include <cassert>\n#include <type_traits>\n#include <variant>\n\n// clang-format on\nstatic Profiler *s_profiler = nullptr;\n\nProfiler& Profiler::Get()\n{\n    if (!s_profiler)\n        s_profiler = new Profiler();\n    return *s_profiler;\n}\n\nvoid Profiler::Terminate()\n{\n    delete s_profiler;\n}\n\nusing CPUTimer = Profiler::Timer<StopwatchCPU>;\nusing GPUTimer = Profiler::Timer<StopwatchGPU>;\n\ntemplate <>\nCPUTimer& Profiler::Timer<StopwatchCPU>::Resolve()\n{\n    if( auto e = Elapsed() )\n        PushBack( *e );\n    return *this;\n}\ntemplate <>\nGPUTimer& Profiler::Timer<StopwatchGPU>::Resolve()\n{\n    if( auto e = ElapsedAsync() )\n        PushBack( *e );\n    return *this;\n}\n\ntemplate <>\nCPUTimer& Profiler::Timer<StopwatchCPU>::Profile()\n{\n    if (Profiler::Get().IsRecording())\n    {\n        auto e = Elapsed();\n        PushBack( e ? *e : 0.f );\n    }\n    return *this;\n}\ntemplate <>\nGPUTimer& Profiler::Timer<StopwatchGPU>::Profile()\n{\n    if (Profiler::Get().IsRecording())\n    {\n        auto e = ElapsedAsync();\n        if (e)\n        {\n            PushBack(*e);\n        }\n    }\n    return *this;\n}\n\nvoid Profiler::FrameStart( std::chrono::steady_clock::time_point time )\n{\n    int frequency = recordingFrequency;\n    if( frequency > 0 )\n    {\n        double period = 1000. / double( frequency );\n\n        m_isRecording = std::chrono::duration<double, std::milli>( time - m_prevTime ).count() >= period\n                        || m_prevTime == std::chrono::steady_clock::time_point{};\n    }\n    else if( frequency < 0 )\n        m_isRecording = true;\n    else\n        m_isRecording = false;\n\n    if (m_isRecording)\n        m_prevTime = time;\n}\n\nvoid Profiler::FrameEnd()\n{\n\n}\n\nvoid Profiler::FrameResolve()\n{\n    auto resolveTimers = []( auto& timers ) {\n        for( auto& timer : timers )\n            timer->Resolve();\n    };\n\n    resolveTimers( m_cpuTimers );\n\n    if( !m_gpuTimers.empty() )\n    {\n        resolveTimers( m_gpuTimers );\n    }\n}\n"
  },
  {
    "path": "rtxmg/profiler/statistics.cpp",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include <numeric>\n#include <opensubdiv/tmr/topologyMap.h>\n\n#include <cmath>\n#include <locale>\n#include <string>\n#include <sstream>\n\n#include <imgui_internal.h>\n#include <implot.h>\n\n#include \"rtxmg/profiler/gui.h\"\n#include \"rtxmg/profiler/statistics.h\"\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/utils/formatters.h\"\n\nnamespace stats {\n\n    constexpr ImPlotAxisFlags flags = ImPlotAxisFlags_AutoFit;\n    constexpr double          yref = 0;  // std::numeric_limits<double>::lowest();\n    constexpr double          xscale = 1.0;\n    constexpr double          xstart = 0;\n\n    FrameSamplers        frameSamplers;\n    ClusterAccelSamplers clusterAccelSamplers;\n    MemUsageSamplers     memUsageSamplers;\n    EvaluatorSamplers evaluatorSamplers;\n\n    void FrameSamplers::BuildUI(ImFont *iconicFont, ImPlotContext *plotContext) const\n    {\n        constexpr int stride = (int)sizeof(float);\n\n        const float fontScale = ImGui::GetIO().FontGlobalScale;\n\n        enum class GraphMode : int {\n            Overview = 0,\n            HiZ\n        };\n        static GraphMode mode = GraphMode::Overview;\n        ImGui::PushItemWidth(125);\n        ImGui::Combo(\"Graph modes\", reinterpret_cast<int*>(&mode), \"Overview\\0Hierarchical-Z\\0\");\n        ImGui::PopItemWidth();\n\n        std::array<GPUTimer*, 3> timers = { nullptr, nullptr, nullptr };\n\n        switch (mode)\n        {\n        case GraphMode::Overview: {\n            timers[0] = &gpuFrameTime.Profile();\n            timers[1] = &gpuRenderTime.Profile();\n            timers[2] = &gpuDenoiserTime.Profile();\n            computeMotionVectorsTimer.Profile();\n        } break;\n        case GraphMode::HiZ: {\n            timers[0] = &zRenderPassTime.Profile();\n            timers[1] = &zReprojectionTime.Profile();\n            timers[2] = &hiZRenderTime.Profile();\n        } break;\n        default:\n            return;\n        }\n\n        // Implot appears to be having issues if the arrays of data have different sizes & offsets\n        assert(timers[0]->size() == timers[1]->size() && timers[0]->size() == timers[2]->size());\n\n        if (ImPlot::BeginPlot(\"##timers2\", ImVec2(-1, 150 * fontScale)))\n        {\n            ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations);\n            ImPlot::SetupAxisLimits(ImAxis_X1, 0, static_cast<double>(timers[0]->size()), ImGuiCond_Always);\n\n            constexpr bool autofit = false;\n\n            if constexpr (autofit)\n            {\n                // ImPlot autofit is a little wiggly - exploring alternatives below\n                ImPlot::SetupAxis(ImAxis_Y1, timers[0]->name.c_str(), ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_AuxDefault);\n                ImPlot::SetupAxis(ImAxis_Y2, timers[1]->name.c_str(), ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_AuxDefault);\n                ImPlot::SetupAxis(ImAxis_Y3, timers[2]->name.c_str(), ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_AuxDefault);\n            }\n            else\n            {\n                ImPlot::SetupAxis(ImAxis_Y1, \"Time (ms)\");\n                ImPlot::SetupAxis(ImAxis_Y2, \"##hidden1\", ImPlotAxisFlags_NoDecorations);\n                ImPlot::SetupAxis(ImAxis_Y3, \"##hidden2\", ImPlotAxisFlags_NoDecorations);\n\n                float vmax = timers[0]->RunningAverage() * 1.75f;\n                if (vmax < 1e-6)\n                    vmax = timers[1]->RunningAverage() * 1.75f;\n\n                // Constrain both Y axes to the same range for better readability\n                ImPlot::SetupAxisLimits(ImAxis_Y1, 0., vmax, ImPlotCond_Always);\n                ImPlot::SetupAxisLimits(ImAxis_Y2, 0., vmax, ImPlotCond_Always);\n                ImPlot::SetupAxisLimits(ImAxis_Y3, 0., vmax, ImPlotCond_Always);\n            }\n\n\n            for (uint8_t i = 0; i < timers.size(); ++i)\n            {\n                if (!timers[i])\n                    continue;\n\n                ImPlot::SetAxes(ImAxis_X1, ImAxis_Y1 + i);\n\n                ImPlot::PlotLine(timers[i]->name.c_str(), timers[i]->data(), (int)timers[i]->size(),\n                    xscale, xstart, ImPlotShadedFlags_None, timers[i]->Offset(), stride);\n            }\n\n            ImPlot::EndPlot();\n            if (ImGui::IsItemHovered())\n            {\n                switch (mode)\n                {\n                case GraphMode::Overview:\n                    ImGui::SetTooltip(\n                        \"GPU timers:\\n\"\n                        \"  - Frame: %.4fms\\n\"\n                        \"  - Pathtrace: %.4fms\\n\"\n                        \"  - Motion Vectors: %.4fms\\n\"\n                        \"  - Denoiser: %.4fms\\n\",\n                        gpuFrameTime.RunningAverage(),\n                        gpuRenderTime.RunningAverage(),\n                        computeMotionVectorsTimer.RunningAverage(),\n                        gpuDenoiserTime.RunningAverage());\n                break;\n                default:\n                    return;\n                }\n            }\n\n        }\n\n        // note: only one of the profiler tabs is active at a time, so we have to call\n        // profile() on these timers to Update these samplers\n\n        static Sampler<float> trisPerSec;\n        if (Profiler::Get().IsRecording())\n        {\n            auto const& clusterTiling = clusterAccelSamplers.clusterTilingTime.Profile();\n            auto const& fillClusters = clusterAccelSamplers.fillClustersTime.Profile();\n            auto const& clas = clusterAccelSamplers.buildClasTime.Profile();\n            auto const& blas = clusterAccelSamplers.buildBlasTime.Profile();\n            float sumTime = (clusterTiling.latest + fillClusters.latest + clas.latest + blas.latest);\n\n            uint32_t ntris = clusterAccelSamplers.numTriangles.latest;\n\n            trisPerSec.PushBack(static_cast<float>(1000. * double(ntris) / double(sumTime)));\n        }\n\n        if (ImPlot::BeginPlot(\"BVH Throughput\", ImVec2(-1, 150 * fontScale)))\n        {\n            ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations);\n            ImPlot::SetupAxisLimits(ImAxis_X1, 0, (double)trisPerSec.size(), ImGuiCond_Always);\n\n            ImPlot::SetupAxis(ImAxis_Y1, \"Tris / Sec\", ImPlotAxisFlags_AutoFit);\n            ImPlot::SetupAxisFormat(ImAxis_Y1, HumanFormatter, nullptr);\n\n            ImPlot::PlotShaded(trisPerSec.name.c_str(), trisPerSec.data(), (int)trisPerSec.size(), 0.f, xscale, xstart,\n                ImPlotShadedFlags_None, trisPerSec.Offset(), stride);\n\n            ImPlot::EndPlot();\n\n            if (ImGui::IsItemHovered())\n                ImGui::SetTooltip(\n                    \"Number of triangles processed per second.\\n\\n\"\n                    \"Processing includes:\\n\"\n                    \"  - surface edge-metric evaluation\\n\"\n                    \"  - catmark limit surface evaluation\\n\"\n                    \"  - displacement\\n\"\n                    \"  - tessellation\\n\"\n                    \"  - cluster fill\\n\"\n                    \"  - BVH build\\n\");\n        }\n    }\n\n    void EvaluatorSamplers::BuildUI(ImFont *iconicFont, ImPlotContext *plotContext)\n    {\n        if (hasBadTopology)\n        {\n            ImGui::PushFont(iconicFont);\n            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.223f, 0.325f, 0.447f, 1.f));\n            ImGui::SeparatorText((char const*)(u8\"\\ue08F\" \"## recommendations\"));\n            ImGui::PopStyleColor();\n            ImGui::PopFont();\n\n            ImGui::TextWrapped(\"Switch to the 'Topology Quality' color mode in the settings \"\n                \"window to visualize problem areas in the mesh. Areas in red are in need of attention\");\n\n            m_topologyQualityButtonPressed = ImGui::Button(\"Topology Quality\");\n            if (ImGui::IsItemHovered() && ImGui::GetCurrentContext()->HoveredIdTimer > .5f)\n                ImGui::SetTooltip(\"Switches the color mode to 'Toplogy Quality'.\");\n            ImGui::Spacing();\n        }\n        char buf[32];\n\n        const float fontScale = ImGui::GetIO().FontGlobalScale;\n\n        auto buildRow = [&buf]<typename T>(char const* name, T value, char const* tooltip = nullptr, bool displayMB = false)\n        {\n            ImGui::TableNextRow();\n            ImGui::TableSetColumnIndex(0);\n            ImGui::Text(\"%s\", name);\n            ImGui::TableSetColumnIndex(1);\n            if constexpr (std::is_same_v<T, size_t>)\n            {\n                if (displayMB)\n                    MegabytesFormatter(static_cast<double>(value), buf, (int) std::size(buf));\n                else\n                    MemoryFormatter(static_cast<double>(value), buf, (int) std::size(buf));\n                ImGui::Text(\"%s\", buf);\n            }\n            else if constexpr (std::is_same_v<T, float>)\n                ImGui::Text(\"%.1f\", value);\n            else if constexpr (std::is_integral_v<T>)\n                ImGui::Text(\"%d\", int64_t(value));\n            if (tooltip && ImGui::IsItemHovered())\n                ImGui::SetTooltip(\"%s\", tooltip);\n        };\n\n        for (uint32_t i = 0; i < (uint32_t)surfaceTableStats.size(); ++i)\n            surfaceTableStats[i].BuildUI(iconicFont, plotContext, i);\n\n        ImGui::Spacing();\n\n        if (ImGui::CollapsingHeader(\"TopologyMap\", ImGuiTreeNodeFlags_DefaultOpen))\n        {\n            ImGui::BeginTable(\"Topology Map\", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoHostExtendX);\n            {\n                static const float kColWidth0 = 200 * fontScale;\n                static const float kColWidth1 = 80 * fontScale;\n\n                ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthFixed, kColWidth0);\n                ImGui::TableSetupColumn(\"Value\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n                ImGui::TableHeadersRow();\n\n                buildRow(\"PSL mean\", topologyMapStats.pslMean);\n                buildRow(\"Hash count\", (int64_t)topologyMapStats.hashCount);\n                buildRow(\"Address space\", (int64_t)topologyMapStats.addressCount);\n                buildRow(\"load factor\", topologyMapStats.loadFactor);\n                ImGui::TableNextRow(ImGuiTableRowFlags_Headers, 2.5f);\n                //buildRow( \"Patch-points max\", (int64_t)topologyMap.patchPointsMax );\n                buildRow(\"Subdivision plans count\", (int64_t)topologyMapStats.plansCount);\n                buildRow(\"Stencil matrix row count min\", (int64_t)topologyMapStats.stencilCountMin);\n                buildRow(\"Stencil matrix row count max\", (int64_t)topologyMapStats.stencilCountMax);\n                buildRow(\"Stencil matrix row count avg\", (int64_t)topologyMapStats.stencilCountAvg);\n                buildRow(\"Memory use\", topologyMapStats.plansByteSize);\n            }\n            ImGui::EndTable();\n\n            ImGui::SameLine();\n\n            if (ImPlot::BeginPlot(\"##TopomapStencilHistogram\", ImVec2(-1, 174 * fontScale), ImPlotFlags_NoMouseText))\n            {\n                auto const& values = topologyMapStats.stencilCountHistogram;\n\n                ImPlotFormatter formatter = [](double value, char* buff, int size, void* user_data) -> int\n                    {\n                        auto const* samplers = reinterpret_cast<EvaluatorSamplers const*>(user_data);\n                        uint32_t    min = samplers->topologyMapStats.stencilCountMin;\n                        uint32_t    max = samplers->topologyMapStats.stencilCountMax;\n                        uint32_t    count = (uint32_t)samplers->topologyMapStats.stencilCountHistogram.size();\n                        if (uint32_t range = max - min; range > 0 && count > 0)\n                            value = min + (value / count) * range;\n                        else\n                            value = min;\n                        return snprintf(buff, size_t(size), \"%d\", (int)value);\n                    };\n\n                ImPlot::SetupAxis(ImAxis_X1, \"Num patch points\", ImPlotAxisFlags_AutoFit);\n                ImPlot::SetupAxisFormat(ImAxis_X1, formatter, (void*)this);\n                ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_AutoFit);\n                ImPlot::PlotBars(\"Num Plans\", values.data(), static_cast<int>(values.size()), 1.0, 0.5, ImPlotBarsFlags_None);\n\n                ImPlot::EndPlot();\n            }\n        }\n    }\n\n\n    void ClusterAccelSamplers::BuildUI(ImFont *iconicFont, ImPlotContext *plotContext) const\n    {\n        constexpr int stride = (int)sizeof(uint32_t);\n\n        const float fontScale = ImGui::GetIO().FontGlobalScale;\n\n        if (ImPlot::BeginPlot(\"##accel_builder_tess\", ImVec2(-1, 150 * fontScale)))\n        {\n            auto const& buildBlas = buildBlasTime.Profile();\n            auto const& fillClusters = fillClustersTime.Profile();\n            auto const& clusterTiling = clusterTilingTime.Profile();\n            auto const& buildClas = buildClasTime.Profile();\n            ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations);\n            float vmax = 3.f;\n            if (float ravg = std::max(buildClas.RunningAverage(),\n                std::max(buildBlas.RunningAverage(), std::max(clusterTiling.RunningAverage(), fillClusters.RunningAverage())));\n                ravg > (vmax * .01f))\n                vmax = ravg * 2.f;\n            ImPlot::SetupAxisLimits(ImAxis_Y1, 0., vmax, ImPlotCond_Always);\n\n            ImPlot::SetupAxisLimits(ImAxis_X1, 0, static_cast<double>(buildBlas.size()), ImGuiCond_Always);\n\n            ImPlot::SetupAxis(ImAxis_Y1, \"Time (ms)\", ImPlotAxisFlags_AutoFit);\n            ImPlot::SetAxes(ImAxis_X1, ImAxis_Y1);\n            auto plot = [](auto& series)\n                {\n                    ImPlot::PlotLine(series.name.c_str(), series.data(), (int)series.size(), xscale, xstart,\n                                      ImPlotShadedFlags_None, static_cast<int>(series.Offset()), stride);\n                };\n            plot(clusterTiling);\n            plot(fillClusters);\n            plot(buildClas);\n            plot(buildBlas);\n            ImPlot::EndPlot();\n            if (ImGui::IsItemHovered())\n                ImGui::SetTooltip(\n                    \"GPU timers:\\n\\n\"\n                    \"  - Cluster Tiling: %.4fms tessellation metric\\n\"\n                    \"    + limit surface evaluation prep\\n\\n\"\n                    \"  - Fill Clusters: %.4fms subdivision surface\\n\"\n                    \"    limit evaluation + vertex writing.\\n\\n\"\n                    \"  - CLAS build: %.4fms CLAS build time.\\n\\n\"\n                    \"  - BLAS build: %.4fms BLAS from CLAS build time\",\n                    clusterTiling.RunningAverage(),\n                    fillClusters.RunningAverage(),\n                    buildClas.RunningAverage(),\n                    buildBlas.RunningAverage());\n        }\n        ImGui::Spacing();\n\n        auto const& nt = numTriangles;\n        auto const& nc = numClusters;\n        if (ImPlot::BeginPlot(\"##accel_builder_geo\", ImVec2(-1, 150 * fontScale)))\n        {\n            ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations);\n            ImPlot::SetupAxisLimits(ImAxis_X1, 0, static_cast<double>(nt.size()), ImGuiCond_Always);\n            ImPlot::SetupAxis(ImAxis_Y1, nt.name.c_str(), ImPlotAxisFlags_AutoFit);\n            ImPlot::SetupAxisFormat(ImAxis_Y1, HumanFormatter, nullptr);\n\n            ImPlot::SetupAxis(ImAxis_X2, nullptr, ImPlotAxisFlags_NoDecorations);\n            ImPlot::SetupAxisLimits(ImAxis_X2, 0, static_cast<double>(nc.size()), ImGuiCond_Always);\n            ImPlot::SetupAxis(ImAxis_Y2, nc.name.c_str(), ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_AuxDefault);\n            ImPlot::SetupAxisFormat(ImAxis_Y2, HumanFormatter, nullptr);\n\n            ImPlot::SetAxes(ImAxis_X1, ImAxis_Y1);\n\n            //ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.5f);\n            ImPlot::PlotShaded(nt.name.c_str(), nt.data(), (int)nt.size(), yref, xscale, xstart, ImPlotShadedFlags_None,\n                                static_cast<int>(nt.Offset()), stride);\n            //ImPlot::PlotLine(nt.name.c_str(), nt.samples.data(), (int)nt.samples.m_size(), xscale, xstart, ImPlotShadedFlags_None, nt.offset(), stride);\n\n            ImPlot::SetAxes(ImAxis_X2, ImAxis_Y2);\n            //ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.5f);\n            //ImPlot::PlotShaded(nc.name.c_str(), nc.samples.data(), (int)nc.samples.m_size(),  yref, xscale, xstart, ImPlotShadedFlags_None, nc.offset(), stride);\n            ImPlot::PlotLine(nc.name.c_str(), nc.data(), (int)nc.size(), xscale, xstart, ImPlotShadedFlags_None,\n                              static_cast<int>(nc.Offset()), stride);\n\n            ImPlot::EndPlot();\n        }\n    }\n    void MemUsageSamplers::BuildUI(ImFont *iconicFont, ImPlotContext *plotContext) const\n    {\n        const float fontScale = ImGui::GetIO().FontGlobalScale;\n\n        uint32_t desiredTris = stats::clusterAccelSamplers.numTriangles.latest;\n        uint32_t desiredClusters= stats::clusterAccelSamplers.numClusters.latest;\n        uint32_t allocatedClusters = stats::clusterAccelSamplers.numClusters.max;\n        uint32_t numPixels = stats::clusterAccelSamplers.renderSize.x * stats::clusterAccelSamplers.renderSize.y;\n\n        ImGui::Spacing();\n\n        ImGui::Text(\"Render Resolution: %d x %d\", stats::clusterAccelSamplers.renderSize.x, stats::clusterAccelSamplers.renderSize.y);\n        ImGui::Text(\"Micro-triangles: %zu (%.2f per pixel)\", desiredTris, desiredTris / float(numPixels));\n        ImGui::Text(\"Clusters: %zu / %zu\", desiredClusters, allocatedClusters);\n\n        ImGui::Spacing();\n\n        ImGui::Separator();\n\n        ImGui::Spacing();\n        ImGui::Text(\"BVH\");\n        ImGui::Spacing();\n\n        ImGui::BeginTable(\"Memory Usage\", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoHostExtendX);\n        {\n            static const float kColWidth0 = 200 * fontScale;\n            static const float kColWidth1 = 80 * fontScale;\n            \n            ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthFixed, kColWidth0);\n            ImGui::TableSetupColumn(\"Required\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n            ImGui::TableSetupColumn(\"Allocated\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n            ImGui::TableSetupColumn(\"Per-uTri\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n            ImGui::TableSetupColumn(\"Per Pixel\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n            ImGui::TableSetupColumn(\"Per Cluster\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n\n            ImGui::TableHeadersRow();\n\n            char buf[32];\n            char bufMax[32];\n\n            auto buildRow = [&buf, &bufMax](char const* name, size_t bytes, size_t maxBytes, uint32_t tris, uint32_t pixels, uint32_t clusters, bool displayMB = true)\n                {\n                    bool memoryExceeded = bytes > maxBytes;\n                    if (memoryExceeded)\n                    {\n                        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));\n                    }\n                    ImGui::TableNextRow();\n                    ImGui::TableSetColumnIndex(0);\n                    ImGui::Text(\"%s\", name);\n                    ImGui::TableSetColumnIndex(1);\n                    if (displayMB)\n                    {\n                        MegabytesFormatter(static_cast<double>(bytes), buf, (int)std::size(buf));\n                        MegabytesFormatter(static_cast<double>(maxBytes), bufMax, (int)std::size(bufMax));\n                    }\n                    else\n                    {\n                        MemoryFormatter(static_cast<double>(bytes), buf, (int)std::size(buf));\n                        MemoryFormatter(static_cast<double>(maxBytes), bufMax, (int)std::size(bufMax));\n                    }\n                    \n                    ImGui::Text(\"%s\", buf);\n                    if (ImGui::IsItemHovered())\n                        ImGui::SetTooltip(\"%zu bytes\", bytes);\n\n                    ImGui::TableSetColumnIndex(2);\n                    ImGui::Text(\"%s\", bufMax);\n                    if (ImGui::IsItemHovered())\n                        ImGui::SetTooltip(\"%zu bytes\", maxBytes);\n\n                    ImGui::TableSetColumnIndex(3);\n                    if (tris)\n                        ImGui::Text(\"%.1f bits\", float(bytes) / float(tris) * 8);\n                    else\n                        ImGui::Text(\"n/a\");\n\n                    ImGui::TableSetColumnIndex(4);\n                    if (pixels)\n                        ImGui::Text(\"%.2f B\", float(bytes) / pixels);\n                    else\n                        ImGui::Text(\"n/a\");\n\n\n                    ImGui::TableSetColumnIndex(5);\n                    if (clusters)\n                    {\n                        MemoryFormatter(static_cast<double>(bytes / double(clusters)), buf, (int)std::size(buf));\n                        ImGui::Text(\"%s\", buf);\n                    }\n                    else\n                        ImGui::Text(\"n/a\");\n\n                    if (memoryExceeded)\n                    {\n                        ImGui::PopStyleColor();\n                    }\n                };\n\n            buildRow(\"Vertex buffer\", vertexBufferSize.latest, vertexBufferSize.max, desiredTris, numPixels, desiredClusters, 0);\n            buildRow(\"Vertex normals buffer\", vertexNormalsBufferSize.latest, vertexNormalsBufferSize.max, desiredTris, numPixels, desiredClusters, 0);\n            buildRow(\"Cluster AS (CLAS)\", clasSize.latest, clasSize.max, desiredTris, numPixels, desiredClusters);\n            buildRow(\"Cluster Data buffer\", clusterShadingDataSize.latest, clusterShadingDataSize.max, 0, 0, desiredClusters);\n            buildRow(\"Bottom Level AS (BLAS)\", blasSize.latest, blasSize.max, 0, 0, allocatedClusters);\n            buildRow(\"BLAS scratch buffer\", blasScratchSize.latest, blasScratchSize.max, 0, 0, allocatedClusters);\n            \n            size_t total = blasSize.latest + blasScratchSize.latest + clasSize.latest + vertexBufferSize.latest\n                + vertexNormalsBufferSize.latest + clusterShadingDataSize.latest;\n\n            size_t totalMax = blasSize.max + blasScratchSize.max + clasSize.max + vertexBufferSize.max\n                + vertexNormalsBufferSize.max + clusterShadingDataSize.max;\n\n            buildRow(\"Total \", total, totalMax, desiredTris, numPixels, desiredClusters, false);\n        }\n        ImGui::EndTable();\n\n        ImGui::Spacing();\n        ImGui::Separator();\n        ImGui::Spacing();\n\n\n        ImGui::Text(\"Topology & Subdivision\");\n        ImGui::Spacing();\n\n        ImGui::BeginTable(\"Subdivision\", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoHostExtendX);\n        {\n            static const float kColWidth0 = 200 * fontScale;\n            static const float kColWidth1 = 80 * fontScale;\n\n            ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthFixed, kColWidth0);\n            ImGui::TableSetupColumn(\"Memory\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n\n            ImGui::TableHeadersRow();\n\n            char buf[32];\n\n            auto buildRow = [&buf](char const* name, size_t m_size, bool displayMB = true, char const* tooltip = nullptr)\n                {\n                    ImGui::TableNextRow();\n                    ImGui::TableSetColumnIndex(0);\n                    ImGui::Text(\"%s\", name);\n                    if (tooltip && ImGui::IsItemHovered())\n                        ImGui::SetTooltip(\"%s\", tooltip);\n                    ImGui::TableSetColumnIndex(1);\n                    if (displayMB)\n                        MegabytesFormatter(static_cast<double>(m_size), buf, (int) std::size(buf));\n                    else\n                        MemoryFormatter(static_cast<double>(m_size), buf, (int) std::size(buf));\n                    ImGui::Text(\"%s\", buf);\n                    if (ImGui::IsItemHovered())\n                        ImGui::SetTooltip(\"%zu bytes.\", m_size);\n                };\n\n            \n            buildRow(\"Topology map\", evaluatorSamplers.topologyMapStats.plansByteSize, false,\n                \"Total m_size of topology map.\\n\"\n                \"note: there should be only 1 topology map shared by all the sub-d meshes in the scene.\\n\");\n            buildRow(\"Surface tables\", evaluatorSamplers.surfaceTablesByteSizeTotal, false,\n                \"Total m_size of vertex surface table.\\n\"\n                \"The 'surface table' replaces the index buffer for each sub-d mesh in the scene.\\n\"\n                \"The m_size of a surface table is typically 3x to 5x the m_size of the control cage\\n\"\n                \"index buffer (compare above).\\n\");\n\n            size_t total = evaluatorSamplers.topologyMapStats.plansByteSize\n                + evaluatorSamplers.surfaceTablesByteSizeTotal;\n\n            buildRow(\"Total \", total, false);\n        }\n        ImGui::EndTable();\n    }\n\n    void SurfaceTableStats::BuildTopologyRecommendations()\n    {\n        float ratio = 0.f;\n        if (!IsCatmarkTopology(&ratio))\n        {\n            std::stringstream ss;\n            ss.setf(std::ios::fixed);\n            ss.precision(1);\n            ss << \"High number of irregular (non-quad) faces detected (\" << ratio * 100.f << \" %). \";\n            ss << \"Irregular faces impact both performance and memory. Catmark subdivision \";\n            ss << \"meshes should use mostly quads.\";\n            topologyRecommendations.push_back(ss.str());\n        }\n\n        // note: TopologyRefiner::GetMaxValence() accumulates both face and vertex valence\n        // into the same max variable ; on the rare occasion where a model has both a high\n        // valence vertex and a face of equal or greater valence, this recommendation will\n        // not be triggered. The assumption is that once the high valence faces are removed\n        // from the topology, if high valence vertices remain, this recommendation will \n        // then trigger as intended.\n        if ((maxValence > 8) && (maxValence > maxFaceSize))\n        {\n            std::stringstream ss;\n            ss << \"Some vertices have up to \" << maxValence << \" incident edges. Ideally max \";\n            ss << \"valence should be <= 8.\";\n            topologyRecommendations.push_back(ss.str());\n        }\n\n        if (maxFaceSize > 5)\n        {\n            std::stringstream ss;\n            ss << \"Some polygons faces have up to \" << maxFaceSize << \" edges. It is recommended \";\n            ss << \"to use quads with a few triangles and pentagons in delicate areas.\";\n            topologyRecommendations.push_back(ss.str());\n        }\n\n        if (sharpnessMax > 8.f)\n        {\n            std::stringstream ss;\n            ss << \"Some creased edges or vertices have a very high sharpness value (found up \";\n            ss << \"to \" << sharpnessMax << \"). Consider replacing those with 'infinitely sharp' \";\n            ss << \"creases of value 10 for better performance.\";\n            topologyRecommendations.push_back(ss.str());\n        }\n        else if (sharpnessMax > 4.f && sharpnessMax <= 8.f)\n        {\n            std::stringstream ss;\n            ss << \"Some creased edges or vertices have a high sharpness value (found up to \";\n            ss << sharpnessMax << \"). Consider adding edge-loops and reducing sharpness creases \";\n            ss << \"to values <= 4.0 for better control over the surface and better performance.\";\n            topologyRecommendations.push_back(ss.str());\n        }\n    }\n    \n    void SurfaceTableStats::BuildRecommendationsUI(ImFont *iconicFont) const\n    {\n        for (auto const& rec : topologyRecommendations)\n        {\n            ImGui::Spacing();\n\n            ImGui::PushFont(iconicFont);\n            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));\n            ImGui::SeparatorText((char const*)(u8\"\\ue0D8\" \"## env map\"));\n            ImGui::PopStyleColor();\n            ImGui::PopFont();\n\n            ImGui::TextWrapped(\"%s\", rec.c_str());\n\n            ImGui::Spacing();\n        }\n    }\n\n\n    void SurfaceTableStats::BuildUI(ImFont* iconicFont, ImPlotContext* plotContext, uint32_t imguiID) const\n    {\n        const float fontScale = ImGui::GetIO().FontGlobalScale;\n\n        char buf[128];\n\n        auto buildRow = [&buf]<typename T>(char const* name, T value, char const* tooltip = nullptr, bool displayMB = false)\n        {\n            ImGui::TableNextRow();\n            ImGui::TableSetColumnIndex(0);\n            ImGui::Text(\"%s\", name);\n            ImGui::TableSetColumnIndex(1);\n            if constexpr (std::is_same_v<T, size_t>)\n            {\n                if (displayMB)\n                    MegabytesFormatter(static_cast<double>(value), buf, (int) std::size(buf));\n                else\n                    MemoryFormatter(static_cast<double>(value), buf, (int) std::size(buf));\n                ImGui::Text(\"%s\", buf);\n            }\n            else if constexpr (std::is_same_v<T, float>)\n                ImGui::Text(\"%.1f\", value);\n            else if constexpr (std::is_integral_v<T>)\n                ImGui::Text(\"%d\", int64_t(value));\n            if (tooltip && ImGui::IsItemHovered())\n                ImGui::SetTooltip(\"%s\", tooltip);\n        };\n\n        if (ImGui::CollapsingHeader(name.empty() ? \"Surface Table\" : name.c_str(), ImGuiTreeNodeFlags_DefaultOpen))\n        {\n            BuildRecommendationsUI(iconicFont);\n\n            ImGui::Spacing();\n\n            snprintf(buf, std::size(buf), \"##Surface_Table_%d\", imguiID);\n\n            ImGui::BeginTable(buf, 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoHostExtendX);\n            {\n                static const float kColWidth0 = 200 * fontScale;\n                static const float kColWidth1 = 80 * fontScale;\n\n                ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_WidthFixed, kColWidth0);\n                ImGui::TableSetupColumn(\"Value\", ImGuiTableColumnFlags_WidthFixed, kColWidth1);\n                ImGui::TableHeadersRow();\n\n                buildRow(\"Memory use\", byteSize,\n                    \"Total memory use for Tmr::SurfaceTable.\\n\"\n                    \"note: does not account for the texcoord surface table.\\n\");\n\n                buildRow(\"Surfaces count\", (int64_t)surfaceCount,\n                    \"Number of surfaces in the table.\\n\");\n\n                buildRow(\"Pure regular surface count\", bsplineSurfaceCount,\n                            \"Number of 'pure' regular surfaces in the table that can\\n\"\n                            \"be resolved with a single b-spline patch (ie. surfaces\\n\"\n                            \"with 16 control points, no boundaries and a subdivision\\n\"\n                            \"plan with no stencil matrix.\\n\");\n\n                buildRow(\"Irregular face count\", irregularFaceCount,\n                    \"Number of irregular faces in the table (ie. non-quads)\\n\"\n                    \"for Catmark subdivision scheme.\\n\");\n\n                buildRow(\"Valence max\", maxValence,\n                    \"Maximum vertex valence in the control cage.\\n\");\n\n                buildRow(\"Face m_size max\", maxFaceSize,\n                    \"Maximum number of vertices in a face in the control cage.\\n\");\n\n                buildRow(\"Sharpness max\", sharpnessMax,\n                    \"Highest sharpness value for edge or vertex in the control cage.\\n\");\n\n                buildRow(\"Inf sharp\", infSharpCreases,\n                    \"Number of edges or vertices with an 'infinitely' sharp crease tag.\\n\");\n\n                buildRow(\"Stencil matrix row count avg\", stencilCountAvg,\n                    \"Average number of patch-points per surface across the table.\\n\"\n                    \"Obtained by iterating over each surface in the table and\\n\"\n                    \"summing up the number of patch-points (aka rows in the stencil\\n\"\n                    \"matrix) in the subdivision plan associated with that surface.\\n\"\n                    \"The aveage is given by dividing this sum by the number of surfaces\\n\"\n                    \"in the table.\\n\"\n                    \"This average is a proxy measure of the global amount of computations\\n\"\n                    \"required to obtain the limit surface. Use of sharp edges or high\\n\"\n                    \"valence vertices will increase this average, while prevalance of\\n\"\n                    \"'regular' topology lowers this average.\\n\");\n            }\n            ImGui::EndTable();\n\n            ImGui::SameLine();\n\n            if (bsplineSurfaceCount > 0)\n            {\n                snprintf(buf, std::size(buf), \"##Surface_Stencil_Pie_Chart_%d\", imguiID);\n\n                if (ImPlot::BeginPlot(buf, ImVec2(235 * fontScale, 153 * fontScale), ImPlotFlags_NoMouseText))\n                {\n                    float count = float(surfaceCount);\n                    float bspline_ratio = float(bsplineSurfaceCount) / count;\n                    float regular_ratio = float(regularSurfaceCount) / count;\n                    float isolation_ratio = float(isolationSurfaceCount) / count;\n                    float sharp_ratio = float(sharpSurfaceCount) / count;\n                    float holes_ratio = float(holesCount) / count;\n\n                    float values[5] = { bspline_ratio, regular_ratio, isolation_ratio, sharp_ratio, holes_ratio };\n\n                    static char const* labels[std::size(values)] = {\n                        \"BSpline\",\n                        \"Regular\",\n                        \"Smooth\",\n                        \"Sharp\",\n                        \"Holes\",\n                    };\n\n                    ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations);\n                    ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);\n\n                    ImPlot::SetupLegend(ImPlotLocation_West, ImPlotLegendFlags_Outside);\n\n                    ImPlot::PlotPieChart(labels, values, (int) std::size(values), 0, 0, 0.4, \"%.2f\", ImPlotFlags_NoLegend);\n\n                    ImPlot::EndPlot();\n                    if (ImGui::IsItemHovered())\n                        ImGui::SetTooltip(\n                            \"Distribution of surfaces in the table:\\n\"\n                            \" - 'B-Spline' surfaces are 'pure' regular surfaces\\n\"\n                            \"   (16 control points in 1-ring, no boundaries, no\\n\"\n                            \"   patch-points, no stencil matrix).\\n\"\n                            \"   The limit of these surfaces can be evaluated\\n\"\n                            \"   through a dedicated fast-path.\\n\"\n                            \"\\n\"\n                            \" - 'Regular' surfaces are still b-spline surfaces, but\\n\"\n                            \"   with boundaries (9 or 12 control points in 1-ring,\\n\"\n                            \"   no patch-points, no stencil matrix).\\n\"\n                            \"\\n\"\n                            \" - 'Smooth' surfaces are areas that require feature\\n\"\n                            \"    isolation (a stencil matrix is required, but with a\\n\"\n                            \"    lower isolation level).\\n\"\n                            \"\\n\"\n                            \" - 'Sharp' surfaces are surfaces with semi-sharp\\n\"\n                            \"   creases (full feature isolation and stencil matrix).\\n\");\n                }\n            }\n            ImGui::SameLine();\n\n            snprintf(buf, std::size(buf), \"##Surface_Stencil_Hisogram_%d\", imguiID);\n\n            if (ImPlot::BeginPlot(buf, ImVec2(-1, 153 * fontScale), ImPlotFlags_NoMouseText))\n            {\n                auto const& values = stencilCountHistogram;\n\n                ImPlotFormatter formatter = [](double value, char* buff, int size, void* user_data) -> int\n                    {\n                        auto const* samplers = reinterpret_cast<EvaluatorSamplers const*>(user_data);\n                        uint32_t    min = samplers->topologyMapStats.stencilCountMin;\n                        uint32_t    max = samplers->topologyMapStats.stencilCountMax;\n                        uint32_t    count = (uint32_t)samplers->topologyMapStats.stencilCountHistogram.size();\n                        if (uint32_t range = max - min; range > 0 && count > 0)\n                            value = min + (value / count) * range;\n                        else\n                            value = min;\n                        return snprintf(buff, size_t(size), \"%d\", (int)value);\n                    };\n\n                ImPlot::SetupAxis(ImAxis_X1, \"Num patch points\", ImPlotAxisFlags_AutoFit);\n                ImPlot::SetupAxisFormat(ImAxis_X1, formatter, reinterpret_cast<void*>(&evaluatorSamplers));\n                ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_AutoFit);\n                ImPlot::PlotBars(\"Num Surfaces\", values.data(), static_cast<int>(values.size()), 1.0, 0.5, ImPlotBarsFlags_None);\n\n                ImPlot::EndPlot();\n            }\n        }\n\n        ImGui::Spacing();\n    }\n\n}  // end namespace stats\n"
  },
  {
    "path": "rtxmg/profiler/stopwatch.cpp",
    "content": "//\n// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n// clang-format off\n\n#include \"rtxmg/profiler/stopwatch.h\"\n\n#include <cassert>\n#include <donut/core/log.h>\n\n// clang-format on\n\n//\n// StopwatchCPU\n//\n\nvoid StopwatchCPU::Start()\n{\n    m_startTime = steady_clock::now();\n    assert( m_stopTime < m_startTime );\n}\n\nvoid StopwatchCPU::Stop()\n{\n    assert( m_startTime.time_since_epoch().count() > 0 );\n    m_stopTime = steady_clock::now();\n}\n\nstd::optional<float> StopwatchCPU::Elapsed()\n{\n    assert( m_stopTime >= m_startTime );\n\n    if( m_startTime == steady_clock::time_point{} )\n        return {};\n\n    float elapsed = static_cast<float>( duration( m_stopTime - m_startTime ).count() );\n    m_startTime = m_stopTime = {};\n    return elapsed;\n}\n\nstd::optional<float> StopwatchCPU::Before( steady_clock::time_point t )\n{\n    assert( m_startTime >= t && m_startTime > steady_clock::time_point{});\n    return (float)duration( m_startTime - t).count();\n}\n\nstd::optional<float> StopwatchCPU::After( steady_clock::time_point t )\n{\n    assert( m_stopTime <= t && m_stopTime > steady_clock::time_point{});\n    return (float)duration( t - m_stopTime ).count();\n}\n\n//\n// StopwatchGPU\n//\nvoid StopwatchGPU::ProcessUnresolvedQueries()\n{\n    if (state == State::uninitialized)\n        return;\n\n    // New frame started\n    // Check our previous queries\n    uint32_t unresolvedQueryIndex = m_unresolvedQueryIndex;\n    while(unresolvedQueryIndex != m_queryIndex)\n    {\n        unresolvedQueryIndex = (unresolvedQueryIndex + 1) % kMaxInFlightQueries;\n        if (!m_device->pollTimerQuery(m_timerQueries[unresolvedQueryIndex]))\n        {\n            break;\n        }\n        // save the last one\n        m_unresolvedQueryIndex = unresolvedQueryIndex;\n        m_lastDuration = m_device->getTimerQueryTime(m_timerQueries[m_unresolvedQueryIndex]);\n        m_hasLastDuration = true;\n        m_device->resetTimerQuery(m_timerQueries[m_unresolvedQueryIndex]);\n    }\n}\n\nvoid StopwatchGPU::Start(nvrhi::ICommandList* commandList)\n{\n    if (state == State::uninitialized)\n    {\n        m_device = commandList->getDevice();\n        for (auto& query : m_timerQueries)\n        {\n            query = m_device->createTimerQuery();\n        }\n        m_queryIndex = -1;\n        m_unresolvedQueryIndex = -1;\n        m_hasLastDuration = false;\n        state = State::reset;\n    }\n\n    ProcessUnresolvedQueries();\n    \n    // Start a new query. Assumption is one star/stop pair per frame\n    // It's possible we overflow max in flight queries, but we just overwrite\n    m_queryIndex = (m_queryIndex + 1) % kMaxInFlightQueries;\n\n    // all but 'stopped' states are valid, so can advance up to 3 times\n    assert(state != State::ticking);\n    commandList->beginTimerQuery(m_timerQueries[m_queryIndex]);\n    m_commandList = commandList;\n    m_device = commandList->getDevice();\n    state = State::ticking;\n}\n\nvoid StopwatchGPU::Stop()\n{\n    assert(state == State::ticking);\n    m_commandList->endTimerQuery(m_timerQueries[m_queryIndex]);\n    state = State::stopped;\n}\n\nstd::optional<float> StopwatchGPU::Elapsed()\n{\n    if( state == State::reset || state == State::uninitialized)\n        return {};\n\n    assert(state == State::stopped);\n\n    state = State::reset;\n\n    ProcessUnresolvedQueries();\n\n    return m_lastDuration * 1000.0f;\n}\n\nstd::optional<float> StopwatchGPU::ElapsedAsync()\n{\n    if (state == State::reset || state == State::uninitialized)\n        return {};\n\n    // user is responsible for device sync, so we can't track it\n    assert(state != State::ticking);\n\n    state = State::reset;\n\n    ProcessUnresolvedQueries();\n    if (m_hasLastDuration)\n        return m_lastDuration * 1000.0f;\n    return {};\n}\n"
  },
  {
    "path": "rtxmg/scene/CMakeLists.txt",
    "content": "set(lib scene)\n\nfile(GLOB sources \"*.cpp\" \"../include/rtxmg/${lib}/*.h\")\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include\"\n)\ntarget_link_libraries(${lib} donut_engine osd_lite_static implot)\nset_target_properties(${lib} PROPERTIES FOLDER RTXMG)\n"
  },
  {
    "path": "rtxmg/scene/box_extent.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n\n#include \"rtxmg/scene/box_extent.h\"\n\nusing namespace donut::math;\n\nfloat MaxBoxExtent(const box3& aabb)\n{\n    float3 diagonal = aabb.diagonal();\n    return max(max(diagonal.x, diagonal.y), diagonal.z);\n}\n"
  },
  {
    "path": "rtxmg/scene/camera.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include <algorithm>\n#include <cmath>\n#include <cstdio>\n#include <sstream>\n\n#include \"rtxmg/scene/box_extent.h\"\n#include \"rtxmg/scene/camera.h\"\n\n#include <donut/core/log.h>\n#include \"rtxmg/utils/buffer.h\"\n\nvoid Camera::Translate(float3 const& v)\n{\n    m_eye += v;\n    m_lookat += v;\n    m_changed = true;\n}\n\nvoid Camera::Rotate(float yaw, float pitch, float roll)\n{\n    float3 w = m_lookat - m_eye;\n    float wlen = length(w);\n    float3 u = normalize(cross(w, m_up));\n\n    affine3 rotation = yawPitchRoll(yaw, pitch, roll);\n\n    float3 dir = rotation.transformVector(float3(w.x, w.y, w.z));\n    float3 m_up = rotation.transformVector(m_up);\n\n    m_lookat = m_eye + dir * wlen;\n\n    m_changed = true;\n}\n\nvoid Camera::Roll(float speed)\n{\n    auto const& basis = GetBasis();\n    float3 u = normalize(basis[0]);\n    float3 v = normalize(basis[1]);\n    m_up = u * cos(radians(90.0f + speed)) + v * sin(radians(90.0f + speed));\n    m_changed = true;\n}\n\nvoid Camera::Pan(float2 speed)\n{\n    auto const& basis = GetBasis();\n    float3 u = basis[0] * (-2.f * speed.x);\n    float3 v = basis[1] * (-2.f * speed.y);\n    Translate(u + v);\n}\n\nvoid Camera::Dolly(float factor)\n{\n    // move closer by factor\n    float3 oldEyeOffset = m_eye - m_lookat;\n    m_eye = m_lookat + (oldEyeOffset * factor);\n    m_changed = true;\n}\n\nvoid Camera::Zoom(const float factor)\n{\n    // increase/decrease field-of-view angle by factor\n    m_fovY = fminf(150.f, m_fovY * factor);\n    m_changed = true;\n}\n\nvoid Camera::Frame(box3 const& aabb)\n{\n    SetFovY(35.0f);\n    SetLookat(aabb.center());\n    SetEye(aabb.center() + 1.2f * MaxBoxExtent(aabb));\n    SetUp({ 0.f, 1.f, 0.f });\n    m_changed = true;\n}\n\nstd::array<float3, 3> const& Camera::GetBasis()\n{\n    if (HasChanged())\n    {\n        ComputeBasis(m_basis[0], m_basis[1], m_basis[2]);\n        m_changed = false;\n    }\n    return m_basis;\n}\n\nvoid Camera::ComputeBasis(float3& U, float3& V, float3& W) const\n{\n    float wlen = 0.f;\n\n    W = m_lookat - m_eye; // Do not normalize W -- it implies focal length\n    wlen = length(W);\n    U = normalize(cross(W, m_up));\n    V = normalize(cross(U, W));\n\n    float vlen = wlen * tanf(0.5f * m_fovY * PI_f / 180.0f);\n    V *= vlen;\n    float ulen = vlen * m_aspectRatio;\n    U *= ulen;\n}\n\nvoid Camera::SetEye(float3 eye)\n{\n    if (!all(isfinite(eye)))\n    {\n        donut::log::error(\"setEye is NaN!: %f %f %f\", eye.x, eye.y, eye.z);\n        return;\n    }\n\n    m_eye = eye;\n    m_changed = true;\n}\nvoid Camera::SetLookat(float3 lookat)\n{\n    if (!all(isfinite(lookat)))\n    {\n        donut::log::error(\"lookat is NaN!: %f %f %f\", lookat.x, lookat.y, lookat.z);\n        return;\n    }\n    m_lookat = lookat;\n    m_changed = true;\n}\nvoid Camera::SetUp(float3 up)\n{\n    m_up = up;\n    m_changed = true;\n}\nvoid Camera::SetFovY(float fovy)\n{\n    m_fovY = fovy;\n    m_changed = true;\n}\nvoid Camera::SetAspectRatio(float ar)\n{\n    m_aspectRatio = ar;\n    m_changed = true;\n}\nvoid Camera::SetNear(float near)\n{\n    m_zNear = near;\n    m_changed = true;\n}\nvoid Camera::SetFar(float far)\n{\n    m_zFar = far;\n    m_changed = true;\n}\n\nstatic inline std::istream& operator>>(std::istream& is, float3& v)\n{\n    char st;\n    is >> st >> v.x >> st >> v.y >> st >> v.z >> st;\n    return is;\n}\n\nvoid Camera::Set(std::string const& camc_string)\n{\n    std::istringstream istr(camc_string);\n    istr >> m_eye;\n    istr >> m_lookat;\n    istr >> m_up;\n    istr >> m_fovY;\n    m_changed = true;\n}\n\nvoid Camera::Print() const\n{\n    printf(\"[%g,%g,%g][%g,%g,%g][%g,%g,%g]%g\\n\", m_eye.x, m_eye.y, m_eye.z,\n        m_lookat.x, m_lookat.y, m_lookat.z, m_up.x, m_up.y, m_up.z, m_fovY);\n}\n\nfloat4x4 Camera::GetViewMatrix() const\n{\n    float3 W = normalize(m_lookat - m_eye);\n    float3 U = normalize(cross(W, m_up));\n    float3 V = normalize(cross(U, W));\n\n    float m[16];\n    m[0] = U.x;\n    m[1] = U.y;\n    m[2] = U.z;\n    m[3] = -dot(U, m_eye);\n\n    m[4] = V.x;\n    m[5] = V.y;\n    m[6] = V.z;\n    m[7] = -dot(V, m_eye);\n\n    m[8] = -W.x;\n    m[9] = -W.y;\n    m[10] = -W.z;\n    m[11] = dot(W, m_eye);\n\n    m[12] = 0.f;\n    m[13] = 0.f;\n    m[14] = 0.f;\n    m[15] = 1.0f;\n\n    return float4x4(m);\n}\n\nfloat4x4 Camera::GetProjectionMatrix() const\n{\n    const float fovRad = m_fovY * PI_f / 180.f;\n    const float tanFov = tan(fovRad / 2.f);\n\n    float m[16];\n    m[0] = 1.f / (tanFov * m_aspectRatio);\n    m[1] = 0.f;\n    m[2] = 0.f;\n    m[3] = 0.f;\n\n    m[4] = 0.f;\n    m[5] = 1.f / tanFov;\n    m[6] = 0.f;\n    m[7] = 0.f;\n\n    m[8] = 0.f;\n    m[9] = 0.f;\n    m[10] = (m_zFar + m_zNear) / (m_zNear - m_zFar);\n    m[11] = 2.f * m_zFar * m_zNear / (m_zNear - m_zFar);\n\n    m[12] = 0.f;\n    m[13] = 0.f;\n    m[14] = -1.f;\n    m[15] = 0.f;\n\n    return float4x4(m);\n}\n\nfloat4x4 Camera::GetViewProjectionMatrix() const\n{\n    return GetProjectionMatrix() * GetViewMatrix();\n}\n"
  },
  {
    "path": "rtxmg/scene/json.cpp",
    "content": "// clang-format off\n#include \"rtxmg/scene/json.h\"\n\n#include <json/json.h>\n\n#include <fstream>\n\n// clang-format on\nnamespace fs = std::filesystem;\n\nJson::Value readFile(const fs::path& m_filepath)\n{\n    std::string fp = m_filepath.generic_string();\n\n    std::stringstream ss;\n\n    {\n        std::ifstream ifs(fp);\n\n        if (!ifs)\n            throw std::runtime_error(std::string(\"Cannot find: \") + fp);\n\n        ss << ifs.rdbuf();\n        ifs.close();\n    }\n\n    std::basic_string_view data = ss.view();\n\n    if (data.empty())\n        throw std::runtime_error(\"error reading '\" + fp + \"'\");\n\n    Json::CharReaderBuilder builder;\n    builder[\"collectComments\"] = false;\n\n    Json::Value root;\n\n    Json::CharReader* reader = builder.newCharReader();\n\n    std::string errors;\n    if (!reader->parse(data.data(), data.data() + data.size(), &root, &errors))\n        throw std::runtime_error(\"JSON Parse error ('\" + fp + \"' \" + errors);\n\n    delete reader;\n\n    return root;\n}\n\ntemplate <>\nstd::string read<std::string>(const Json::Value& node, const std::string& defaultValue)\n{\n    if (node.isString())\n        return node.asString();\n    return defaultValue;\n}\n\ntemplate <>\nbool read<bool>(const Json::Value& node, const bool& defaultValue)\n{\n    if (node.isBool())\n        return node.asBool();\n    if (node.isNumeric())\n        return node.asFloat() != 0.f;\n    return defaultValue;\n}\n\ntemplate <>\nint8_t read<int8_t>(const Json::Value& node, const int8_t& defaultValue)\n{\n    if (node.isNumeric())\n        return (int8_t)node.asInt();\n    return defaultValue;\n}\ntemplate <>\nint16_t read<int16_t>(const Json::Value& node, const int16_t& defaultValue)\n{\n    if (node.isNumeric())\n        return (int16_t)node.asInt();\n    return defaultValue;\n}\ntemplate <>\nint32_t read<int32_t>(const Json::Value& node, const int32_t& defaultValue)\n{\n    if (node.isNumeric())\n        return (int32_t)node.asInt();\n    return defaultValue;\n}\n\ntemplate <>\nint2 read<int2>(const Json::Value& node, const int2& defaultValue)\n{\n    if (node.isArray() && node.size() == 2)\n        return int2(node[0].asInt(), node[1].asInt());\n    return defaultValue;\n}\n\ntemplate <>\nint3 read<int3>(const Json::Value& node, const int3& defaultValue)\n{\n    if (node.isArray() && node.size() == 3)\n        return int3(node[0].asInt(), node[1].asInt(), node[2].asInt());\n    return defaultValue;\n}\n\ntemplate <>\nint4 read<int4>(const Json::Value& node, const int4& defaultValue)\n{\n    if (node.isArray() && node.size() == 4)\n        return int4(node[0].asInt(), node[1].asInt(), node[2].asInt(), node[3].asInt());\n    return defaultValue;\n}\n\ntemplate <>\nuint8_t read<uint8_t>(const Json::Value& node, const uint8_t& defaultValue)\n{\n    if (node.isNumeric())\n        return (uint8_t)node.asUInt();\n    return defaultValue;\n}\n\ntemplate <>\nuint16_t read<uint16_t>(const Json::Value& node, const uint16_t& defaultValue)\n{\n    if (node.isNumeric())\n        return (uint16_t)node.asUInt();\n    return defaultValue;\n}\ntemplate <>\nuint32_t read<uint32_t>(const Json::Value& node, const uint32_t& defaultValue)\n{\n    if (node.isNumeric())\n        return (uint32_t)node.asUInt();\n    return defaultValue;\n}\n\ntemplate <>\nuint2 read<uint2>(const Json::Value& node, const uint2& defaultValue)\n{\n    if (node.isArray() && node.size() == 2)\n        return uint2(node[0].asUInt(), node[1].asUInt());\n    return defaultValue;\n}\n\ntemplate <>\nuint3 read<uint3>(const Json::Value& node, const uint3& defaultValue)\n{\n    if (node.isArray() && node.size() == 3)\n        return uint3(node[0].asUInt(), node[1].asUInt(), node[2].asUInt());\n    return defaultValue;\n}\n\ntemplate <>\nuint4 read<uint4>(const Json::Value& node, const uint4& defaultValue)\n{\n    if (node.isArray() && node.size() == 4)\n        return uint4(node[0].asUInt(), node[1].asUInt(), node[2].asUInt(), node[3].asUInt());\n    return defaultValue;\n}\n\ntemplate <>\nfloat read<float>(const Json::Value& node, const float& defaultValue)\n{\n    if (node.isNumeric())\n        return node.asFloat();\n    return defaultValue;\n}\n\ntemplate <>\nfloat2 read<float2>(const Json::Value& node, const float2& defaultValue)\n{\n    if (node.isArray() && node.size() == 2)\n        return float2(node[0].asFloat(), node[1].asFloat());\n    if (node.isNumeric())\n        return float2(node.asFloat());\n    return defaultValue;\n}\n\ntemplate <>\nfloat3 read<float3>(const Json::Value& node, const float3& defaultValue)\n{\n    if (node.isArray() && node.size() == 3)\n        return float3(node[0].asFloat(), node[1].asFloat(), node[2].asFloat());\n    if (node.isNumeric())\n        return float3(node.asFloat());\n    return defaultValue;\n}\n\ntemplate <>\nfloat4 read<float4>(const Json::Value& node, const float4& defaultValue)\n{\n    if (node.isArray() && node.size() == 4)\n        return float4(node[0].asFloat(), node[1].asFloat(), node[2].asFloat(), node[3].asFloat());\n    if (node.isNumeric())\n        return float4(node.asFloat());\n    return defaultValue;\n}\ntemplate <>\ndouble read<double>(const Json::Value& node, const double& defaultValue)\n{\n    if (node.isNumeric())\n        return node.asDouble();\n    return defaultValue;\n}\n\ntemplate <>\ndouble2 read<double2>(const Json::Value& node, const double2& defaultValue)\n{\n    if (node.isArray() && node.size() == 2)\n        return double2(node[0].asDouble(), node[1].asDouble());\n    if (node.isNumeric())\n        return double2(node.asDouble(), node.asDouble());\n    return defaultValue;\n}\n\ntemplate <>\ndouble3 read<double3>(const Json::Value& node, const double3& defaultValue)\n{\n    if (node.isArray() && node.size() == 3)\n        return double3(node[0].asDouble(), node[1].asDouble(), node[2].asDouble());\n    if (node.isNumeric())\n        return double3(node.asDouble(), node.asDouble(), node.asDouble());\n    return defaultValue;\n}\n\ntemplate <>\ndouble4 read<double4>(const Json::Value& node, const double4& defaultValue)\n{\n    if (node.isArray() && node.size() == 4)\n        return double4(node[0].asDouble(), node[1].asDouble(), node[2].asDouble(), node[3].asDouble());\n    if (node.isNumeric())\n        return double4(node.asDouble(), node.asDouble(), node.asDouble(), node.asDouble());\n    return defaultValue;\n}\n"
  },
  {
    "path": "rtxmg/scene/obj_importer.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"rtxmg/scene/obj_importer.h\"\n#include \"rtxmg/utils/buffer.h\"\n\n#include \"rtxmg/subdivision/shape.h\"\n#include \"rtxmg/subdivision/subdivision_surface.h\"\n\n#include <donut/core/log.h>\n#include <donut/engine/Scene.h>\n#include <execution>\n#include <filesystem>\n#include <iostream>\n#include <numeric>\n#include <set>\n\nusing namespace donut;\nusing namespace donut::engine;\nusing namespace donut::math;\nnamespace fs = std::filesystem;\n\n// parses filenames of type 'filename[start-stop].ext'\nstatic std::optional<int2> getSequenceRange(const std::string& str)\n{\n    size_t open = str.find('[');\n    size_t close = str.find(']');\n    if (open == std::string::npos || close == std::string::npos)\n        return {};\n\n    std::string_view range = { str.data() + open + 1, str.data() + close };\n\n    size_t delim = range.find('-');\n    if (delim == std::string::npos)\n        return {};\n\n    int2 framerange = { 0, 0 };\n    std::from_chars(range.data(), range.data() + delim, framerange.x);\n    std::from_chars(range.data() + delim + 1, range.data() + range.size(),\n        framerange.y);\n    return framerange;\n}\n\nstatic std::string getSequenceFormat(const std::string& str, int2 frameRange)\n{\n    size_t open = str.find('[');\n    size_t close = str.find(']');\n    if (open == std::string::npos || close == std::string::npos)\n        return str;\n\n    std::string prefix = str.substr(0, open);\n    std::string suffix = str.substr(close + 1, std::string::npos);\n\n    // test some common padding formats\n    std::array<const char*, 3> formats = { \"%d\", \"%03d\", \"%04d\" };\n\n    for (const char* format : formats)\n    {\n        char buf[16];\n        std::snprintf(buf, std::size(buf), format, frameRange.x);\n\n        if (fs::is_regular_file(prefix + buf + suffix))\n            return prefix + format + suffix;\n    }\n    return str;\n}\n\nObjImporter::ObjImporter(std::shared_ptr<vfs::IFileSystem> fs,\n    const fs::path& mediapath,\n    std::shared_ptr<SceneTypeFactory> sceneTypeFactory,\n    std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTableManager,\n    TopologyCache& topologyCache)\n    : m_fs(std::move(fs)), m_sceneTypeFactory(std::move(sceneTypeFactory)),\n    m_topologyCache(topologyCache),\n    m_descriptorTableManager(std::move(descriptorTableManager)),\n    m_mediaPath(mediapath)\n{}\n\nstd::optional<Model> ObjImporter::Load(const fs::path& fileName, TextureCache& textureCache,\n    int2 frameRange, const Instance& parent,\n    nvrhi::ICommandList* commandList) const\n{\n    fs::path fp = m_modelPath.empty() ? fileName : m_modelPath / fileName;\n\n    if (!fs::is_regular_file(fp) && fs::is_regular_file(m_mediaPath / fp))\n        fp = m_mediaPath / fp;\n\n    if (auto range = getSequenceRange(fp.string()))\n    {\n        frameRange = *range;\n        fp = getSequenceFormat(fp.string(), frameRange);\n    }\n\n    Model model;\n    int nframes = 1;\n    if (frameRange.y > frameRange.x)\n        nframes = frameRange.y - frameRange.x + 1;\n\n\n    std::unique_ptr<Shape> shape;\n    if (fp.empty())\n    {\n        shape = Shape::DefaultShape();\n    }\n    else\n    {\n        if (fp.extension() == \".eddbin\")\n        {\n            log::warning(\"EDDBin files are not supported by the ObjImporter\");\n            return {};\n        }\n        if (nframes == 1)\n        {\n            shape = Shape::LoadObjFile(fp.string().c_str());\n        }\n        else\n        {\n            char buf[1024];\n            std::snprintf(buf, std::size(buf), fp.generic_string().c_str(),\n                frameRange.x);\n            shape = Shape::LoadObjFile(buf);\n        }\n    }\n    if (shape->mtlbind.empty())\n    {\n        shape->mtlbind.resize(shape->nvertsPerFace.size(), 0);\n        shape->mtls.push_back(std::make_unique<Shape::material>());\n    }\n\n    int32_t fallbackErrorMaterial = -1;\n    size_t originalMaterialRange = shape->mtls.size();\n\n    shape->subshapes.reserve(originalMaterialRange);\n    shape->faceToSubshapeIndex.reserve(shape->mtlbind.size());\n    int currMtl = -1;\n    for (size_t m_idx = 0; m_idx < shape->mtlbind.size(); ++m_idx)\n    {\n        int nextMtl = shape->mtlbind[m_idx];\n        if (nextMtl < 0 || nextMtl >= originalMaterialRange)\n        {\n            // missing mtl for face\n            log::warning(\"Missing material bind for face: %d\\n\", m_idx);\n            if (fallbackErrorMaterial == -1)\n            {\n                fallbackErrorMaterial = (int32_t)shape->mtls.size();\n                shape->mtls.push_back(std::make_unique<Shape::material>());\n            }\n            nextMtl = fallbackErrorMaterial;\n        }\n\n        if (nextMtl != currMtl)\n        {\n            currMtl = nextMtl;\n            shape->subshapes.push_back({ m_idx, currMtl });\n        }\n\n        assert(shape->subshapes.size());\n        assert(shape->subshapes.size() < std::numeric_limits<uint16_t>::max());\n        shape->faceToSubshapeIndex.push_back(uint16_t(shape->subshapes.size()) - 1);\n    }\n\n    std::vector<std::unique_ptr<Shape>> keyFrameShapes;\n    if (nframes > 1)\n    {\n        std::vector<int> frames(nframes - 1);\n        std::iota(frames.begin(), frames.end(), 1);\n\n        keyFrameShapes.resize(frames.size());\n\n        std::for_each(std::execution::par_unseq, frames.begin(), frames.end(), [&](int frame)\n            {\n                char buf[1024];\n                std::snprintf(buf, std::size(buf), fp.generic_string().c_str(), frame + frameRange.x);\n\n                keyFrameShapes[frame - 1] = (Shape::LoadObjFile(buf, false, false));\n            });\n    }\n\n    std::unique_ptr<SubdivisionSurface> subd =\n        std::make_unique<SubdivisionSurface>(m_topologyCache, std::move(shape), keyFrameShapes,\n            m_descriptorTableManager, commandList);\n\n    Instance instance = parent;\n    instance.aabb = subd->m_aabb * instance.localToWorld;\n\n    model.frameRange = frameRange;\n    model.subd = std::move(subd);\n    model.instances.emplace_back(instance);\n\n    return model;\n}\n\nvoid Instance::Animate(float animTime, float animRate) {}\n\nvoid Instance::UpdateLocalTransform()\n{\n    localToWorld = donut::math::scaling(scaling);\n\n    localToWorld *= rotation.toAffine();\n\n    localToWorld *= donut::math::translation(translation);\n}\n\nvoid Instance::Lerp(Instance const& a, Instance const& b, float t)\n{\n    auto lerp = [](auto a, auto b, float t) { return (1.f - t) * a + t * b; };\n\n    localToWorld.m_linear =\n        lerp(a.localToWorld.m_linear, b.localToWorld.m_linear, t);\n    localToWorld.m_translation =\n        lerp(a.localToWorld.m_translation, b.localToWorld.m_translation, t);\n\n    // TODO: AABB lerp\n\n    radius = lerp(a.radius, b.radius, t);\n    edgelength = lerp(a.edgelength, b.edgelength, t);\n}\n"
  },
  {
    "path": "rtxmg/scene/scene.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n //#include \"args.h\"\n\n#include <donut/core/log.h>\n#include <donut/engine/Scene.h>\n#include <donut/engine/TextureCache.h>\n#include <json/json.h>\n#include <nvrhi/common/misc.h>\n#include <execution>\n#include <filesystem>\n#include <numeric>\n#include <numbers>\n#include <ranges>\n#include <set>\n\n#include \"rtxmg/cluster_builder/cluster_accels.h\"\n#include \"rtxmg/profiler/statistics.h\"\n#include \"rtxmg/scene/box_extent.h\"\n#include \"rtxmg/scene/scene.h\"\n#include \"rtxmg/scene/json.h\"\n#include \"rtxmg/subdivision/shape.h\"\n#include \"rtxmg/subdivision/subdivision_surface.h\"\n#include \"rtxmg/subdivision/topology_cache.h\"\n#include \"rtxmg/utils/buffer.h\"\n\nusing namespace donut;\n\nnamespace fs = std::filesystem;\n\nRTXMGScene::RTXMGScene(nvrhi::IDevice* device,\n    const fs::path& mediapath,\n    std::shared_ptr<CommonRenderPasses> commonPasses,\n    ShaderFactory& shaderFactory,\n    std::shared_ptr<vfs::IFileSystem> fs,\n    std::shared_ptr<TextureCache> textureCache,\n    std::shared_ptr<DescriptorTableManager> descriptorTable,\n    std::shared_ptr<SceneTypeFactory> sceneTypeFactory,\n    int2 initialFrameRange, int isoLevelSharp, int isoLevelSmooth)\n    : Scene(device, shaderFactory, fs, textureCache,\n        descriptorTable, sceneTypeFactory),\n    m_commonPasses(commonPasses),\n    m_mediaPath(mediapath),\n    m_isoLevelSharp(isoLevelSharp),\n    m_isoLevelSmooth(isoLevelSmooth)\n{\n    m_attributes.frameRange = initialFrameRange;\n    m_UseResourceDescriptorHeapBindless = true;\n}\n\nvoid RTXMGScene::InsertModel(Model&& model)\n{\n    if (model.subd)\n    {\n        m_attributes.frameRange.x =\n            std::min(m_attributes.frameRange.x, model.frameRange.x);\n        m_attributes.frameRange.y =\n            std::max(m_attributes.frameRange.y, model.frameRange.y);\n\n        for (auto& instance : model.instances)\n            instance.meshID = uint32_t(m_subdMeshes.size());\n\n        m_subdMeshes.emplace_back(std::move(model.subd));\n\n        std::ranges::move(model.instances, std::back_inserter(m_instances));\n    }\n}\n\nstd::span<Instance const> RTXMGScene::GetSubdMeshInstances() const\n{\n    return const_cast<RTXMGScene*>(this)->GetSubdMeshInstances();\n}\nstd::span<Instance> RTXMGScene::GetSubdMeshInstances()\n{\n    if (!m_subdMeshes.empty())\n        return std::span<Instance>(m_instances).subspan(0);\n    return {};\n}\n\nuint32_t RTXMGScene::TotalSubdPatchCount() const\n{\n    const auto& instances = GetSubdMeshInstances();\n    const auto& subds = GetSubdMeshes();\n\n    uint32_t sum{ 0 };\n    for (auto i = instances.begin(); i != instances.end(); ++i)\n        sum += subds[i->meshID]->SurfaceCount();\n    return sum;\n}\n\nvoid RTXMGScene::Animate(float animTime, float animRate)\n{\n    for (auto& subd : m_subdMeshes)\n    {\n        subd->Animate(animTime, animRate);\n    }\n\n    for (auto& instance : m_instances)\n    {\n        instance.Animate(animTime, animRate);\n    }\n}\n\nstatic Instance& operator << (Instance& instance, const Json::Value& node)\n{\n    if (const auto& value = node[\"translation\"]; !value.isNull())\n        value >> instance.translation;\n\n    if (const auto& value = node[\"rotation\"]; !value.isNull())\n    {\n        if (node.isArray() && node.size() == 4)\n            throw std::runtime_error(\"expecting 4-component quaternion for node's 'rotation' (use 'euler' otherwise)\");\n        value >> instance.rotation;\n    }\n    else if (const auto& value = node[\"euler\"]; !value.isNull())\n    {\n        float3 euler = { 0.0, 0.0, 0.0 };\n        value >> euler;\n        euler *= float(std::numbers::pi) / 180.f;\n        instance.rotation = donut::math::rotationQuat<float>(euler);\n    }\n\n    if (const auto& value = node[\"scaling\"]; !value.isNull())\n        value >> instance.scaling;\n\n    instance.UpdateLocalTransform();\n\n    return instance;\n}\n\n\n//\n// View\n//\n\nstatic View& operator << (View& view, const Json::Value& node)\n{\n    if (const auto& value = node[\"position\"]; !value.isNull())\n        value >> view.position;\n    if (const auto& value = node[\"lookat\"]; !value.isNull())\n        value >> view.lookat;\n    if (const auto& value = node[\"up\"]; !value.isNull())\n        value >> view.up;\n    if (const auto& value = node[\"fov\"]; !value.isNull())\n        value >> view.fov;\n\n    return view;\n}\n\n\nstatic RTXMGScene::Attributes& operator << (RTXMGScene::Attributes& attrs, const Json::Value& node)\n{\n    if (const auto& value = node[\"audio\"]; value.isString())\n        value >> attrs.audio;\n\n    if (const auto& value = node[\"audio start time\"]; value.isDouble())\n        value >> attrs.audioStartTime;\n\n    if (const auto& value = node[\"frame range\"]; value.isArray())\n        value >> attrs.frameRange;\n\n    if (const auto& value = node[\"frame rate\"]; value.isDouble())\n        value >> attrs.frameRate;\n\n    return attrs;\n}\n\nfs::path RTXMGScene::ResolveMediapath(const fs::path& m_filepath, const fs::path& mediapath)\n{\n    if (m_filepath.empty())\n        return {};\n\n    if (fs::is_regular_file(m_filepath))\n        return m_filepath;\n\n    if (!mediapath.empty() && fs::is_regular_file(mediapath / m_filepath))\n        return mediapath / m_filepath;\n\n    return {};\n}\n\nvoid RTXMGScene::LoadSceneFile(const std::filesystem::path& m_filepath, std::unique_ptr<ObjImporter>& objImporter, nvrhi::ICommandList* commandList)\n{\n    fs::path fp = m_filepath;\n\n    Json::Value jsonRoot;\n\n    try\n    {\n        jsonRoot = readFile(fp);\n    }\n    catch (const std::exception& e)\n    {\n        log::fatal(\"failed to Parse JSON file '%s': %s\", fp.generic_string().c_str(), e.what());\n    }\n    if (jsonRoot.isObject())\n    {\n        objImporter->SetModelPath(fp.parent_path());\n\n        const Json::Value& models = jsonRoot[\"models\"];\n        const Json::Value& graph = jsonRoot[\"graph\"];\n\n        if (!models.isArray() || !graph.isArray())\n            throw std::runtime_error(\"need valid 'models' and 'graph' arrays in '\" + fp.generic_string() + \"'\");\n\n        uint32_t nmodels = models.size();\n\n        for (uint32_t i = 0; i < graph.size(); ++i)\n        {\n            const Json::Value& node = graph[i];\n\n            Instance instance;\n\n            instance << node;\n\n            std::string nodeName;\n            if (const auto& name = node[\"name\"]; name.isString())\n                nodeName = name.asString();\n\n            if (const auto& modelNode = node[\"model\"]; !modelNode.isNull())\n            {\n                if (!modelNode.isIntegral())\n                    throw std::runtime_error(\"'model' value for graph node '\" + nodeName + \"' must be an index\");\n\n                int modelIndex = modelNode.asInt();\n                if (modelIndex < 0 || modelIndex >= (int)nmodels)\n                    throw std::runtime_error(\"out of bounds 'model' index for graph node '\" + nodeName + \"'\");\n\n                const Json::Value& modelName = models[modelIndex];\n\n                if (!modelName.isString())\n                    throw std::runtime_error(\"invalid model path in 'models' section\");\n\n                float frameOffset = 0.0f;\n                if (const auto& offset = node[\"frameoffset\"]; offset.isDouble())\n                {\n                    frameOffset = offset.asFloat();\n                }\n\n                auto model =\n                    objImporter->Load(modelName.asString(), *m_TextureCache, { 0, 0 }, instance, commandList);\n\n                if (model.has_value())\n                    InsertModel(std::move(*model));\n            }\n\n            if (const auto& type = node[\"type\"]; type.isString())\n                throw std::runtime_error(\"'type' token for graph node '\" + nodeName + \"' not supported\");\n\n            if (const auto& parent = node[\"parent\"]; !parent.isNull())\n                throw std::runtime_error(\"'parent' token for graph node '\" + nodeName + \"' not supported\");\n\n            if (const auto& children = node[\"children\"]; !children.isNull())\n                throw std::runtime_error(\"'children' token for graph node '\" + nodeName + \"' not supported\");\n        }\n\n        if (Json::Value& view = jsonRoot[\"view\"]; view.isObject())\n        {\n            m_view = std::make_unique<View>();\n            *m_view << view;\n        }\n\n        if (Json::Value& settings = jsonRoot[\"settings\"]; settings.isObject())\n        {\n            m_sceneSettings = settings; // so the app can use these settings to override some of its own behavior\n            m_attributes << settings;\n        }\n    }\n    else\n    {\n        log::fatal(\"failed to Parse JSON file '%s'\", fp.generic_string().c_str());\n    }\n}\n\nbool RTXMGScene::LoadWithExecutor(const std::filesystem::path& filename,\n    tf::Executor* executor)\n{\n    if (executor != nullptr)\n    {\n        log::warning(\"RTXMGScene::LoadWithExecutor: executor based loading is not \"\n            \"supported, ignoring\");\n    }\n    log::info(\"RTXMGScene::LoadWithExecutor: %s\", filename.string().c_str());\n\n    TopologyCache topologyCache(TopologyCache::Options{\n        .isoLevelSharp = (uint8_t)m_isoLevelSharp,\n        .isoLevelSmooth = (uint8_t)m_isoLevelSmooth,\n        });\n\n    fs::path sanitizedFilePath = filename;\n    std::string sceneName = sanitizedFilePath.empty() ? \"default_scene\" : sanitizedFilePath.filename().generic_string();\n\n    auto commandList = m_Device->createCommandList();\n    commandList->open();\n    {\n        std::unique_ptr<ObjImporter> objImporter =\n            std::make_unique<ObjImporter>(m_fs, m_mediaPath, m_SceneTypeFactory, m_DescriptorTable, topologyCache);\n\n        if (sanitizedFilePath.empty() || sanitizedFilePath.extension() == \".obj\")\n        {\n            // obj importer will default to a cube without a filename\n            log::info(\"RTXMGScene::LoadWithExecutor: Loading an OBJ file\");\n\n            auto model =\n                objImporter->Load(sanitizedFilePath, *m_TextureCache, m_attributes.frameRange,\n                    Instance{}, commandList);\n\n            if (model.has_value())\n            {\n                InsertModel(std::move(*model));\n            }\n            else\n            {\n                log::fatal(\"RTXMGScene::LoadWithExecutor: Failed to load the OBJ file\");\n            }\n        }\n        else if (sanitizedFilePath.extension() == \".json\")\n        {\n            log::info(\"RTXMGScene::LoadWithExecutor: Loading a JSON file\");\n            LoadSceneFile(sanitizedFilePath, objImporter, commandList);\n        }\n        else\n        {\n            log::fatal(\"RTXMGScene::LoadWithExecutor: Unsupported file format\");\n            return false;\n        }\n\n        m_attributes.averageInstanceScale = 0.0f;\n        for (const auto& instance : m_instances)\n        {\n            m_attributes.averageInstanceScale +=\n                maxComponent(instance.aabb.m_maxs - instance.aabb.m_mins);\n            m_attributes.aabb |= instance.aabb;\n        }\n\n        m_attributes.averageInstanceScale /= float(m_instances.size());\n        if ((m_attributes.frameRange.y - m_attributes.frameRange.x) > 1 &&\n            m_attributes.frameRate == 0.f)\n        {\n            m_attributes.frameRate = 24.0f;\n        }\n\n        m_topologyMaps = topologyCache.InitDeviceData(m_DescriptorTable, commandList);\n\n        m_inputPath = sanitizedFilePath.lexically_normal().generic_string();\n    }\n    commandList->close();\n    m_Device->executeCommandList(commandList);\n\n    /// Convert control cage to donut scenegraph for easy viz\n    std::shared_ptr<SceneGraphNode> root = std::make_shared<SceneGraphNode>();\n    root->SetName(sceneName);\n    m_Models.emplace_back(SceneImportResult{ root });\n\n    m_SceneGraph = std::make_shared<SceneGraph>();\n    m_SceneGraph->SetRootNode(m_Models.front().rootNode);\n\n    \n    std::vector<std::shared_ptr<MeshInfo>> sceneMeshInfos;\n    for (auto& subdMesh : m_subdMeshes)\n    {\n        auto shape = subdMesh->GetShape();\n    \n        // Add scene mesh info with dummy vertices\n        // This is to set up GeometryData and Material data in a donut compatible way\n        auto meshInfo = m_SceneTypeFactory->CreateMesh();\n        sceneMeshInfos.push_back(meshInfo);\n        meshInfo->name = shape->filepath.empty() ? \"default_shape\" : shape->filepath.generic_string();\n        meshInfo->buffers = std::make_shared<BufferGroup>(); // empty dummy buffer group\n        meshInfo->buffers->positionData.resize(4, float3::zero()); // add dummy vertex buffer. Must be 16 byte aligned\n        meshInfo->objectSpaceBounds = shape->aabb;\n        meshInfo->totalIndices = 0;\n        meshInfo->totalVertices = 0;\n\n        auto addTexture = [&sanitizedFilePath, this](const fs::path& shapePath, const std::string& mtlLib, const std::string& texPath, bool* enable) -> std::shared_ptr<LoadedTexture>\n            {\n                *enable = false;\n                if (texPath.empty())\n                {\n                    return nullptr;\n                }\n                fs::path fp = (((shapePath.parent_path() / mtlLib)).parent_path() / texPath).lexically_normal();\n                if (!is_regular_file(fp))\n                {\n                    fp = ResolveMediapath(fp, m_mediaPath);\n                }\n                if (is_regular_file(fp))\n                {\n                    *enable = true;\n                    return m_TextureCache->LoadTextureFromFileDeferred(fp, false);\n                }\n                else\n                {\n                    log::warning(\"Texture %s not found...\", fp.generic_string().c_str());\n\n                }\n                return nullptr;\n            };\n\n        std::vector<std::shared_ptr<Material>> materials(shape->mtls.size());\n\n        meshInfo->geometries.reserve(shape->subshapes.size());\n        size_t numFaces = shape->nvertsPerFace.size();\n        size_t vertIndex = 0;\n        for (uint32_t subshapeIndex = 0; subshapeIndex < shape->subshapes.size(); subshapeIndex++)\n        {\n            const auto& subshape = shape->subshapes[subshapeIndex];\n\n            // lazy initialize of materials\n            if (!materials[subshape.mtlBind].get())\n            {\n                const auto& mtl = shape->mtls[subshape.mtlBind];\n                auto material = m_SceneTypeFactory->CreateMaterial();\n                material->baseOrDiffuseColor = mtl->kd;\n                material->specularColor = mtl->ks;\n                // material->specularExponent = mtl->ns;\n                material->emissiveColor = mtl->ke;\n                material->roughness = mtl->Pr;\n                material->baseOrDiffuseTexture = addTexture(shape->filepath, shape->mtllib, mtl->map_kd, &material->enableBaseOrDiffuseTexture);\n                material->metalRoughOrSpecularTexture = addTexture(shape->filepath, shape->mtllib, mtl->map_pr, &material->enableMetalRoughOrSpecularTexture);\n\n                // hijack emissive texture for metalness for now (so we can use the existing Donut material structs)\n                material->emissiveTexture = addTexture(shape->filepath, shape->mtllib, mtl->map_pm, &material->enableEmissiveTexture);\n\n                // hijack occlusion texture for specular for now (so we can use the existing Donut material structs)\n                material->occlusionTexture = addTexture(shape->filepath, shape->mtllib, mtl->map_ks, &material->enableOcclusionTexture);\n\n                material->normalTexture = addTexture(shape->filepath, shape->mtllib, mtl->map_bump, &material->enableNormalTexture);\n                material->normalTextureScale = mtl->bm;\n                material->metalness = mtl->Pm;\n\n                materials[subshape.mtlBind] = material;\n            }\n\n            std::shared_ptr<MeshGeometry> geometry = std::make_shared<MeshGeometry>();\n            geometry->material = materials[subshape.mtlBind];\n            geometry->numIndices = 0;\n            geometry->numVertices = 0;\n            geometry->objectSpaceBounds = box3::empty();\n            geometry->indexOffsetInMesh = 0;\n            geometry->vertexOffsetInMesh = 0;\n            meshInfo->geometries.push_back(geometry);\n\n            size_t endFaceIndex = (subshapeIndex + 1) < shape->subshapes.size() ?\n                shape->subshapes[subshapeIndex + 1].startFaceIndex :\n                numFaces;\n\n            for (size_t faceIndex = subshape.startFaceIndex; faceIndex < endFaceIndex; faceIndex++)\n            {\n                for (size_t v_offset = 0; v_offset < shape->nvertsPerFace[faceIndex];\n                    ++v_offset)\n                {\n                    geometry->objectSpaceBounds |=\n                        shape->verts[shape->faceverts[vertIndex + v_offset]];\n                }\n                vertIndex += shape->nvertsPerFace[faceIndex];\n            }\n\n            meshInfo->objectSpaceBounds |= geometry->objectSpaceBounds;\n\n            if (geometry->material->normalTexture.get() && geometry->material->enableNormalTexture)\n            {\n                subdMesh->m_hasDisplacementMaterial = true;\n            }\n        }\n    }\n\n    auto subdMeshInstances = GetSubdMeshInstances();\n    uint32_t instanceIndex = 0;\n    for (auto& instance : subdMeshInstances)\n    {\n        auto meshInfo = sceneMeshInfos[instance.meshID];\n        auto meshInstance = m_SceneTypeFactory->CreateMeshInstance(meshInfo);\n\n        // connect our mesh instances to donut's\n        instance.meshInstance = meshInstance;\n\n        auto node = std::make_shared<SceneGraphNode>();\n\n        node->SetScaling(double3(instance.scaling));\n        node->SetRotation(dquat(instance.rotation));\n        node->SetTranslation(double3(instance.translation));\n\n        m_SceneGraph->Attach(root, node);\n\n        node->SetLeaf(meshInstance);\n\n        std::string meshInstanceName = meshInfo->name + \"_\" + std::to_string(instanceIndex);\n        node->SetName(meshInstanceName);\n\n        instanceIndex++;\n    }\n\n    PrintSceneGraph(root);\n\n    SceneGraphWalker walker(m_SceneGraph->GetRootNode().get());\n    int boxCount = 0;\n    while (walker)\n    {\n        auto leaf = walker->GetLeaf();\n        if (leaf)\n        {\n            auto box = leaf->GetLocalBoundingBox();\n            m_attributes.averageInstanceScale += MaxBoxExtent(box);\n            boxCount++;\n        }\n        walker.Next(true);\n    }\n    m_attributes.averageInstanceScale /= boxCount;\n\n    return true;\n}\n"
  },
  {
    "path": "rtxmg/scene/string_utils.cpp",
    "content": "//\n// Copyright (c) 2012-2016, NVIDIA CORPORATION. All rights reserved.\n//\n// NVIDIA CORPORATION and its licensors retain all intellectual property\n// and proprietary rights in and to this software, related documentation\n// and any modifications thereto. Any use, reproduction, disclosure or\n// distribution of this software and related documentation without an express\n// license agreement from NVIDIA CORPORATION is strictly prohibited.\n//\n\n#include \"rtxmg/scene/string_utils.h\"\n\n#include <donut/core/log.h>\n\n#include <fstream>\n\nnamespace fs = std::filesystem;\n\nchar const* ParseInt(char const* ptr, int* value)\n{\n    ptr = SkipWhiteSpace(ptr);\n\n    int sign = 1;\n    if (*ptr == '-')\n    {\n        sign = -1;\n        ++ptr;\n    }\n\n    int num = 0;\n    while (IsDigit(*ptr))\n        num = 10 * num + (*ptr++ - '0');\n\n    *value = sign * num;\n    return ptr;\n}\n\nchar const* ParseDouble(char const* ptr, double* value)\n{\n    static double const kPowersPos[] = {\n        1.0e0,  1.0e1,  1.0e2,  1.0e3,  1.0e4,  1.0e5,  1.0e6,\n        1.0e7,  1.0e8,  1.0e9,  1.0e10, 1.0e11, 1.0e12, 1.0e13,\n        1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19,\n    };\n\n    static double const kPowersNeg[] = {\n        1.0e0,   1.0e-1,  1.0e-2,  1.0e-3,  1.0e-4,  1.0e-5,  1.0e-6,\n        1.0e-7,  1.0e-8,  1.0e-9,  1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13,\n        1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19,\n    };\n    static constexpr uint8_t npowers = sizeof(kPowersPos) / sizeof(double);\n\n    double sign = 1.0;\n\n    ptr = SkipWhiteSpace(ptr);\n\n    if (*ptr == '-')\n    {\n        sign = -1;\n        ++ptr;\n    }\n    else if (*ptr == '+')\n    {\n        ++ptr;\n    }\n\n    double num = 0.0;\n    while (IsDigit(*ptr))\n        num = 10.0 * num + (double)(*ptr++ - '0');\n\n    if (*ptr == '.')\n        ++ptr;\n\n    double frac = 0.0, div = 1.0;\n    while (IsDigit(*ptr))\n    {\n        frac = 10.0 * frac + (double)(*ptr++ - '0');\n        div *= 10.0;\n    }\n    num += frac / div;\n\n    if (IsExponent(*ptr))\n    {\n        ptr++;\n        double const* powers = nullptr;\n        if (*ptr == '+')\n        {\n            powers = kPowersPos;\n            ++ptr;\n        }\n        else if (*ptr == '-')\n        {\n            powers = kPowersNeg;\n            ++ptr;\n        }\n        else\n        {\n            powers = kPowersPos;\n        }\n\n        int e = 0;\n        while (IsDigit(*ptr))\n            e = 10 * e + (*ptr++ - '0');\n\n        num *= (e >= npowers) ? 0.0 : powers[e];\n    }\n\n    *value = sign * num;\n\n    return ptr;\n}\n\nstd::unique_ptr<uint8_t[]> ReadBigFile(fs::path const& m_filepath,\n    uint64_t* size)\n{\n    if (std::ifstream file(m_filepath, std::ios::binary); file.is_open())\n    {\n        auto tstart = std::chrono::steady_clock::now();\n\n        std::streampos start = file.tellg();\n        file.seekg(0, std::ios::end);\n        std::streampos end = file.tellg();\n        file.seekg(0, std::ios::beg);\n        uint64_t length = end - start;\n\n        if (length > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))\n            return nullptr;\n\n        std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(length + 1);\n        file.read((char*)data.get(), length);\n\n        data[length] = '\\0';\n\n        if (size)\n            *size = length;\n\n        {\n            auto tstop = std::chrono::steady_clock::now();\n            std::chrono::duration<float> elapsed = tstop - tstart;\n            donut::log::info(\"read (%.2f seconds) '%s'\", elapsed.count(),\n                m_filepath.generic_string().c_str());\n        }\n\n        if (file.good())\n            return std::move(data);\n    }\n    donut::log::error(\"reading '%s'\\n\", m_filepath.generic_string().c_str());\n    return nullptr;\n}\n\nchar const* ParseString(char const* ptr, std::string* value)\n{\n    char const* wordEnd = SkipWord(ptr);\n    *value = std::string(ptr, wordEnd);\n    return wordEnd;\n}\n\nstd::string ReadASCIIFile(char const* m_filepath)\n{\n    std::ifstream ifs(m_filepath);\n\n    if (!ifs)\n        throw std::runtime_error(std::string(\"Cannot find: \") + m_filepath);\n\n    std::stringstream ss;\n    ss << ifs.rdbuf();\n    ifs.close();\n\n    std::string s = ss.str();\n    if (s.empty())\n        throw std::runtime_error(std::string(\"Read error: \") + m_filepath);\n\n    return std::move(s);\n}\n\nchar const* sgets(char* s, int size, char** stream)\n{\n    for (int i = 0; i < size; ++i)\n    {\n        if ((*stream)[i] == '\\n' || (*stream)[i] == '\\0')\n        {\n\n            memcpy(s, *stream, i);\n            s[i] = '\\0';\n\n            if ((*stream)[i] == '\\0')\n                return 0;\n            else\n            {\n                (*stream) += i + 1;\n                return s;\n            }\n        }\n    }\n    return 0;\n}\nstd::istream& operator>>(std::istream& is, float3& v)\n{\n    // parse [x,y,z]\n    char st;\n    is >> st >> v.x >> st >> v.y >> st >> v.z >> st;\n    return is;\n}\nstd::ostream& operator<<(std::ostream& os, float3& v)\n{\n    os << \"[\" << v.x << \",\" << v.y << \",\" << v.z << \"]\";\n    return os;\n}\nstd::ostream& operator<<(std::ostream& os, box3& b)\n{\n    os << b.m_mins << \" --> \" << b.m_maxs;\n    return os;\n}\n"
  },
  {
    "path": "rtxmg/subdivision/CMakeLists.txt",
    "content": "set(lib subdivision)\nset(folder RTXMG)\n\nfile(GLOB sources \"*.cpp\" \"../include/rtxmg/${lib}/*.h\" \"../include/rtxmg/${lib}/osd_ports/tmr/*.h\" \"../include/rtxmg/${lib}/*.hlsli\" *.cfg)\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include\"\n)\ntarget_link_libraries(${lib} donut_engine profiler osd_lite_static implot)\nset_target_properties(${lib} PROPERTIES FOLDER ${folder})"
  },
  {
    "path": "rtxmg/subdivision/shape.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <sstream>\n\n#include <cstring>\n#include <donut/core/log.h>\n#include <donut/core/math/math.h>\n#include <cassert>\n#include <iostream>\n#include <iterator>\n#include <fstream>\n#include <array>\n#include <map>\n#include <tuple>\n\n#include \"rtxmg/subdivision/shape.h\"\n#include \"rtxmg/scene/string_utils.h\"\n#include \"rtxmg/utils/debug.h\"\n\nusing namespace donut::math;\nusing namespace donut;\n\nnamespace fs = std::filesystem;\n\n#define mtl_assert(cond, msg) \\\nlogassert(cond, \"Malformed material file %s:%d: %s\", m_filepath, lineNumber, msg)\n\n#define NEED_MTL() mtl_assert(mtl != nullptr, \"No material defined\");\n\n#define obj_assert(cond, msg) \\\nlogassert(cond, \"Malformed obj file %s:%d: %s\", m_filepath, lineNumber, msg)\n\nstatic std::vector<std::unique_ptr<Shape::material>>\nparseMtllib(const char* m_filepath)\n{\n    std::vector<std::unique_ptr<Shape::material>> mtls;\n\n    std::string mtlstring = ReadASCIIFile(m_filepath);\n    char* str = const_cast<char*>(mtlstring.c_str()), line[256];\n\n    Shape::material* mtl = nullptr;\n\n    bool done = false;\n    float r, g, b, a;\n    int lineNumber = 0;\n    while (!done)\n    {\n        done = sgets(line, sizeof(line), &str) == 0;\n        lineNumber++;\n\n        size_t lineLen = strlen(line);\n        if (lineLen == 0)\n            continue;\n\n        char* end = &line[lineLen - 1];\n        if (*end == '\\n')\n            *end = '\\0'; // strip trailing nl\n        switch (line[0])\n        {\n        case 'n':\n        {\n            char name[256] = { \"\" };\n            if (sscanf_s(line, \"newmtl %255s\", name, 256) == 1)\n            {\n                mtl = mtls.emplace_back(std::make_unique<Shape::material>()).get();\n                mtl->name = name;\n            }\n        } break;\n        case 'K':\n            NEED_MTL();\n            mtl_assert(sscanf_s(line + 2, \" %f %f %f\", &r, &g, &b) == 3, \"Missing RGB values\");\n            switch (line[1])\n            {\n            case 'a':\n                mtl->ka[0] = r;\n                mtl->ka[1] = g;\n                mtl->ka[2] = b;\n                break;\n            case 'd':\n                mtl->kd[0] = r;\n                mtl->kd[1] = g;\n                mtl->kd[2] = b;\n                break;\n            case 's':\n                mtl->ks[0] = r;\n                mtl->ks[1] = g;\n                mtl->ks[2] = b;\n                break;\n            case 'e':\n                mtl->ke[0] = r;\n                mtl->ke[1] = g;\n                mtl->ke[2] = b;\n                break;\n            default:\n                mtl_assert(false, \"Unknown K value\");\n            }\n            break;\n        case 'N':\n            NEED_MTL();\n            mtl_assert(sscanf_s(line + 2, \" %f\", &a) == 1, \"Missing N value\");\n            switch (line[1])\n            {\n            case 's':\n                mtl->ns = a;\n                break;\n            case 'i':\n                mtl->ni = a;\n                break;\n            default:\n                mtl_assert(false, \"Unknown N\");\n            }\n            break;\n        case 'd':\n            NEED_MTL();\n            mtl_assert(sscanf_s(line, \"d %f\", &a) == 1, \"malformed dissolve\");\n            mtl->d = a;\n            break;\n        case 'T':\n            NEED_MTL();\n            mtl_assert(sscanf_s(line, \"Tf %f %f %f\", &r, &g, &b) == 3, \"Malformed transmission filter\");\n            mtl->tf[0] = r;\n            mtl->tf[1] = g;\n            mtl->tf[2] = b;\n            break;\n        case 'i':\n            NEED_MTL();\n            int illum;\n            mtl_assert(sscanf_s(line, \"illum %d\", &illum) == 1, \"Malformed illum\");\n            mtl->illum = illum;\n            break;\n        case 's':\n            NEED_MTL();\n            if (sscanf_s(line, \"sharpness %f\", &a) == 1)\n                mtl->sharpness = a;\n            break;\n        case 'm':\n            NEED_MTL();\n            if (strncmp(line, \"map_\", 4) == 0)\n            {\n                char buf[1024];\n\n                switch (line[4])\n                {\n                case 'K':\n                    mtl_assert(sscanf_s(line + 6, \" %1023s\", buf, 1024) == 1, \"Malformed map_K line\");\n                    switch (line[5])\n                    {\n                    case 'a':\n                        mtl->map_ka = buf;\n                        break;\n                    case 'd':\n                        mtl->map_kd = buf;\n                        break;\n                    case 'e':\n                        mtl->map_ke = buf;\n                        break;\n                    case 's':\n                        mtl->map_ks = buf;\n                        break;\n                    }\n                    break;\n                case 'B':\n                    if (sscanf_s(line + 5, \"ump -bm %f -bb %f %1023s\", &mtl->bm, &mtl->bb,\n                        buf, 1024) == 3)\n                        mtl->map_bump = buf;\n                    else if (sscanf_s(line + 5, \"ump -bm %f %1023s\", &mtl->bm, buf, 1024) == 2)\n                        mtl->map_bump = buf;\n                    else if (sscanf_s(line + 5, \"ump %1023s\", buf, 1024) == 1)\n                        mtl->map_bump = buf;\n                    else mtl_assert(false, \"Malformed bump map line\");\n                    break;\n                case 'P':\n                    switch (line[5])\n                    {\n                    case 'r':\n                        mtl_assert(sscanf_s(line + 5, \"r %1023s\", buf, 1024) == 1, \"Malformed map_Pr line\");\n                        mtl->map_pr = buf;\n                        break;\n                    case 'm':\n                        mtl_assert(sscanf_s(line + 5, \"m %1023s\", buf, 1024) == 1, \"Malformed map_Pm line\");\n                        mtl->map_pm = buf;\n                        break;\n                    default:\n                        mtl_assert(false, \"Unknown map_P value\");\n                    }\n                    break;\n                case 'R':\n                    mtl_assert(sscanf_s(line + 5, \"ma %1023s\", buf, 1024) == 1, \"Malformed map_R line\");\n                    mtl->map_rma = buf;\n                    break;\n                case 'O':\n                    mtl_assert(sscanf_s(line + 5, \"rm %1023s\", buf, 1024) == 1, \"Malformed map_KO line\");\n                    mtl->map_orm = buf;\n                    break;\n                }\n            }\n            break;\n        case 'P':\n            NEED_MTL();\n            switch (line[1])\n            {\n            case 'r':\n                mtl_assert(sscanf_s(line + 2, \" %f\", &mtl->Pr) == 1, \"Malformed Pr line\");\n                break;\n            case 'm':\n                mtl_assert(sscanf_s(line + 2, \" %f\", &mtl->Pm) == 1, \"Malformed Pm line\");\n                break;\n            case 's':\n                mtl_assert(sscanf_s(line + 2, \" %f\", &mtl->Ps) == 1, \"Malformed Ps line\");\n                break;\n            case 'c':\n                switch (line[2])\n                {\n                case ' ':\n                    mtl_assert(sscanf_s(line + 2, \" %f\", &mtl->Pc) == 1, \"Malformed Pc line\");\n                    break;\n                case 'r':\n                    mtl_assert(sscanf_s(line + 3, \" %f\", &mtl->Pcr) == 1, \"Malformed Pcr line\");\n                    break;\n                }\n            }\n            break;\n        case 'a':\n            NEED_MTL();\n            {\n                float a = 0.f;\n                if (sscanf_s(line, \"aniso %f\", &a) == 1)\n                    mtl->aniso = a;\n                else if (sscanf_s(line, \"anisor %f\", &a) == 1)\n                    mtl->anisor = a;\n                else mtl_assert(false, \"Malformed aniso line\");\n            }\n            break;\n        }\n    }\n    return mtls;\n}\n\nstatic char const* parseDouble2(char const* ptr, std::vector<float2>& values)\n{\n    double x, y;\n    ptr = ParseDouble(ptr, &x);\n    ptr = ParseDouble(ptr, &y);\n    values.push_back({ (float)x, (float)y });\n    return ptr;\n}\n\nstatic char const* parseDouble3(char const* ptr, std::vector<float3>& values)\n{\n    double x, y, z;\n    ptr = SkipWhiteSpace(ptr);\n    ptr = ParseDouble(ptr, &x);\n    ptr = ParseDouble(ptr, &y);\n    ptr = ParseDouble(ptr, &z);\n    values.push_back({ (float)x, (float)y, (float)z });\n    return ptr;\n}\n\nstatic char const* parseFace(char const* ptr, std::vector<int>& vertcounts,\n    std::vector<int>& verts, std::vector<int>& uvs,\n    std::vector<int>& facenormals)\n{\n    static constexpr int invalid_id = -1;\n\n    ptr = SkipWhiteSpace(ptr);\n\n    uint8_t count = 0;\n    int4 vert = { invalid_id, invalid_id, invalid_id, invalid_id };\n    while (*ptr && !IsNewLine(*ptr))\n    {\n        ptr = ParseInt(ptr, &vert.x);\n        if (*ptr == '/')\n        {\n            ++ptr;\n            if (*ptr != '/')\n                ptr = ParseInt(ptr, &vert.y);\n\n            if (*ptr == '/')\n                ptr = ParseInt(++ptr, &vert.z);\n        }\n\n        if (vert.x != invalid_id)\n            verts.push_back(vert.x - 1);\n        if (vert.y != invalid_id)\n            uvs.push_back(std::max(0, vert.y - 1));\n        if (vert.z != invalid_id)\n            facenormals.push_back(std::max(0, vert.z - 1));\n\n        ++count;\n        ptr = SkipWhiteSpace(ptr);\n    }\n    vertcounts.push_back(count);\n    return ptr;\n}\n\nstd::unique_ptr<Shape> parseObj(char const* m_filepath, Scheme shapescheme,\n    bool isLeftHanded, bool parseMaterials)\n{\n    uint64_t size = 0;\n    std::unique_ptr<uint8_t const []> obj_str = ReadBigFile(m_filepath, &size);\n    std::unique_ptr<Shape> s = std::make_unique<Shape>();\n\n    s->scheme = shapescheme;\n    s->isLeftHanded = isLeftHanded;\n    s->aabb = box3::empty();\n\n    auto tstart = std::chrono::steady_clock::now();\n\n    \n    short usemtl = -1;\n    int delta = 0;\n    uint64_t lineNumber = 1;\n\n    char groupName[512] = { 0 };\n    char buf[256];\n    char line[1024];\n    char* str = (char *)(obj_str.get());\n\n    bool done = false;\n    while (!done)\n    {\n        done = sgets(line, sizeof(line), &str) == 0;\n        if (line[0])\n        {\n            char* end = &line[strlen(line) - 1];\n            if (*end == '\\n')\n                *end = '\\0';  // strip trailing nl\n        }\n        const char* ptr = line;\n        switch (*ptr)\n        {\n        case 'v':\n            ++ptr;\n            switch (*ptr)\n            {\n            case ' ':\n                ptr = parseDouble3(ptr, s->verts);\n                s->aabb |= s->verts.back();\n                break;\n            case 't':\n                ++ptr;\n                ptr = parseDouble2(ptr, s->uvs);\n                break;\n            case 'n':\n                ++ptr;\n                ptr = parseDouble3(ptr, s->normals);\n                break;\n            }\n            break;\n        case 'f':\n            ++ptr;\n            if (*ptr == ' ' || *ptr == '\\t')\n            {\n                ptr = parseFace(ptr, s->nvertsPerFace, s->faceverts, s->faceuvs,\n                    s->facenormals);\n                if (!s->mtls.empty())\n                {\n                    s->mtlbind.push_back(usemtl);\n                }\n            }\n            break;\n        case 't':\n            ++ptr;\n            if (*ptr == ' ')\n            {\n                Shape::tag t;\n                if (Shape::tag::ParseTag(ptr, &t))\n                    s->tags.emplace_back(std::move(t));\n            }\n            break;\n        case 'g':\n            ++ptr;\n            if (*ptr == ' ')\n            {\n                obj_assert(sscanf_s(ptr, \" %255s%n\", groupName, 256, &delta) == 1, \"Malformed group name\");\n                ptr += delta;\n            }\n            break;\n        case 'u':\n            obj_assert(sscanf_s(ptr, \"usemtl %255s%n\", buf, 256, &delta) == 1, \"Malformed usemtl\");\n            usemtl = static_cast<short>(s->FindMaterial(buf));\n            ptr += delta;\n            break;\n        case 'm':\n            obj_assert(sscanf_s(ptr, \"mtllib %255s%n\", buf, 256, &delta) == 1, \"Malformed mtllib\");\n            if (parseMaterials)\n            {\n                fs::path p = buf;\n\n                if (!fs::is_regular_file(p))\n                    p = fs::path(m_filepath).parent_path() / buf;\n                if (fs::is_regular_file(p))\n                {\n                    s->mtls =\n                        parseMtllib(p.generic_string().c_str());\n                    s->mtllib = buf;\n                }\n            }\n            ptr += delta;\n            break;\n        case 'c':\n            obj_assert(sscanf_s(ptr, \"capslib %255s%n\", buf, 256, &delta) == 1, \"Malformed capslib\");\n            if (parseMaterials)\n            {\n                fs::path p = buf;\n\n                if (!fs::is_regular_file(p))\n                    p = fs::path(m_filepath).parent_path() / buf;\n\n                if (fs::is_regular_file(p))\n                    s->capslib = buf;\n            }\n            ptr += delta;\n            break;\n        case '#':\n            break;\n        }\n        ++lineNumber;\n    }\n\n    return s;\n}\n\nbool Shape::tag::ParseTag(char const* cp, tag* t)\n{\n\n    char buf[256];\n\n    while (*cp == ' ')\n        cp++;\n    if (sscanf_s(cp, \"%255s\", buf, 256) != 1)\n        return false;\n    while (*cp && *cp != ' ')\n        cp++;\n    t->name = buf;\n\n    int nints = 0, nfloats = 0, nstrings = 0;\n    while (*cp == ' ')\n        cp++;\n    if (sscanf_s(cp, \"%d/%d/%d\", &nints, &nfloats, &nstrings) != 3)\n        return false;\n    while (*cp && *cp != ' ')\n        cp++;\n\n    t->intargs.reserve(nints);\n    for (int i = 0; i < nints; ++i)\n    {\n        int val;\n        while (*cp == ' ')\n            cp++;\n        if (sscanf_s(cp, \"%d\", &val) != 1)\n            return false;\n        t->intargs.push_back(val);\n        while (*cp && *cp != ' ')\n            cp++;\n    }\n\n    t->floatargs.reserve(nfloats);\n    for (int i = 0; i < nfloats; ++i)\n    {\n        float val;\n        while (*cp == ' ')\n            cp++;\n        if (sscanf_s(cp, \"%f\", &val) != 1)\n            return false;\n        t->floatargs.push_back(val);\n        while (*cp && *cp != ' ')\n            cp++;\n    }\n\n    t->stringargs.reserve(nstrings);\n    for (int i = 0; i < nstrings; ++i)\n    {\n        char val[512];\n        while (*cp == ' ')\n            cp++;\n        if (sscanf_s(cp, \"%511s\", val, 512) != 1)\n            return false;\n        t->stringargs.push_back(std::string(val));\n        while (*cp && *cp != ' ')\n            cp++;\n    }\n    return true;\n}\n\nstd::string Shape::tag::GenTag() const\n{\n    std::stringstream t;\n\n    t << \"t \" << name << \" \";\n\n    t << intargs.size() << \"/\" << floatargs.size() << \"/\" << stringargs.size()\n        << \" \";\n\n    std::copy(intargs.begin(), intargs.end(), std::ostream_iterator<int>(t, \" \"));\n    // t<<\" \";\n\n    t << std::fixed;\n    std::copy(floatargs.begin(), floatargs.end(),\n        std::ostream_iterator<float>(t, \" \"));\n    // t<<\" \";\n\n    std::copy(stringargs.begin(), stringargs.end(),\n        std::ostream_iterator<std::string>(t, \" \"));\n    t << \"\\n\";\n\n    return t.str();\n}\n\nint Shape::FindMaterial(char const* name)\n{\n    for (int i = 0; i < (int)mtls.size(); ++i)\n        if (mtls[i]->name == name)\n            return i;\n    return -1;\n}\n\n//\n// serialization / deserialization\n//\n\nauto writeTrivial = []<typename T>(std::ofstream & os, T const& v) -> std::ofstream&\n{\n    static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>);\n    os.write(reinterpret_cast<char const*>(&v), sizeof(T));\n    return os;\n};\n\nauto readTrivial = []<typename T>(std::ifstream & is, T & v) -> std::ifstream&\n{\n    static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>);\n    is.read(reinterpret_cast<char*>(&v), sizeof(T));\n    return is;\n};\n\nstd::ofstream& operator<<(std::ofstream& os, float2 const& v)\n{\n    writeTrivial(os, v.x);\n    writeTrivial(os, v.y);\n    return os;\n}\n\nstd::ifstream& operator>>(std::ifstream& is, float2& v)\n{\n    readTrivial(is, v.x);\n    readTrivial(is, v.y);\n    return is;\n}\n\n\nstd::ofstream& operator<<(std::ofstream& os, float3 const& v)\n{\n    writeTrivial(os, v.x);\n    writeTrivial(os, v.y);\n    writeTrivial(os, v.z);\n    return os;\n}\n\nstd::ifstream& operator>>(std::ifstream& is, float3& v)\n{\n    readTrivial(is, v.x);\n    readTrivial(is, v.y);\n    readTrivial(is, v.z);\n    return is;\n}\n\n//template <unsigned int M, unsigned int N>\n//std::ofstream& operator<<(std::ofstream&w os, otk::Matrix<M, N> const& m)\n//{\n//\tconstexpr size_t m_size = M * N * sizeof(typename std::remove_pointer<decltype(m.getData())>::type);\n//\tos.write(reinterpret_cast<char const*>(m.getData()), m_size);\n//\treturn os;\n//}\n//\n//template <unsigned int M, unsigned int N>\n//std::ifstream& operator>>(std::ifstream& is, otk::Matrix<M, N>& m)\n//{\n//\tconstexpr size_t m_size = M * N * sizeof(typename std::remove_pointer<decltype(m.getData())>::type);\n//\tis.read(reinterpret_cast<char*>(m.getData()), m_size);\n//\treturn is;\n//}\n\nstd::ofstream& operator<<(std::ofstream& os, box3 const& aabb)\n{\n    os << aabb.m_mins;\n    os << aabb.m_maxs;\n    return os;\n}\n\nstd::ifstream& operator>>(std::ifstream& is, box3& aabb)\n{\n    is >> aabb.m_mins;\n    is >> aabb.m_maxs;\n    return is;\n}\n\nstd::ofstream& operator<<(std::ofstream& os, std::string const& s)\n{\n    if (writeTrivial(os, s.size()); !s.empty())\n        os.write(s.data(), s.size());\n    return os;\n}\n\nstd::ifstream& operator>>(std::ifstream& is, std::string& s)\n{\n    size_t size;\n    if (readTrivial(is, size); size > 0)\n    {\n        s.resize(size);\n        is.read(s.data(), size);\n    }\n    return is;\n}\n\ntemplate <typename T>\nstd::ofstream& operator<<(std::ofstream& os, std::vector<T> const& v)\n{\n    if (writeTrivial(os, v.size()); !v.empty())\n    {\n        if constexpr (std::is_trivial_v<T> && std::is_standard_layout_v<T>)\n            os.write(reinterpret_cast<char const*>(v.data()), v.size() * sizeof(T));\n        else\n            for (size_t i = 0; i < v.size(); ++i)\n                os << v[i];\n    }\n    return os;\n}\n\ntemplate <typename T>\nstd::ifstream& operator>>(std::ifstream& is, std::vector<T>& v)\n{\n    v.clear();\n    size_t size = 0;\n    if (readTrivial(is, size); size > 0)\n    {\n        v.resize(size);\n        if constexpr (std::is_trivial_v<T> && std::is_standard_layout_v<T>)\n            is.read(reinterpret_cast<char*>(v.data()), size * sizeof(T));\n        else\n            for (size_t i = 0; i < v.size(); ++i)\n                is >> v[i];\n    }\n    return is;\n}\n\nstd::ofstream& operator<<(std::ofstream& os, Shape::tag const& t)\n{\n    os << t.name;\n    os << t.intargs;\n    os << t.floatargs;\n    os << t.stringargs;\n    return os;\n}\n\nstd::ifstream& operator>>(std::ifstream& is, Shape::tag& t)\n{\n    is >> t.name;\n    is >> t.intargs;\n    is >> t.floatargs;\n    is >> t.stringargs;\n    return is;\n}\n\nvoid Shape::WriteShape(const std::string& objFile) const\n{\n    using namespace std::chrono;\n    fs::path cacheFile = fs::path(objFile).replace_extension(\".bin\");\n\n    if (std::ofstream os(cacheFile, std::ios::out | std::ofstream::binary); os.is_open())\n    {\n        system_clock::duration::rep objFileTimeStamp = (fs::last_write_time(objFile).time_since_epoch() + version).count();\n\n        writeTrivial(os, objFileTimeStamp);\n\n        os << verts;\n        os << normals;\n        os << uvs;\n        os << faceverts;\n        os << faceuvs;\n        os << facenormals;\n        os << nvertsPerFace;\n        os << tags;\n\n        os << mtllib;\n        os << mtlbind;\n\n        os << aabb;\n\n        os << capslib;\n    }\n}\n\n\nbool Shape::ReadShape(const std::string& objFile)\n{\n    using namespace std::chrono;\n\n    system_clock::duration::rep objFileTimeStamp = (fs::last_write_time(objFile).time_since_epoch() + version).count();\n\n    fs::path cacheFile = fs::path(objFile).replace_extension(\".bin\");\n\n    if (std::ifstream is(cacheFile, std::ios::in | std::ofstream::binary); is.is_open())\n    {\n        system_clock::duration::rep binFileTimStamp;\n\n        readTrivial(is, binFileTimStamp);\n\n        // if timestamp stored in .bin doesn't match the .obj's timestamp return false\n        // i.e. read/load the .obj file instead\n        if (binFileTimStamp == objFileTimeStamp)\n        {\n            is >> verts;\n            is >> normals;\n            is >> uvs;\n            is >> faceverts;\n            is >> faceuvs;\n            is >> facenormals;\n            is >> nvertsPerFace;\n            is >> tags;\n\n            is >> mtllib;\n            is >> mtlbind;\n\n            is >> aabb;\n\n            is >> capslib;\n            return true;\n        }\n    }\n    return false;\n}\n\n// Create shape with default cube geometry from catmark_cube.h\nstd::unique_ptr<Shape> Shape::DefaultShape()\n{\n    auto shape = std::unique_ptr<Shape>(new Shape);\n\n    shape->verts = std::vector<float3>{\n        {-0.5f, -0.5f, 0.5f},  {0.5f, -0.5f, 0.5f},  {-0.5f, 0.5f, 0.5f},\n        {0.5f, 0.5f, 0.5f},    {-0.5f, 0.5f, -0.5f}, {0.5f, 0.5f, -0.5f},\n        {-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f} };\n\n    shape->uvs = std::vector<float2>{\n        {0.375, 0.00}, {0.625, 0.00}, {0.375, 0.25}, {0.625, 0.25}, {0.375, 0.50},\n        {0.625, 0.50}, {0.375, 0.75}, {0.625, 0.75}, {0.375, 1.00}, {0.625, 1.00},\n        {0.875, 0.00}, {0.875, 0.25}, {0.125, 0.00}, {0.125, 0.25} };\n\n    shape->nvertsPerFace = std::vector<int>{ 4, 4, 4, 4, 4, 4 };\n\n    shape->faceverts = std::vector<int>{ 0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6,\n                                        6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4 };\n\n    shape->faceuvs = std::vector<int>{ 0, 1, 3, 2, 2, 3,  5,  4, 4,  5, 7, 6,\n                                      6, 7, 9, 8, 1, 10, 11, 3, 12, 0, 2, 13 };\n\n    shape->aabb = donut::math::box(float3(-.5f), float3(.5f));\n\n    return shape;\n}\n\n\n\n//\n// udim\n//\n\nstatic std::array<char const*, 2> udim_patterns = { \".<UDIM>\", \".<UVTILE>\" };\n\nstatic std::string udimPath(std::string const& filename, char const* udimID = nullptr)\n{\n    for (char const* pattern : udim_patterns)\n        if (size_t p = filename.find(pattern); p != std::string::npos)\n            return filename.substr(0, p + 1) + (udimID ? udimID : \"%04d\")\n            + filename.substr(p + strlen(pattern), std::string::npos);\n    return {};\n};\n\ntemplate <typename... Ts>\nconstexpr auto materialMaps(Shape::material& m)\n{\n    return std::forward_as_tuple(m.map_ka, m.map_kd, m.map_ks, m.map_bump, m.map_ke, m.map_pr, m.map_pm, m.map_rma, m.map_orm);\n}\ntemplate <typename... Ts>\nconstexpr auto materialMaps(Shape::material const& m)\n{\n    return std::forward_as_tuple(m.map_ka, m.map_kd, m.map_ks, m.map_bump, m.map_ke, m.map_pr, m.map_pm, m.map_rma, m.map_orm);\n}\nstatic bool hasUdims(Shape::material const& mtl)\n{\n    bool result = false;\n\n    auto hasUdim = [&result](std::string const& texpath)\n        {\n            if (result || texpath.empty())\n                return;\n            for (auto pattern : udim_patterns)\n                if (size_t p = texpath.find(pattern); p != std::string::npos)\n                {\n                    result = true;\n                    return;\n                }\n        };\n\n    std::apply([&result, &hasUdim](auto const&... maps) { (hasUdim(maps), ...); }, materialMaps(mtl));\n    return result;\n}\n\nstatic bool hasUdims(Shape const& shape)\n{\n    for (auto& mtl : shape.mtls)\n        if (hasUdims(*mtl))\n            return true;\n    return false;\n}\n\nstatic std::vector<uint32_t> findUdims(fs::path const& basepath, Shape::material const& mtl)\n{\n    std::vector<uint32_t> udims;\n\n    auto search = [&basepath, &udims](fs::path const& texpath)\n        {\n            if (udims.empty() && !texpath.empty())\n            {\n                if (std::string pattern = udimPath(texpath.filename().generic_string()); !pattern.empty())\n                {\n                    fs::path dir = (basepath / texpath.parent_path());\n\n                    if (!fs::is_directory(dir))\n                        return;\n\n                    for (auto const& entry : fs::directory_iterator(dir))\n                    {\n                        int id;\n                        if (sscanf_s(entry.path().filename().generic_string().c_str(), pattern.c_str(), &id) == 1)\n                            udims.push_back(id);\n                    }\n\n                    // remove duplicates (ex. caused by dds version of texture)\n                    std::sort(udims.begin(), udims.end());\n                    udims.erase(std::unique(udims.begin(), udims.end()), udims.end());\n\n                    if (udims.empty())\n                        throw std::runtime_error(std::string(\"cannot find udims for: \") + texpath.generic_string());\n                }\n            }\n        };\n\n    std::apply([&search](auto const&... maps) { (search(maps), ...); }, materialMaps(mtl));\n\n    return udims;\n}\n\nstatic std::unique_ptr<Shape::material> resolveUdim(fs::path const& basepath, Shape::material const& mtl, uint32_t udim)\n{\n    auto newMtl = std::make_unique<Shape::material>(mtl);\n\n    auto resolve = [&basepath, &udim](std::string& texpath)\n        {\n            if (texpath.empty())\n                return;\n\n            texpath = udimPath(texpath, std::to_string(udim).c_str());\n\n            if (!fs::is_regular_file(basepath / texpath))\n                throw std::runtime_error(std::string(\"cannot find udim: \") + (basepath / texpath).generic_string().c_str());\n        };\n\n    std::apply([&resolve](auto&... maps) { (resolve(maps), ...); }, materialMaps(*newMtl));\n\n    newMtl->udim = udim;\n\n    return newMtl;\n}\n\nstatic void resolveUdims(Shape& shape)\n{\n    if (!hasUdims(shape) || shape.mtlbind.empty() || shape.faceuvs.empty())\n        return;\n\n    fs::path basepath = shape.filepath.parent_path();\n\n    // generate new library where materials with udims are duplicated\n\n    std::vector<std::unique_ptr<Shape::material>> mtls;\n    mtls.reserve(shape.mtls.size() * 2);\n\n    std::map<uint64_t, uint32_t> mtlsMap;\n\n    auto makeKey = [](uint32_t mtlid, uint32_t udim) { return uint64_t(mtlid) << 32 | uint64_t(udim); };\n\n    for (uint32_t i = 0; i < shape.mtls.size(); ++i)\n    {\n        auto mtl = std::move(shape.mtls[i]);\n\n        if (hasUdims(*mtl))\n        {\n            std::vector<uint32_t> udims = findUdims(basepath, *mtl);\n\n            for (uint32_t udim : udims)\n            {\n                //printf(\"material %s : %d udim: %d -> %d\\n\", mtl->name.c_str(), i, udim, (uint32_t)mtls.m_size());\n                mtlsMap[makeKey(i, udim)] = static_cast<uint32_t>(mtls.size());\n                mtls.emplace_back(resolveUdim(basepath, *mtl, udim));\n            }\n        }\n        else\n        {\n            //printf(\"material %s : %d -> %d\\n\", mtl->name.c_str(), i, (uint32_t)mtls.m_size());\n            mtlsMap[makeKey(i, 0)] = static_cast<uint32_t>(mtls.size());\n            mtls.emplace_back(std::move(mtl));\n        }\n    }\n\n    mtls.shrink_to_fit();\n\n    // re-assign material bindings\n\n    assert(shape.mtlbind.size() == shape.GetNumFaces());\n\n    // see: https://learn.foundry.com/katana/Content/ug/checking_uvs/multi_tile_textures.html\n    auto makeUdim = [](float2 uv) -> uint32_t\n        {\n            return 1001 + uint32_t(std::trunc(uv.x) + 10 * std::trunc(uv.y));\n        };\n\n    std::vector<unsigned short> mtlbind(shape.mtlbind.size());\n\n    for (uint32_t face = 0, vertCount = 0; face < shape.GetNumFaces(); ++face)\n    {\n        uint32_t nverts = shape.nvertsPerFace[face];\n        uint32_t mtlId = shape.mtlbind[face];\n\n        auto it = mtlsMap.find(makeKey(mtlId, 0));\n\n        if (it == mtlsMap.end())\n        {\n            float2 texcoord = shape.uvs[shape.faceuvs[vertCount]];\n\n            uint32_t udim = makeUdim(texcoord);\n\n            it = mtlsMap.find(makeKey(mtlId, udim));\n\n            assert(it != mtlsMap.end() && mtls[it->second]->udim == udim);\n\n            for (uint32_t vert = 1; vert < nverts; ++vert)\n            {\n                texcoord = shape.uvs[shape.faceuvs[vertCount + vert]];\n\n                if (makeUdim(texcoord) != udim)\n                    throw std::runtime_error(std::string(\"udim crosses bounds for face \" + std::to_string(face)));\n            }\n        }\n        else\n            assert(mtls[it->second]->udim == 0);\n\n        mtlbind[face] = static_cast<short>(it->second);\n\n        vertCount += nverts;\n    }\n\n    shape.mtls = std::move(mtls);\n    shape.mtlbind = std::move(mtlbind);\n}\n\n\nstd::unique_ptr<Shape> Shape::LoadObjFile(const fs::path& m_filepath,\n    bool parseMaterials, bool requireUVs)\n{\n    std::unique_ptr<Shape> shape = std::make_unique<Shape>();\n\n    std::string filepathStr = m_filepath.lexically_normal().generic_string();\n\n    constexpr bool isLeftHanded = false;\n\n    if (!shape->ReadShape(filepathStr))\n    {\n        shape = parseObj(filepathStr.c_str(), Scheme::kCatmark, isLeftHanded,\n            parseMaterials);\n\n        if (!shape)\n        {\n            log::error(\"Error parsing obj file: %s\", filepathStr.c_str());\n            return nullptr;\n        }\n\n        shape->WriteShape(filepathStr);\n    }\n\n    shape->filepath = filepathStr;\n\n\n    // Require texcoords, to simplify mesh processing later\n    if (!shape->HasUV() && requireUVs)\n    {\n        log::fatal(\"OBJ file is missing texture coords\");\n    }\n\n    if (parseMaterials && !shape->mtllib.empty())\n    {\n        fs::path p = shape->mtllib;\n        if (!fs::is_regular_file(p))\n        {\n            p = shape->filepath.parent_path() / shape->mtllib;\n        }\n\n        if (fs::is_regular_file(p))\n        {\n            log::info(\"Loading mtl file from disk: %s\", p.generic_string().c_str());\n            shape->mtls = parseMtllib(p.generic_string().c_str());\n        }\n        else\n        {\n            log::error(\"Error loading mtl file: %s\", p.generic_string().c_str());\n        }\n\n        resolveUdims(*shape);\n\n        if (!shape->capslib.empty())\n        {\n        \tfs::path p = shape->capslib;\n\n            if (!fs::is_regular_file(p))\n            {\n                p = shape->filepath.parent_path() / shape->capslib;\n            }\n            if (fs::is_regular_file(p))\n            {\n                // TODO: load capsules\n                //log::info(\"Loading caps file from disk: %s\", p.generic_string().c_str());\n                //shape->capsules.Load(p.generic_string());\n            }\n        }\n    }\n\n    return shape;\n}\n"
  },
  {
    "path": "rtxmg/subdivision/subdivision_surface.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n\n#include \"rtxmg/subdivision/subdivision_surface.h\"\n\n#include \"rtxmg/utils/buffer.h\"\n#include \"rtxmg/subdivision/topology_cache.h\"\n#include \"rtxmg/subdivision/topology_map.h\"\n#include \"rtxmg/subdivision/far.h\"\n#include \"rtxmg/subdivision/segmented_vector.h\"\n#include \"rtxmg/profiler/statistics.h\"\n\n#include <opensubdiv/tmr/surfaceTableFactory.h>\n#include <opensubdiv/tmr/subdivisionPlanBuilder.h>\n#include <opensubdiv/tmr/subdivisionPlan.h>\n\n#include <donut/engine/CommonRenderPasses.h>\n#include <donut/core/log.h>\n\n#include <algorithm>\n#include <numeric>\n#include <ranges>\n\n// clang-format on\n\nusing namespace OpenSubdiv;\nusing namespace donut;\n\nusing TexcoordDeviceData = SubdivisionSurface::SurfaceTableDeviceData;\n\nvoid initSubdLinearDeviceData(const Tmr::LinearSurfaceTable& surfaceTable,\n    TexcoordDeviceData& deviceData, nvrhi::ICommandList* commandList)\n{\n    deviceData.surfaceDescriptors =\n        CreateAndUploadBuffer<Tmr::LinearSurfaceDescriptor>(\n            surfaceTable.descriptors, \"texture coordinate surface descriptors\", commandList);\n\n    deviceData.controlPointIndices = CreateAndUploadBuffer<Vtr::Index>(\n        surfaceTable.controlPointIndices, \"texture coordinate control point indices\", commandList);\n\n    // Support (patch) points\n    const uint32_t numSurfaces = surfaceTable.GetNumSurfaces();\n    std::vector<uint32_t> patchPointsOffsets(numSurfaces + 1, 0);\n    for (uint32_t i = 0; i < numSurfaces; i++)\n    {\n        Tmr::LinearSurfaceDescriptor desc = surfaceTable.GetDescriptor(i);\n        if (!desc.HasLimit())\n        {\n            patchPointsOffsets[i + 1] = patchPointsOffsets[i];\n            continue;\n        }\n\n        uint32_t numPatchPoints =\n            (desc.GetQuadSubfaceIndex() == Tmr::LOCAL_INDEX_INVALID)\n            ? 0\n            : desc.GetFaceSize() + 1;\n        patchPointsOffsets[i + 1] = patchPointsOffsets[i] + numPatchPoints;\n    }\n\n    deviceData.patchPointsOffsets = CreateAndUploadBuffer<uint32_t>(\n        patchPointsOffsets, \"texture coordinate patch points offsets\", commandList);\n\n    deviceData.patchPoints = CreateBuffer(patchPointsOffsets.back(), sizeof(float2),\n        \"texture coordinate patch points\", commandList->getDevice());\n}\n\nstatic void gatherStatistics(Shape const& shape,\n    Far::TopologyRefiner const& refiner,\n    Tmr::TopologyMap const& topologyMap,\n    Tmr::SurfaceTable const& surfTable,\n    std::vector<uint16_t> &topologyQuality)\n{\n    int nsurfaces = surfTable.GetNumSurfaces();\n\n    auto& evalStats = stats::evaluatorSamplers;\n\n    evalStats.topologyMapStats = TopologyMap::ComputeStatistics(topologyMap);\n\n    static constexpr int const histogramSize = 50;\n\n    stats::SurfaceTableStats surfStats;\n\n    surfStats.name = shape.filepath.filename().generic_string();\n\n    surfStats.maxValence = refiner.getLevel(0).getMaxValence();\n\n    surfStats.byteSize = surfTable.GetByteSize();\n\n    topologyQuality.resize(nsurfaces, 0);\n    \n    size_t stencilSum = 0;\n\n    for (int surfIndex = 0; surfIndex < nsurfaces; ++surfIndex)\n    {\n        Tmr::SurfaceDescriptor const& desc = surfTable.GetDescriptor(surfIndex);\n\n        if (!desc.HasLimit())\n        {\n            ++surfStats.holesCount;\n            continue;\n        }\n\n        auto const plan =\n            topologyMap.GetSubdivisionPlan(desc.GetSubdivisionPlanIndex());\n\n        uint16_t& quality = topologyQuality[surfIndex];\n\n        // check face m_size (regular / non-quad)\n        if (!plan->IsRegularFace())\n            ++surfStats.irregularFaceCount;\n\n        uint32_t faceSize = plan->GetFaceSize();\n\n        if (faceSize > 5)\n            quality = std::max(quality, (uint16_t)0xff);\n\n        surfStats.maxFaceSize = std::max(faceSize, surfStats.maxFaceSize);\n\n        // check vertex valences\n        const Tmr::Index* controlPoints =\n            surfTable.GetControlPointIndices(surfIndex);\n\n        uint32_t maxVertexValence = 0;\n        for (uint8_t i = 0; i < faceSize; ++i)\n        {\n            auto edges = refiner.GetLevel(0).GetVertexEdges(controlPoints[i]);\n            maxVertexValence = std::max(maxVertexValence, uint32_t(edges.size()));\n        }\n        if (maxVertexValence > 8)\n            quality = std::max(quality, (uint16_t)0xff);\n\n        // check sharpness\n        bool hasSharpness = false;\n\n        if (plan->GetNumNeighborhoods())\n        {\n            Tmr::Neighborhood const& n = plan->GetNeighborhood(0);\n\n            Tmr::ConstFloatArray corners = n.GetCornerSharpness();\n            Tmr::ConstFloatArray creases = n.GetCreaseSharpness();\n\n            if (hasSharpness = !(corners.empty() && creases.empty()))\n            {\n\n                auto processSharpness = [&surfStats,\n                    &quality](Tmr::ConstFloatArray values)\n                    {\n                        for (int i = 0; i < values.size(); ++i)\n                        {\n                            if (values[i] >= 10.f)\n                                ++surfStats.infSharpCreases;\n                            else\n                            {\n                                surfStats.sharpnessMax =\n                                    std::max(surfStats.sharpnessMax, values[i]);\n\n                                if (values[i] > 8.f)\n                                    quality = std::max(quality, (uint16_t)0xff);\n                                else if (values[i] > 4.f)\n                                    quality = std::max(\n                                        quality,\n                                        uint16_t((values[i] / Sdc::Crease::SHARPNESS_INFINITE) *\n                                            255.f));\n                            }\n                        }\n                    };\n                processSharpness(creases);\n                processSharpness(corners);\n            }\n        }\n\n        // check stencil matrix\n        size_t nstencils = plan->GetNumStencils();\n\n        if (nstencils == 0)\n        {\n            if (plan->GetNumControlPoints() == 16)\n                ++surfStats.bsplineSurfaceCount;\n            else\n                ++surfStats.regularSurfaceCount;\n        }\n        else\n        {\n            if (hasSharpness)\n                ++surfStats.sharpSurfaceCount;\n            else\n                ++surfStats.isolationSurfaceCount;\n        }\n\n        stencilSum += nstencils;\n\n        surfStats.stencilCountMin =\n            std::min(surfStats.stencilCountMin, (uint32_t)nstencils);\n        surfStats.stencilCountMax =\n            std::max(surfStats.stencilCountMax, (uint32_t)nstencils);\n    }\n\n    assert((surfStats.holesCount + surfStats.bsplineSurfaceCount +\n        surfStats.regularSurfaceCount + surfStats.isolationSurfaceCount +\n        surfStats.sharpSurfaceCount) == nsurfaces);\n\n    surfStats.stencilCountAvg = float(stencilSum) / float(nsurfaces);\n\n    surfStats.stencilCountHistogram.resize(histogramSize);\n\n    surfStats.surfaceCount = nsurfaces;\n\n    if (!surfStats.IsCatmarkTopology())\n    {\n        // if we suspect this was not a sub-d model (likely a triangular mesh), run\n        // a second pass of the surfaces to tag all the irregular faces (non-quads)\n        // as poor quality\n        int const regularFaceSize =\n            Sdc::SchemeTypeTraits::GetRegularFaceSize(refiner.GetSchemeType());\n\n        const Vtr::internal::Level& level = refiner.getLevel(0);\n        for (int faceIndex = 0, surfaceIndex = 0; faceIndex < level.getNumFaces();\n            ++faceIndex)\n        {\n            if (level.isFaceHole(faceIndex))\n                continue;\n            if (int nverts = level.getFaceVertices(faceIndex).size();\n                nverts == regularFaceSize)\n                ++surfaceIndex;\n            else\n            {\n                for (int vert = 0; vert < nverts; ++vert, ++surfaceIndex)\n                    topologyQuality[surfaceIndex] = 0xff;\n            }\n        }\n    }\n\n    // fill stencil counts histogram\n    if (surfStats.stencilCountMin == surfStats.stencilCountMax)\n    {\n        // all the surfaces have the same number of stencils\n        surfStats.stencilCountHistogram.push_back(nsurfaces);\n    }\n    else\n    {\n        surfStats.stencilCountHistogram.resize(histogramSize);\n\n        float delta = float(surfStats.stencilCountMax - surfStats.stencilCountMin) /\n            histogramSize;\n\n        for (int surfIndex = 0; surfIndex < nsurfaces; ++surfIndex)\n        {\n\n            Tmr::SurfaceDescriptor const& desc = surfTable.GetDescriptor(surfIndex);\n\n            if (!desc.HasLimit())\n                continue;\n\n            auto const plan =\n                topologyMap.GetSubdivisionPlan(desc.GetSubdivisionPlanIndex());\n\n            uint32_t nstencils = (uint32_t)plan->GetNumStencils();\n\n            uint32_t i = (uint32_t)std::floor(\n                float(nstencils - surfStats.stencilCountMin) / delta);\n\n            ++surfStats\n                .stencilCountHistogram[std::min(uint32_t(histogramSize - 1), i)];\n        }\n    }\n\n    surfStats.BuildTopologyRecommendations();\n\n    evalStats.surfaceTablesByteSizeTotal += surfStats.byteSize;\n    evalStats.hasBadTopology |= (!surfStats.topologyRecommendations.empty());\n\n    evalStats.surfaceTableStats.emplace_back(std::move(surfStats));\n}\n\nstatic std::vector<uint16_t> quadrangulateFaceToSubshape(\n    Shape const& shape, uint32_t nsurfaces)\n{\n    assert(shape.scheme == Scheme::kCatmark);\n\n    if (shape.nvertsPerFace.empty() || shape.faceToSubshapeIndex.empty() || !nsurfaces)\n        return {};\n\n    std::vector<uint16_t> result(nsurfaces);\n\n    // Strong assumption here that this matches the quadrangulation to Create the surface descriptors\n    for (uint32_t face = 0, vcount = 0; face < (uint32_t)shape.nvertsPerFace.size(); ++face)\n    {\n        int nverts = shape.nvertsPerFace[face];\n\n        uint32_t subShapeIndex = shape.faceToSubshapeIndex[face];\n\n        if (nverts == 4)\n        {\n            assert(vcount < result.size());\n            result[vcount++] = static_cast<uint16_t>(subShapeIndex);\n        }\n        else\n        {\n            assert(vcount + nverts <= result.size());\n            for (int vert = 0; vert < nverts; ++vert)\n            {\n                result[vcount + vert] = static_cast<uint16_t>(subShapeIndex);\n            }\n            vcount += nverts;\n        }\n    }\n    return result;\n}\n\n// -----------------------------------------------------------------------------\n// SubdivisionSurface\n// -----------------------------------------------------------------------------\nSubdivisionSurface::SubdivisionSurface(TopologyCache& topologyCache,\n    std::unique_ptr<Shape> shape,\n    const std::vector<std::unique_ptr<Shape>> &keyFrameShapes,\n    std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTable,\n    nvrhi::ICommandList* commandList)\n{\n    m_shape = std::move(shape);\n\n    // Create Far mesh (control cage topology)\n\n    Sdc::SchemeType schemeType = GetSdcType(*m_shape);\n    Sdc::Options schemeOptions = GetSdcOptions(*m_shape);\n    Tmr::EndCapType endCaps = Tmr::EndCapType::ENDCAP_BSPLINE_BASIS;\n\n    {\n        // note: for now the topology cache only supports a single map\n        // for a given set of traits ; eventually Tmr::SurfaceTableFactory\n        // may support directly topology caches, allowing a given\n        // Tmr::SurfaceTable to reference multiple topology maps at run-time.\n        Tmr::TopologyMap::Traits traits;\n        traits.SetCompatible(schemeType, schemeOptions, endCaps);\n\n        m_topology_map = &topologyCache.get(traits.value);\n    }\n\n    Tmr::TopologyMap& topologyMap = *m_topology_map->aTopologyMap;\n\n    std::unique_ptr<Far::TopologyRefiner> refiner;\n\n    refiner.reset(Far::TopologyRefinerFactory<Shape>::Create(\n        *m_shape,\n        Far::TopologyRefinerFactory<Shape>::Options(schemeType, schemeOptions)));\n\n    Tmr::SurfaceTableFactory tableFactory;\n\n    Tmr::SurfaceTableFactory::Options options;\n    options.planBuilderOptions.endCapType = endCaps;\n    options.planBuilderOptions.isolationLevel = topologyCache.options.isoLevelSharp;\n    options.planBuilderOptions.isolationLevelSecondary = topologyCache.options.isoLevelSmooth;\n    options.planBuilderOptions.useSingleCreasePatch = true;\n    options.planBuilderOptions.useInfSharpPatch = true;\n    options.planBuilderOptions.useTerminalNode = topologyCache.options.useTerminalNodes;\n    options.planBuilderOptions.useDynamicIsolation = true;\n    options.planBuilderOptions.orderStencilMatrixByLevel = true;\n    options.planBuilderOptions.generateLegacySharpCornerPatches = false;\n\n    m_surface_table =\n        tableFactory.Create(*refiner, topologyMap, options);\n\n    std::vector<uint16_t> topologyQuality;\n    gatherStatistics(*m_shape, *refiner, topologyMap, *m_surface_table, topologyQuality);\n\n    m_topologyQualityBuffer = CreateAndUploadBuffer<uint16_t>(\n        topologyQuality, \"topology quality\", commandList);\n\n    m_topologyQualityDescriptor = descriptorTable->CreateDescriptorHandle(\n        nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_topologyQualityBuffer));\n\n    // setup for texcoords\n    Tmr::LinearSurfaceTableFactory tableFactoryFvar;\n    constexpr int const fvarChannel = 0;\n    m_texcoord_surface_table =\n        tableFactoryFvar.Create(*refiner, fvarChannel, m_surface_table.get());\n\n    InitDeviceData(commandList);\n\n    m_texcoordsBuffer =\n        CreateAndUploadBuffer<float2>(m_shape->uvs, \"base texcoords\", commandList);\n\n    m_positionsBuffer = CreateAndUploadBuffer<float3>(m_shape->verts, \"SubdPosedPositions\", commandList);\n    m_aabb = m_shape->aabb;\n\n    if (keyFrameShapes.size() > 0)\n    {\n        // Includes the 0th frame\n        size_t nframes = keyFrameShapes.size() + 1;\n\n        m_positionsPrevBuffer = CreateAndUploadBuffer<float3>(m_shape->verts, \"SubdPosedPositions\", commandList);\n\n        m_positionKeyframeBuffers.resize(nframes);\n        m_aabbKeyframes.resize(nframes);\n\n        m_positionKeyframeBuffers[0] = CreateAndUploadBuffer<float3>(\n            m_shape->verts, \"SubdKeyFramePosition0\", commandList);\n        m_aabbKeyframes[0] = m_shape->aabb;\n\n        // starts 1 indexed\n        uint32_t frameIndex = 1;\n        for (auto& keyFrameShape : keyFrameShapes)\n        {\n            char debugName[50];\n            std::snprintf(debugName, std::size(debugName), \"SubdKeyFramePosition%d\",\n                frameIndex);\n            m_positionKeyframeBuffers[frameIndex] =\n                CreateAndUploadBuffer<float3>(keyFrameShape->verts, debugName, commandList);\n            m_aabbKeyframes[frameIndex] = keyFrameShape->aabb;\n\n            frameIndex++;\n        }\n    }\n\n    m_vertexSurfaceDescriptorDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_vertexDeviceData.surfaceDescriptors));\n    m_vertexControlPointIndicesDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_vertexDeviceData.controlPointIndices));\n    \n    m_positionsDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_positionsBuffer));\n    if (m_positionsPrevBuffer)\n    {\n        m_positionsPrevDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_positionsPrevBuffer));\n    }\n\n    m_surfaceToGeometryIndexDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, m_surfaceToGeometryIndexBuffer));\n}\n\nuint32_t SubdivisionSurface::NumVertices() const\n{\n    return static_cast<uint32_t>(m_positionsBuffer->getDesc().byteSize / sizeof(float3));\n}\n\nuint32_t SubdivisionSurface::SurfaceCount() const\n{\n    return m_surfaceCount;\n}\n\nvoid SubdivisionSurface::InitDeviceData(nvrhi::ICommandList* commandList)\n{\n    m_surfaceCount = uint32_t(m_surface_table->descriptors.size());\n\n    // Sort surfaces by PureBspline, Bspline, Complex types for shader optimization\n    std::vector<Tmr::SurfaceDescriptor> sortedDescriptors = m_surface_table->descriptors;\n    std::vector<Tmr::LinearSurfaceDescriptor> sortedTexcoordDescriptors = m_texcoord_surface_table->descriptors;\n    auto surfaceToGeometryIndex = quadrangulateFaceToSubshape(*m_shape, m_surfaceCount);\n\n    assert(m_surfaceCount == sortedDescriptors.size());\n    assert(m_surfaceCount == sortedTexcoordDescriptors.size());\n    assert(m_surfaceCount == surfaceToGeometryIndex.size());\n\n    auto zippedDescriptorsGeometryIndex = std::ranges::views::zip(sortedDescriptors, sortedTexcoordDescriptors, surfaceToGeometryIndex);\n\n    std::ranges::sort(zippedDescriptorsGeometryIndex.begin(), zippedDescriptorsGeometryIndex.end(), [this](const auto& lhs, const auto& rhs)\n        {\n            // Extract surface descriptors \n            const auto& a = std::get<0>(lhs);\n            const auto& b = std::get<0>(rhs);\n            bool tieBreakerAB = a.firstControlPoint < b.firstControlPoint;\n\n            // All holes last\n            bool aHasLimit = a.HasLimit();\n            bool bHasLimit = b.HasLimit();\n            if (aHasLimit != bHasLimit)\n                return aHasLimit;\n            else if (!aHasLimit && !bHasLimit)\n                return tieBreakerAB;\n            \n            // PureBspline\n            bool aIsPureBSplinePatch = a.GetSubdivisionPlanIndex() == 0;\n            bool bIsPureBSplinePatch = b.GetSubdivisionPlanIndex() == 0;\n            if (aIsPureBSplinePatch != bIsPureBSplinePatch)\n                return aIsPureBSplinePatch;\n            else if (aIsPureBSplinePatch && bIsPureBSplinePatch)\n                return tieBreakerAB;\n\n            // BSpline\n            const auto* aPlan = m_surface_table->topologyMap.GetSubdivisionPlan(a.GetSubdivisionPlanIndex());\n            bool aIsBSplinePatch = aPlan->GetTreeDescriptor().GetNumPatchPoints(Tmr::kMaxIsolationLevel) == 0;\n\n            const auto* bPlan = m_surface_table->topologyMap.GetSubdivisionPlan(b.GetSubdivisionPlanIndex());\n            bool bIsBSplinePatch = bPlan->GetTreeDescriptor().GetNumPatchPoints(Tmr::kMaxIsolationLevel) == 0;\n\n            if (aIsBSplinePatch != bIsBSplinePatch)\n                return aIsBSplinePatch;\n            \n            // Complex, Limit Surface\n            return tieBreakerAB;\n        });\n\n    // Array is sorted lets find the starting index for each stype\n    int lastSurfaceType = -1;\n    auto UpdateSurfaceOffset = [&lastSurfaceType, this](SurfaceType surfaceType, uint32_t startIndex)\n        {\n            for (int i = lastSurfaceType + 1; i <= int(surfaceType); i++)\n            {\n                m_surfaceOffsets[i] = startIndex;\n            }\n            lastSurfaceType = int(surfaceType);\n        };\n\n    for (uint32_t i = 0; i < m_surfaceCount; i++)\n    {\n        const auto& descriptor = sortedDescriptors[i];\n        bool isPureBSplinePatch = descriptor.GetSubdivisionPlanIndex() == 0;\n        if (isPureBSplinePatch)\n        {\n            UpdateSurfaceOffset(SurfaceType::PureBSpline, i);\n        }\n        else\n        {\n            const auto* plan = m_surface_table->topologyMap.GetSubdivisionPlan(descriptor.GetSubdivisionPlanIndex());\n            bool isBSplinePatch = plan->GetTreeDescriptor().GetNumPatchPoints(Tmr::kMaxIsolationLevel) == 0;\n            if (isBSplinePatch)\n            {\n                UpdateSurfaceOffset(SurfaceType::RegularBSpline, i);\n            }\n            else\n            {\n                if (descriptor.HasLimit())\n                {\n                    UpdateSurfaceOffset(SurfaceType::Limit, i);\n                }\n                else\n                {\n                    UpdateSurfaceOffset(SurfaceType::NoLimit, i);\n                }\n            }\n        }\n    }\n    UpdateSurfaceOffset(SurfaceType::NoLimit, m_surfaceCount);\n\n    std::vector<uint32_t> patchPointsOffsets(m_surfaceCount + 1, 0);\n    for (uint32_t iSurface = 0; iSurface < m_surfaceCount; ++iSurface)\n    {\n        const Tmr::SurfaceDescriptor surface = sortedDescriptors[iSurface];\n        if (!surface.HasLimit())\n        {\n            patchPointsOffsets[iSurface + 1] = patchPointsOffsets[iSurface];\n            continue;\n        }\n\n        // plan is never going to be null here\n        const auto* plan = m_surface_table->topologyMap.GetSubdivisionPlan(\n            surface.GetSubdivisionPlanIndex());\n\n        patchPointsOffsets[iSurface + 1] =\n            patchPointsOffsets[iSurface] + static_cast<uint32_t>(plan->GetNumPatchPoints());\n    }\n\n    // Texcoord Patch Points\n    std::vector<uint32_t> texcoordPatchPointsOffsets(m_surfaceCount + 1, 0);\n    for (uint32_t i = 0; i < m_surfaceCount; i++)\n    {\n        Tmr::LinearSurfaceDescriptor desc = sortedTexcoordDescriptors[i];\n        if (!desc.HasLimit())\n        {\n            texcoordPatchPointsOffsets[i + 1] = texcoordPatchPointsOffsets[i];\n            continue;\n        }\n\n        uint32_t numPatchPoints =\n            (desc.GetQuadSubfaceIndex() == Tmr::LOCAL_INDEX_INVALID)\n            ? 0\n            : desc.GetFaceSize() + 1;\n        texcoordPatchPointsOffsets[i + 1] = texcoordPatchPointsOffsets[i] + numPatchPoints;\n    }\n\n    m_vertexDeviceData.surfaceDescriptors =\n        CreateAndUploadBuffer<Tmr::SurfaceDescriptor>(\n            sortedDescriptors, \"surface descriptors\", commandList);\n\n    m_vertexDeviceData.controlPointIndices = CreateAndUploadBuffer<Vtr::Index>(\n        m_surface_table->controlPointIndices, \"control point indices\", commandList);\n\n    m_vertexDeviceData.patchPoints = CreateBuffer(patchPointsOffsets.back(), sizeof(float3), \"patch points\", commandList->getDevice());\n\n    m_vertexDeviceData.patchPointsOffsets = CreateAndUploadBuffer<uint32_t>(\n        patchPointsOffsets, \"patch points offsets\", commandList);\n\n    m_surfaceToGeometryIndexBuffer = CreateAndUploadBuffer<uint16_t>(surfaceToGeometryIndex, \"surfaceToGeometryIndex\", commandList);\n\n    m_texcoordDeviceData.surfaceDescriptors =\n        CreateAndUploadBuffer<Tmr::LinearSurfaceDescriptor>(\n            sortedTexcoordDescriptors, \"texture coordinate surface descriptors\", commandList);\n\n    m_texcoordDeviceData.controlPointIndices = CreateAndUploadBuffer<Vtr::Index>(\n        m_texcoord_surface_table->controlPointIndices, \"texture coordinate control point indices\", commandList);\n\n    m_texcoordDeviceData.patchPointsOffsets = CreateAndUploadBuffer<uint32_t>(\n        texcoordPatchPointsOffsets, \"texture coordinate patch points offsets\", commandList);\n\n    m_texcoordDeviceData.patchPoints = CreateBuffer(texcoordPatchPointsOffsets.back(), sizeof(float2),\n        \"texture coordinate patch points\", commandList->getDevice());\n}\n\nbool SubdivisionSurface::HasAnimation() const\n{\n    return !m_positionKeyframeBuffers.empty();\n}\n\nuint32_t SubdivisionSurface::NumKeyframes() const\n{\n    return (uint32_t)m_positionKeyframeBuffers.size();\n}\n\nstatic inline box3 lerpAabb(const box3& a, const box3& b, float t)\n{\n    box3 result;\n    result.m_mins = lerp(a.m_mins, b.m_mins, t);\n    result.m_maxs = lerp(a.m_maxs, b.m_maxs, t);\n    return result;\n}\n\nvoid SubdivisionSurface::Animate(float animTime, float frameRate)\n{\n    if (!HasAnimation())\n        return;\n\n    uint32_t nframes = static_cast<uint32_t>(m_positionKeyframeBuffers.size());\n\n    float frameTime = m_frameOffset + animTime * frameRate;\n    float frame = std::truncf(frameTime);\n\n    // animation implicitly loops if frameTime >= NumKeyframes\n    m_f0 = static_cast<int>(frame) % nframes;\n    m_f1 = (m_f0 + 1) % nframes;\n\n    m_dt = frameTime - frame;\n\n    m_aabb = lerpAabb(m_aabbKeyframes[m_f0], m_aabbKeyframes[m_f1], animTime);\n}\n"
  },
  {
    "path": "rtxmg/subdivision/topology_cache.cpp",
    "content": "//\n// Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n// clang-format off\n\n#include \"rtxmg/subdivision/topology_cache.h\"\n#include \"rtxmg/subdivision/topology_map.h\"\n\n#include <opensubdiv/tmr/topologyMap.h>\n\n// clang-format on\n\nusing namespace OpenSubdiv;\n\nunion Key\n{\n    Tmr::TopologyMap::Traits traits;\n    uint8_t value;\n};\n\nTopologyCache::TopologyCache(TopologyCache::Options const& opts)\n    : options(opts)\n{\n}\n\nTopologyMap& TopologyCache::get(uint8_t traits)\n{\n    std::lock_guard lock(m_mtx);\n\n    Key key{\n        .value = traits,\n    };\n\n    if (auto it = m_topologyMaps.find(key.value); it != m_topologyMaps.end())\n        return *it->second;\n\n    auto aTopologyMap = std::make_unique<Tmr::TopologyMap>(\n        key.traits, Tmr::TopologyMap::Options(uint8_t(m_topologyMaps.size())));\n\n    auto [it, done] = m_topologyMaps.emplace(\n        key.value, std::make_unique<TopologyMap>(std::move(aTopologyMap)));\n\n    return *it->second;\n}\n\nbool TopologyCache::Empty() const\n{\n    std::lock_guard lock(m_mtx);\n    return m_topologyMaps.empty();\n}\n\nsize_t TopologyCache::Size() const\n{\n    std::lock_guard lock(m_mtx);\n    return m_topologyMaps.size();\n}\n\nvoid TopologyCache::Clear()\n{\n    std::lock_guard lock(m_mtx);\n    return m_topologyMaps.clear();\n}\n\n// note: all hashing must be completed before hoisting maps into device memory !\nstd::vector<std::unique_ptr<TopologyMap const>>\nTopologyCache::InitDeviceData(std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTable,\n    nvrhi::ICommandList* commandList, bool keepHostData)\n{\n    std::lock_guard lock(m_mtx);\n\n    std::vector<std::unique_ptr<TopologyMap const>> topologyMaps;\n\n    topologyMaps.reserve(m_topologyMaps.size());\n\n    for (auto& it : m_topologyMaps)\n    {\n        it.second->InitDeviceData(descriptorTable, commandList, keepHostData);\n\n        topologyMaps.emplace_back(std::move(it.second));\n    }\n    return topologyMaps;\n}\n"
  },
  {
    "path": "rtxmg/subdivision/topology_map.cpp",
    "content": "//\n// Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n// clang-format off\n\n#include \"rtxmg/subdivision/topology_map.h\"\n\n#include \"rtxmg/profiler/statistics.h\"\n#include \"rtxmg/subdivision/segmented_vector.h\"\n#include \"rtxmg/subdivision/subdivision_plan_hlsl.h\"\n#include \"rtxmg/utils/buffer.h\"\n\n#include <opensubdiv/tmr/neighborhood.h>\n#include <opensubdiv/tmr/topologyMap.h>\n\n#include <fstream>\n\n\nusing namespace OpenSubdiv;\n\n// clang-format on\n\nTopologyMap::TopologyMap(\n    std::unique_ptr<OpenSubdiv::OPENSUBDIV_VERSION::Tmr::TopologyMap>\n    atopologyMap)\n    : aTopologyMap(std::move(atopologyMap))\n{\n}\n\nvoid TopologyMap::InitDeviceData(std::shared_ptr<donut::engine::DescriptorTableManager> descriptorTable,\n    nvrhi::ICommandList* commandList, bool keepHostData)\n{\n    assert(aTopologyMap);\n\n    Sdc::SchemeType schemeType = aTopologyMap->GetTraits().getSchemeType();\n    Tmr::EndCapType endcapType = aTopologyMap->GetTraits().getEndCapType();\n\n    const int numPlans = aTopologyMap->GetNumSubdivisionPlans();\n\n    std::vector<SubdivisionPlanHLSL> gpuPlans(numPlans);\n\n    segmented_vector<uint32_t> gpuTrees;\n    gpuTrees.Reserve(numPlans);\n\n    segmented_vector<int> gpuPatchPointIndexArrays;\n    gpuPatchPointIndexArrays.Reserve(numPlans);\n\n    segmented_vector<float> gpuStencilMatrixArrays;\n    gpuStencilMatrixArrays.Reserve(numPlans);\n\n    for (auto planIndex = 0; planIndex < numPlans; ++planIndex)\n    {\n        SubdivisionPlanHLSL& gpuPlan = gpuPlans[planIndex];\n        // Data is set in the plan, but shader code references hardcodes these\n        gpuPlan.scheme = (SchemeType)schemeType;\n        gpuPlan.endCap = (EndCapType)endcapType;\n\n        if (const auto* plan = aTopologyMap->GetSubdivisionPlan(planIndex))\n        {\n            const auto& tree_desc = plan->GetTreeDescriptor();\n            gpuPlan.numControlPoints = tree_desc.GetNumControlPoints();\n            gpuPlan.coarseFaceQuadrant = tree_desc.GetSubfaceIndex();\n            gpuPlan.coarseFaceSize = tree_desc.GetFaceSize();\n\n            gpuPatchPointIndexArrays.Append(plan->GetPatchPoints());\n            gpuTrees.Append(plan->GetPatchTreeData());\n            gpuStencilMatrixArrays.Append(plan->GetStencilMatrix());\n        }\n        else\n        {\n            gpuPlan.numControlPoints = 0;\n            gpuPlan.coarseFaceSize = 4;\n            gpuPlan.coarseFaceQuadrant = -1;\n            static std::vector<int> empty{};\n            gpuPatchPointIndexArrays.Append(empty);\n            gpuTrees.Append(empty);\n            gpuStencilMatrixArrays.Append(\n                reinterpret_cast<std::vector<float> &>(empty));\n        }\n    }\n\n    subpatchTreesArraysBuffer = CreateAndUploadBuffer<uint32_t>(gpuTrees.elements, \"subpatch trees arrays\", commandList);\n    patchPointIndicesArraysBuffer = CreateAndUploadBuffer<int>(gpuPatchPointIndexArrays.elements, \"patch point indices arrays\", commandList);\n    stencilMatrixArraysBuffer = CreateAndUploadBuffer<float>(gpuStencilMatrixArrays.elements, \"stencil matrix arrays\", commandList);\n\n    for (auto i_plan = 0; i_plan < numPlans; ++i_plan)\n    {\n        gpuPlans[i_plan].treeOffset = gpuTrees.offsets[i_plan];\n        gpuPlans[i_plan].treeSize = gpuTrees.sizes[i_plan];\n\n        gpuPlans[i_plan].patchPointsOffset = gpuPatchPointIndexArrays.offsets[i_plan];\n        gpuPlans[i_plan].patchPointsSize = gpuPatchPointIndexArrays.sizes[i_plan];\n\n        gpuPlans[i_plan].stencilMatrixOffset = gpuStencilMatrixArrays.offsets[i_plan];\n        gpuPlans[i_plan].stencilMatrixSize = gpuStencilMatrixArrays.sizes[i_plan];\n    }\n\n    plansBuffer = CreateAndUploadBuffer<SubdivisionPlanHLSL>(gpuPlans, \"plans\", commandList);\n\n    subpatchTreesDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, subpatchTreesArraysBuffer));\n    patchPointIndicesDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, patchPointIndicesArraysBuffer));\n    stencilMatrixDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, stencilMatrixArraysBuffer));\n    plansDescriptor = descriptorTable->CreateDescriptorHandle(nvrhi::BindingSetItem::StructuredBuffer_SRV(0, plansBuffer));\n\n    if (!keepHostData)\n        aTopologyMap.reset();\n}\n\nstats::TopologyMapStats\nTopologyMap::ComputeStatistics(const Tmr::TopologyMap& topologyMap,\n    int histogramSize)\n{\n    using namespace OpenSubdiv;\n\n    stats::TopologyMapStats stats;\n\n    {\n        auto hashStats = topologyMap.ComputeHashTableStatistics();\n\n        stats.pslMean = hashStats.pslMean;\n        stats.hashCount = hashStats.hashCount;\n        stats.addressCount = hashStats.addressCount;\n        stats.loadFactor = hashStats.loadFactor;\n    }\n\n    uint32_t nplans = static_cast<uint32_t>(topologyMap.GetNumSubdivisionPlans());\n\n    if (nplans == 0)\n        return stats;\n\n    size_t stencilSum = 0;\n\n    for (uint32_t planIndex = 0; planIndex < nplans; ++planIndex)\n    {\n        Tmr::SubdivisionPlan const* plan =\n            topologyMap.GetSubdivisionPlan(planIndex);\n\n        if (planIndex == 0 && !plan)\n        {\n            assert(Tmr::TopologyMap::kRegularPlanAtIndexZero);\n            continue;\n        }\n\n        if (plan->IsRegularFace())\n            ++stats.regularFacePlansCount;\n\n        stats.maxFaceSize =\n            std::max((uint32_t)plan->GetFaceSize(), stats.maxFaceSize);\n\n        if (plan->GetNumNeighborhoods())\n        {\n\n            Tmr::Neighborhood const& n = plan->GetNeighborhood(0);\n\n            Tmr::ConstFloatArray corners = n.GetCornerSharpness();\n            Tmr::ConstFloatArray creases = n.GetCreaseSharpness();\n\n            if (bool hasSharpness = !(corners.empty() && creases.empty()))\n            {\n                ++stats.sharpnessCount;\n                for (int i = 0; i < corners.size(); ++i)\n                    stats.sharpnessMax = std::max(stats.sharpnessMax, corners[i]);\n                for (int i = 0; i < creases.size(); ++i)\n                    stats.sharpnessMax = std::max(stats.sharpnessMax, creases[i]);\n            }\n        }\n\n        size_t nstencils = plan->GetNumStencils();\n\n        stencilSum += nstencils;\n\n        stats.stencilCountMin =\n            std::min(stats.stencilCountMin, (uint32_t)nstencils);\n        stats.stencilCountMax =\n            std::max(stats.stencilCountMax, (uint32_t)nstencils);\n\n        stats.plansByteSize += plan->GetByteSize(true);\n    }\n\n    stats.plansCount = nplans - (topologyMap.GetSubdivisionPlan(0) == nullptr);\n\n    stats.stencilCountAvg = float(stencilSum) / float(stats.plansCount);\n\n    // fill stencil counts histogram\n    if (stats.stencilCountMin == stats.stencilCountMax)\n    {\n        // all the plans have the same number of stencils (ex. single cube)\n        stats.stencilCountHistogram.push_back(stats.plansCount);\n    }\n    else\n    {\n        stats.stencilCountHistogram.resize(histogramSize);\n\n        float delta =\n            float(stats.stencilCountMax - stats.stencilCountMin) / histogramSize;\n\n        for (uint32_t planIndex = 0; planIndex < nplans; ++planIndex)\n        {\n            Tmr::SubdivisionPlan const* plan =\n                topologyMap.GetSubdivisionPlan(planIndex);\n\n            if (planIndex == 0 && !plan)\n                continue;\n\n            size_t nstencils = plan->GetNumStencils();\n\n            uint32_t i = (uint32_t)std::floor(\n                float(nstencils - stats.stencilCountMin) / delta);\n\n            ++stats.stencilCountHistogram[std::min(uint32_t(histogramSize - 1), i)];\n        }\n    }\n    return stats;\n}\n"
  },
  {
    "path": "rtxmg/utils/CMakeLists.txt",
    "content": "set(lib rtxmg_utils)\n\nfile(GLOB sources \"*.cpp\" \"../include/rtxmg/utils/*.h\")\n\nadd_library(${lib} OBJECT ${sources})\ntarget_include_directories(${lib} PUBLIC \n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include\"\n)\ntarget_link_libraries(${lib} donut_engine)\nset_target_properties(${lib} PROPERTIES FOLDER RTXMG)\n"
  },
  {
    "path": "rtxmg/utils/buffer.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"rtxmg/utils/buffer.h\"\n\nnvrhi::BufferDesc GetGenericDesc(size_t nElements, uint32_t elementSize, const char* name, nvrhi::Format format)\n{\n    nElements = std::max(1ull, nElements);\n    return nvrhi::BufferDesc()\n        .setByteSize(nElements * elementSize)\n        .setCanHaveTypedViews(true)\n        .setCanHaveUAVs(true)\n        .setDebugName(name)\n        .setFormat(format)\n        .setInitialState(nvrhi::ResourceStates::UnorderedAccess)\n        .setKeepInitialState(true)\n        .setStructStride(elementSize)\n        .setCanHaveRawViews(true);\n}\n\nnvrhi::BufferDesc GetReadbackDesc(const nvrhi::BufferDesc& desc)\n{\n    nvrhi::BufferDesc readbackBufferDesc = nvrhi::BufferDesc()\n        .setByteSize(desc.byteSize)\n        .setCpuAccess(nvrhi::CpuAccessMode::Read)\n        .setDebugName(desc.debugName + \" Readback\")\n        .setFormat(desc.format)\n        .setInitialState(nvrhi::ResourceStates::CopyDest)\n        .setKeepInitialState(true);\n\n    return readbackBufferDesc;\n}\n\nvoid DownloadBuffer(nvrhi::IBuffer* src, void* dest, nvrhi::IBuffer* staging, bool async, nvrhi::ICommandList* commandList)\n{\n    size_t numBytes = src->getDesc().byteSize;\n    commandList->copyBuffer(staging, 0, src, 0, numBytes);\n\n    if (!async)\n    {\n        commandList->close();\n        commandList->getDevice()->executeCommandList(commandList);\n        commandList->getDevice()->waitForIdle();\n    }\n    void* mappedBuffer = commandList->getDevice()->mapBuffer(staging, nvrhi::CpuAccessMode::Read);\n    if (mappedBuffer)\n        memcpy(dest, mappedBuffer, numBytes);\n    else\n        memset(dest, 0, numBytes);\n    commandList->getDevice()->unmapBuffer(staging);\n\n    if (!async)\n    {\n        commandList->open();\n    }\n}"
  },
  {
    "path": "rtxmg/utils/csvdump.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <iomanip>\n#include <fstream>\n\n#include \"rtxmg/utils/debug.h\"\n\nvoid WriteTexToCSV(nvrhi::ICommandList* commandList, nvrhi::ITexture* tex, char const filename[])\n{\n    nvrhi::TextureDesc desc = tex->getDesc();\n    nvrhi::StagingTextureHandle staging = commandList->getDevice()->createStagingTexture(desc, nvrhi::CpuAccessMode::Read);\n\n    commandList->copyTexture(staging, nvrhi::TextureSlice(), tex, nvrhi::TextureSlice());\n    commandList->close();\n    commandList->getDevice()->executeCommandList(commandList);\n\n    size_t rowPitch = 0;\n    float const* pData = static_cast<float const*>(commandList->getDevice()->mapStagingTexture(\n        staging, nvrhi::TextureSlice(), nvrhi::CpuAccessMode::Read, &rowPitch));\n\n    std::ofstream debugDump(filename);\n\n    for (uint32_t y = 0; y < desc.height; y++)\n    {\n        for (uint32_t x = 0; x < desc.width; x++)\n        {\n            float z = pData[y * rowPitch / sizeof(float) + x];\n            if (isinf(z))\n                z = -1.0f;\n            debugDump << std::setw(8) << std::right << z;\n            if (x < desc.width - 1)\n                debugDump << \", \";\n        }\n        debugDump << std::endl;\n    }\n\n    commandList->open();\n}\n\nvoid WriteBufferToCSV(nvrhi::ICommandList* commandList, RTXMGBuffer<float>& buf, char const filename[], int width, int height)\n{\n    auto values = buf.Download(commandList);\n\n    std::ofstream debugDump(filename);\n\n    for (uint32_t i = 0; i < values.size(); i++)\n    {\n        debugDump << std::setw(8) << std::right << values[i];\n        if (i < values.size() - 1)\n        {\n            if (i % width == width - 1)\n                debugDump << std::endl;\n            else\n                debugDump << \", \";\n        }\n    }\n}\n"
  },
  {
    "path": "rtxmg/utils/debug.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n\n#include <cstring>\n#include <fstream>\n#include <sstream>\n#include <filesystem>\n\n#include <iostream>\n\nint GetUniqueFileIndex(const char* baseName, const char* extension)\n{\n    // Avoid overwriting an existing screenshot : scan the default output directory\n    // for existing files with pattern 'screenshot_xxxx.bmp' to find the highest index.\n    int index = -1;\n    namespace fs = std::filesystem;\n    for (auto it : fs::directory_iterator(fs::current_path()))\n    {\n        if (it.path().extension() != extension)\n            continue;\n        std::string filename = it.path().filename().generic_string();\n        if (std::strstr(filename.c_str(), baseName) != filename.c_str())\n            continue;\n        int existingIndex = std::atoi(filename.c_str() + strlen(baseName));\n        index = std::max(index, existingIndex);\n    }\n    return index + 1;\n}\n"
  },
  {
    "path": "rtxmg/utils/formatters.cpp",
    "content": "/*\n * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"rtxmg/utils/formatters.h\"\n\n#include <assert.h>\n#include <cstdio>\n#include <cmath>\n#include <xutility>\n\nint HumanFormatter(double value, char* buff, int bufsize, void*)\n{\n    static char const* scale[] = { \"\", \"K\", \"M\", \"B\", \"T\", \"Q\", };\n\n    int ndigits = static_cast<int>(value == 0 ? 0 : 1 + std::floor(log10l(std::abs(value))));\n\n    int exp = ndigits <= 4 ? 0 : 3 * ((ndigits - 1) / 3);\n\n    if ((exp / 3) >= std::size(scale))\n        return false;\n\n    double n = static_cast<double>(value / powl(10, exp));\n\n    bool decimals = value - n == 0. ? false : true;\n\n    return std::snprintf(buff, bufsize, decimals ? \"%.1f%s\" : \"%.0f%s\", n, scale[exp / 3]);\n}\n\nint MetricFormatter(double value, char* buff, int bufsize, void* data)\n{\n    const char* unit = (const char*)data;\n    static double v[] = { 1000000000,1000000,1000,1,0.001,0.000001,0.000000001 };\n    static const char* p[] = { \"G\",\"M\",\"k\",\"\",\"m\",\"u\",\"n\" };\n    if (value == 0)\n    {\n        return snprintf(buff, bufsize, \"0 %s\", unit);\n    }\n    for (int i = 0; i < 7; ++i)\n    {\n        if (fabs(value) >= v[i])\n        {\n            return snprintf(buff, bufsize, \"%g %s%s\", value / v[i], p[i], unit);\n        }\n    }\n    return snprintf(buff, bufsize, \"%g %s%s\", value / v[6], p[6], unit);\n}\n\nint MegabytesFormatter(double value, char* buff, int bufsize, void*)\n{\n    double mbsize = value / (1024 * 1024);\n    return snprintf(buff, bufsize, \"%.1f MB\", mbsize);\n}\n\nint MemoryFormatter(double value, char* buff, int bufsize, void*)\n{\n    static char const* suffixes[] = { \"B\", \"KB\",  \"MB\",  \"GB\", \"TB\" };\n\n    uint8_t s = 0;\n    for (; value >= 1024; ++s)\n        value /= 1024;\n\n    assert(s < std::size(suffixes));\n\n    if (value - std::floor(value) == 0.)\n        snprintf(buff, bufsize, \"%d %s\", (int)value, suffixes[s]);\n    else\n        snprintf(buff, bufsize, \"%.1f %s\", value, suffixes[s]);\n    return 0;\n}\n"
  },
  {
    "path": "rtxmg_static_analysis.props",
    "content": "<Project>\n    <PropertyGroup>\n        <RunCodeAnalysis>true</RunCodeAnalysis>\n        <CodeAnalysisRuleset>dxcb_static_analysis.ruleset</CodeAnalysisRuleset>\n    </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "rtxmg_static_analysis.ruleset",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RuleSet Name=\"DXClusterBench static analysis \" Description=\"These rules focus on the most critical and common problems in your native code, including potential security holes and application crashes.  You should include this rule set in any custom rule set you create for your native projects.  This ruleset is designed to work with Visual Studio Professional edition and higher.\" ToolsVersion=\"17.0\">\n  <Include Path=\"nativeminimumrules.ruleset\" Action=\"Default\" />\n  <Rules AnalyzerId=\"Microsoft.Analyzers.NativeCodeAnalysis\" RuleNamespace=\"Microsoft.Rules.Native\">\n    <Rule Id=\"C26100\" Action=\"Warning\" />\n    <Rule Id=\"C26101\" Action=\"Warning\" />\n    <Rule Id=\"C26110\" Action=\"Warning\" />\n    <Rule Id=\"C26111\" Action=\"Warning\" />\n    <Rule Id=\"C26112\" Action=\"Warning\" />\n    <Rule Id=\"C26115\" Action=\"Warning\" />\n    <Rule Id=\"C26116\" Action=\"Warning\" />\n    <Rule Id=\"C26117\" Action=\"Warning\" />\n    <Rule Id=\"C26140\" Action=\"Warning\" />\n    <Rule Id=\"C26437\" Action=\"Warning\" />\n    <Rule Id=\"C26439\" Action=\"Warning\" />\n    <Rule Id=\"C26478\" Action=\"Warning\" />\n    <Rule Id=\"C26479\" Action=\"Warning\" />\n    <Rule Id=\"C26495\" Action=\"None\" />\n    <Rule Id=\"C26498\" Action=\"Warning\" />\n    <Rule Id=\"C26800\" Action=\"Warning\" />\n    <Rule Id=\"C26813\" Action=\"Warning\" />\n    <Rule Id=\"C26817\" Action=\"Warning\" />\n    <Rule Id=\"C26820\" Action=\"Warning\" />\n    <Rule Id=\"C26827\" Action=\"Warning\" />\n    <Rule Id=\"C26828\" Action=\"Warning\" />\n    <Rule Id=\"C26829\" Action=\"Warning\" />\n    <Rule Id=\"C26859\" Action=\"Warning\" />\n    <Rule Id=\"C28020\" Action=\"Warning\" />\n    <Rule Id=\"C28022\" Action=\"Warning\" />\n    <Rule Id=\"C28023\" Action=\"Warning\" />\n    <Rule Id=\"C28024\" Action=\"Warning\" />\n    <Rule Id=\"C28039\" Action=\"Warning\" />\n    <Rule Id=\"C28112\" Action=\"Warning\" />\n    <Rule Id=\"C28113\" Action=\"Warning\" />\n    <Rule Id=\"C28125\" Action=\"Warning\" />\n    <Rule Id=\"C28137\" Action=\"Warning\" />\n    <Rule Id=\"C28138\" Action=\"Warning\" />\n    <Rule Id=\"C28159\" Action=\"Warning\" />\n    <Rule Id=\"C28160\" Action=\"Warning\" />\n    <Rule Id=\"C28163\" Action=\"Warning\" />\n    <Rule Id=\"C28164\" Action=\"Warning\" />\n    <Rule Id=\"C28183\" Action=\"Warning\" />\n    <Rule Id=\"C28193\" Action=\"Warning\" />\n    <Rule Id=\"C28196\" Action=\"Warning\" />\n    <Rule Id=\"C28209\" Action=\"Warning\" />\n    <Rule Id=\"C28244\" Action=\"Warning\" />\n    <Rule Id=\"C28306\" Action=\"Warning\" />\n    <Rule Id=\"C28307\" Action=\"Warning\" />\n    <Rule Id=\"C33004\" Action=\"Warning\" />\n    <Rule Id=\"C33005\" Action=\"Warning\" />\n    <Rule Id=\"C33011\" Action=\"Warning\" />\n    <Rule Id=\"C6031\" Action=\"Warning\" />\n    <Rule Id=\"C6054\" Action=\"Warning\" />\n    <Rule Id=\"C6214\" Action=\"Warning\" />\n    <Rule Id=\"C6215\" Action=\"Warning\" />\n    <Rule Id=\"C6216\" Action=\"Warning\" />\n    <Rule Id=\"C6217\" Action=\"Warning\" />\n    <Rule Id=\"C6220\" Action=\"Warning\" />\n    <Rule Id=\"C6226\" Action=\"Warning\" />\n    <Rule Id=\"C6230\" Action=\"Warning\" />\n    <Rule Id=\"C6235\" Action=\"Warning\" />\n    <Rule Id=\"C6236\" Action=\"Warning\" />\n    <Rule Id=\"C6237\" Action=\"Warning\" />\n    <Rule Id=\"C6242\" Action=\"Warning\" />\n    <Rule Id=\"C6248\" Action=\"Warning\" />\n    <Rule Id=\"C6250\" Action=\"Warning\" />\n    <Rule Id=\"C6255\" Action=\"Warning\" />\n    <Rule Id=\"C6258\" Action=\"Warning\" />\n    <Rule Id=\"C6259\" Action=\"Warning\" />\n    <Rule Id=\"C6260\" Action=\"Warning\" />\n    <Rule Id=\"C6262\" Action=\"Warning\" />\n    <Rule Id=\"C6263\" Action=\"Warning\" />\n    <Rule Id=\"C6268\" Action=\"Warning\" />\n    <Rule Id=\"C6269\" Action=\"Warning\" />\n    <Rule Id=\"C6278\" Action=\"Warning\" />\n    <Rule Id=\"C6279\" Action=\"Warning\" />\n    <Rule Id=\"C6280\" Action=\"Warning\" />\n    <Rule Id=\"C6281\" Action=\"Warning\" />\n    <Rule Id=\"C6282\" Action=\"Warning\" />\n    <Rule Id=\"C6283\" Action=\"Warning\" />\n    <Rule Id=\"C6285\" Action=\"Warning\" />\n    <Rule Id=\"C6286\" Action=\"Warning\" />\n    <Rule Id=\"C6287\" Action=\"Warning\" />\n    <Rule Id=\"C6288\" Action=\"Warning\" />\n    <Rule Id=\"C6289\" Action=\"Warning\" />\n    <Rule Id=\"C6292\" Action=\"Warning\" />\n    <Rule Id=\"C6293\" Action=\"Warning\" />\n    <Rule Id=\"C6294\" Action=\"Warning\" />\n    <Rule Id=\"C6295\" Action=\"Warning\" />\n    <Rule Id=\"C6296\" Action=\"Warning\" />\n    <Rule Id=\"C6297\" Action=\"Warning\" />\n    <Rule Id=\"C6299\" Action=\"Warning\" />\n    <Rule Id=\"C6308\" Action=\"Warning\" />\n    <Rule Id=\"C6310\" Action=\"Warning\" />\n    <Rule Id=\"C6312\" Action=\"Warning\" />\n    <Rule Id=\"C6314\" Action=\"Warning\" />\n    <Rule Id=\"C6317\" Action=\"Warning\" />\n    <Rule Id=\"C6318\" Action=\"Warning\" />\n    <Rule Id=\"C6319\" Action=\"Warning\" />\n    <Rule Id=\"C6324\" Action=\"Warning\" />\n    <Rule Id=\"C6331\" Action=\"Warning\" />\n    <Rule Id=\"C6332\" Action=\"Warning\" />\n    <Rule Id=\"C6333\" Action=\"Warning\" />\n    <Rule Id=\"C6335\" Action=\"Warning\" />\n    <Rule Id=\"C6381\" Action=\"Warning\" />\n    <Rule Id=\"C6383\" Action=\"Warning\" />\n    <Rule Id=\"C6384\" Action=\"Warning\" />\n    <Rule Id=\"C6388\" Action=\"Warning\" />\n    <Rule Id=\"C6993\" Action=\"Warning\" />\n    <Rule Id=\"C6995\" Action=\"Warning\" />\n    <Rule Id=\"C6997\" Action=\"Warning\" />\n  </Rules>\n</RuleSet>"
  },
  {
    "path": "shadertoolsconfig.json",
    "content": "{\n\t\"hlsl.additionalIncludeDirectories\": [\n\t\t\"c:\\\\work\\\\dxclusterbench\\\\src\"\n\t]\n}\n"
  },
  {
    "path": "tools/set_vs_vars.ps1",
    "content": "#\r\n# Find vswhere (installed with recent Visual Studio versions).\r\n#\r\nIf ($vsWhere = Get-Command \"vswhere.exe\" -ErrorAction SilentlyContinue) {\r\n  $vsWhere = $vsWhere.Path\r\n} ElseIf (Test-Path \"${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\Installer\\vswhere.exe\") {\r\n  $vsWhere = \"${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\Installer\\vswhere.exe\"\r\n}\r\n Else {\r\n  Write-Error \"vswhere not found. Aborting.\" -ErrorAction Stop\r\n}\r\nWrite-Host \"vswhere found at: $vsWhere\" -ForegroundColor Yellow\r\n\r\n\r\n#\r\n# Get path to Visual Studio installation using vswhere.\r\n#\r\n$vsPath = &$vsWhere -latest -version \"[16.0,18.0)\" -products * `\r\n -requires Microsoft.Component.MSBuild `\r\n -property installationPath\r\nIf ([string]::IsNullOrEmpty(\"$vsPath\")) {\r\n  Write-Error \"Failed to find Visual Studio 2019-2022 installation. Aborting.\" -ErrorAction Stop\r\n}\r\nWrite-Host \"Using Visual Studio installation at: ${vsPath}\" -ForegroundColor Yellow\r\n\r\n\r\n#\r\n# Make sure the Visual Studio Command Prompt variables are set.\r\n#\r\nIf (Test-Path env:LIBPATH) {\r\n  Write-Host \"Visual Studio Command Prompt variables already set.\" -ForegroundColor Yellow\r\n} Else {\r\n  # Load VC vars\r\n  Push-Location \"${vsPath}\\VC\\Auxiliary\\Build\"\r\n  cmd /c \"vcvarsall.bat x64&set\" |\r\n    ForEach-Object {\r\n      If ($_ -match \"=\") {\r\n        $v = $_.split(\"=\"); Set-Item -Force -Path \"ENV:\\$($v[0])\" -Value \"$($v[1])\"\r\n      }\r\n    }\r\n  Pop-Location\r\n  Write-Host \"Visual Studio Command Prompt variables set.\" -ForegroundColor Yellow\r\n}\r\n\r\n"
  }
]