From 571af41cf6b858f10b319918aa64463a69df59ea Mon Sep 17 00:00:00 2001 From: "erwin.coumans" Date: Mon, 5 Mar 2012 00:54:32 +0000 Subject: [PATCH] Add the GPU rigid body pipeline from https://github.com/erwincoumans/experiments as a Bullet 3.x preview for Bullet 2.80 --- Extras/RigidBodyGpuPipeline/bin/glut32.dll | Bin 0 -> 160256 bytes Extras/RigidBodyGpuPipeline/bin/glut64.dll | Bin 0 -> 272896 bytes .../build/findDirectX11.lua | 36 + .../RigidBodyGpuPipeline/build/findOpenCL.lua | 84 + .../build/findOpenGLGlewGlut.lua | 52 + .../RigidBodyGpuPipeline/build/premake4.lua | 55 + Extras/RigidBodyGpuPipeline/build/vs2008.bat | 10 + Extras/RigidBodyGpuPipeline/build/vs2010.bat | 5 + .../dynamics/basic_demo/AMD/premake4.lua | 45 + .../dynamics/basic_demo/BasicDemo.cpp | 538 +++++ .../dynamics/basic_demo/BasicDemo.h | 86 + .../basic_demo/ConvexHeightFieldShape.cpp | 507 +++++ .../basic_demo/ConvexHeightFieldShape.h | 143 ++ .../dynamics/basic_demo/CubeMapUtils.h | 111 ++ .../basic_demo/CustomCollisionDispatcher.cpp | 699 +++++++ .../basic_demo/CustomCollisionDispatcher.h | 70 + .../basic_demo/CustomConvexPairCollision.cpp | 409 ++++ .../basic_demo/CustomConvexPairCollision.h | 56 + .../dynamics/basic_demo/CustomConvexShape.cpp | 45 + .../dynamics/basic_demo/CustomConvexShape.h | 35 + .../dynamics/basic_demo/Stubs/AdlAabb.cpp | 0 .../dynamics/basic_demo/Stubs/AdlAabb.h | 230 +++ .../dynamics/basic_demo/Stubs/AdlArray.h | 212 ++ .../basic_demo/Stubs/AdlCollideUtils.h | 111 ++ .../basic_demo/Stubs/AdlCollisionShape.h | 49 + .../basic_demo/Stubs/AdlConstraint4.h | 49 + .../dynamics/basic_demo/Stubs/AdlContact4.h | 102 + .../dynamics/basic_demo/Stubs/AdlError.h | 80 + .../dynamics/basic_demo/Stubs/AdlMath.h | 216 ++ .../dynamics/basic_demo/Stubs/AdlMatrix3x3.h | 194 ++ .../dynamics/basic_demo/Stubs/AdlQuaternion.h | 155 ++ .../dynamics/basic_demo/Stubs/AdlRigidBody.h | 59 + .../dynamics/basic_demo/Stubs/AdlTransform.h | 61 + .../dynamics/basic_demo/Stubs/Adlfloat4.inl | 373 ++++ .../basic_demo/Stubs/Adlfloat4SSE.inl | 381 ++++ .../dynamics/basic_demo/Stubs/ChNarrowPhase.h | 154 ++ .../basic_demo/Stubs/ChNarrowphase.inl | 303 +++ .../basic_demo/Stubs/ChNarrowphaseKernels.cl | 1629 +++++++++++++++ .../basic_demo/Stubs/ChNarrowphaseKernels.h | 1616 +++++++++++++++ .../dynamics/basic_demo/Stubs/Solver.h | 203 ++ .../dynamics/basic_demo/Stubs/Solver.inl | 762 +++++++ .../dynamics/basic_demo/Stubs/SolverHost.inl | 848 ++++++++ .../basic_demo/Stubs/SolverKernels.cl | 1051 ++++++++++ .../dynamics/basic_demo/Stubs/SolverKernels.h | 1037 ++++++++++ .../basic_demo/Stubs/batchingKernels.cl | 338 ++++ .../basic_demo/Stubs/batchingKernels.h | 371 ++++ .../dynamics/basic_demo/Stubs/stringify.py | 13 + .../basic_demo/Stubs/stringifykernels.bat | 6 + .../basic_demo/Stubs/stringifykernelsAll.bat | 10 + .../Stubs/stringifykernelsBatching.bat | 8 + .../Stubs/stringifykernelsNarrowphase.bat | 8 + .../Stubs/stringifykernelsSolver.bat | 8 + .../dynamics/basic_demo/main.cpp | 77 + .../dynamics/basic_demo/premake4.lua | 34 + .../dynamics/testbed/DebugCastResult.h | 88 + .../dynamics/testbed/DemoApplication.cpp | 1375 +++++++++++++ .../dynamics/testbed/DemoApplication.h | 257 +++ .../dynamics/testbed/GLDebugDrawer.cpp | 139 ++ .../dynamics/testbed/GLDebugDrawer.h | 38 + .../dynamics/testbed/GLDebugFont.cpp | 1000 ++++++++++ .../dynamics/testbed/GLDebugFont.h | 29 + .../dynamics/testbed/GL_ShapeDrawer.cpp | 1058 ++++++++++ .../dynamics/testbed/GL_ShapeDrawer.h | 70 + .../dynamics/testbed/GL_Simplex1to4.cpp | 76 + .../dynamics/testbed/GL_Simplex1to4.h | 40 + .../dynamics/testbed/GlutDemoApplication.cpp | 87 + .../dynamics/testbed/GlutDemoApplication.h | 34 + .../dynamics/testbed/GlutStuff.cpp | 119 ++ .../dynamics/testbed/GlutStuff.h | 86 + .../dynamics/testbed/RenderTexture.cpp | 86 + .../dynamics/testbed/RenderTexture.h | 73 + .../dynamics/testbed/Win32AppMain.cpp | 405 ++++ .../dynamics/testbed/Win32DemoApplication.cpp | 79 + .../dynamics/testbed/Win32DemoApplication.h | 40 + .../dynamics/testbed/premake4.lua | 18 + .../opencl/3dGridBroadphase/AMD/premake4.lua | 29 + .../MiniCL/MiniCLTaskWrap.cpp | 23 + .../Shared/bt3dGridBroadphaseOCL.cl | 349 ++++ .../Shared/bt3dGridBroadphaseOCL.cpp | 697 +++++++ .../Shared/bt3dGridBroadphaseOCL.h | 146 ++ .../Shared/btGpu3DGridBroadphase.cpp | 626 ++++++ .../Shared/btGpu3DGridBroadphase.h | 154 ++ .../Shared/btGpu3DGridBroadphaseSharedCode.h | 428 ++++ .../Shared/btGpu3DGridBroadphaseSharedDefs.h | 61 + .../Shared/btGpu3DGridBroadphaseSharedTypes.h | 64 + .../3dGridBroadphase/Shared/btGpuDefines.h | 211 ++ .../Shared/btGpuUtilsSharedCode.h | 55 + .../Shared/btGpuUtilsSharedDefs.h | 52 + .../opencl/3dGridBroadphase/premake4.lua | 5 + .../opencl/basic_initialize/AMD/premake4.lua | 23 + .../basic_initialize/Intel/premake4.lua | 23 + .../basic_initialize/NVIDIA/premake4.lua | 23 + .../opencl/basic_initialize/btOpenCLInclude.h | 43 + .../opencl/basic_initialize/btOpenCLUtils.cpp | 731 +++++++ .../opencl/basic_initialize/btOpenCLUtils.h | 104 + .../opencl/basic_initialize/main.cpp | 92 + .../opencl/basic_initialize/premake4.lua | 4 + .../broadphase_benchmark/AMD/premake4.lua | 49 + .../broadphase_benchmark/Intel/premake4.lua | 49 + .../broadphase_benchmark/NVIDIA/premake4.lua | 49 + .../broadphase_benchmark/broadphaseKernel.cl | 335 ++++ .../btGridBroadphaseCL.cpp | 231 +++ .../broadphase_benchmark/btGridBroadphaseCL.h | 73 + .../computeAabbKernelOCL.cl | 112 ++ .../broadphase_benchmark/findPairsOpenCL.cpp | 204 ++ .../broadphase_benchmark/findPairsOpenCL.h | 90 + .../broadphase_benchmark/integrateKernel.cl | 116 ++ .../opencl/broadphase_benchmark/main.cpp | 1565 +++++++++++++++ .../opencl/broadphase_benchmark/premake4.lua | 5 + .../opencl/global_atomics/AMD/premake4.lua | 23 + .../global_atomics/globalAtomicsKernel.h | 36 + .../opencl/global_atomics/global_atomics.cl | 34 + .../opencl/global_atomics/main.cpp | 201 ++ .../opencl/global_atomics/premake4.lua | 4 + .../opencl/global_atomics/stringify.py | 13 + .../global_atomics/stringifykernels.bat | 5 + .../gpu_rigidbody_pipeline/AMD/premake4.lua | 58 + .../gpu_rigidbody_pipeline/CommandLineArgs.h | 91 + .../gpu_rigidbody_pipeline/Intel/premake4.lua | 58 + .../NVIDIA/premake4.lua | 57 + .../btConvexUtility.cpp | 240 +++ .../gpu_rigidbody_pipeline/btConvexUtility.h | 41 + .../btGpuNarrowPhaseAndSolver.cpp | 730 +++++++ .../btGpuNarrowPhaseAndSolver.h | 72 + .../opencl/gpu_rigidbody_pipeline/main.cpp | 1775 +++++++++++++++++ .../gpu_rigidbody_pipeline/premake4.lua | 5 + .../gpu_rigidbody_pipeline2/AMD/premake4.lua | 64 + .../gpu_rigidbody_pipeline2/CLPhysicsDemo.cpp | 529 +++++ .../gpu_rigidbody_pipeline2/CLPhysicsDemo.h | 53 + .../gpu_rigidbody_pipeline2/DemoSettings.h | 24 + .../GLInstancingRenderer.cpp | 861 ++++++++ .../GLInstancingRenderer.h | 45 + .../gpu_rigidbody_pipeline2/GlutRenderer.cpp | 107 + .../gpu_rigidbody_pipeline2/GlutRenderer.h | 59 + .../NVIDIA/premake4.lua | 64 + .../gpu_rigidbody_pipeline2/OpenGLInclude.h | 41 + .../gpu_rigidbody_pipeline2/ShapeData.h | 210 ++ .../Win32OpenGLRenderManager.cpp | 465 +++++ .../Win32OpenGLRenderManager.h | 70 + .../opencl/gpu_rigidbody_pipeline2/main.cpp | 224 +++ .../gpu_rigidbody_pipeline2/premake4.lua | 5 + .../opencl/integration/AMD/premake4.lua | 34 + .../opencl/integration/Intel/premake4.lua | 36 + .../opencl/integration/NVIDIA/premake4.lua | 35 + .../opencl/integration/integrateKernel.cl | 73 + .../opencl/integration/main.cpp | 1106 ++++++++++ .../opencl/integration/premake4.lua | 5 + .../opencl/opengl_interop/AMD/premake4.lua | 33 + .../opencl/opengl_interop/Intel/premake4.lua | 34 + .../opencl/opengl_interop/NVIDIA/premake4.lua | 34 + .../btOpenCLGLInteropBuffer.cpp | 60 + .../opengl_interop/btOpenCLGLInteropBuffer.h | 49 + .../opencl/opengl_interop/btStopwatch.cpp | 182 ++ .../opencl/opengl_interop/btStopwatch.h | 45 + .../opencl/opengl_interop/interopKernel.cl | 13 + .../opencl/opengl_interop/main.cpp | 1057 ++++++++++ .../opencl/opengl_interop/premake4.lua | 5 + .../opencl/primitives/Adl/Adl.cpp | 19 + .../opencl/primitives/Adl/Adl.h | 235 +++ .../opencl/primitives/Adl/Adl.inl | 344 ++++ .../opencl/primitives/Adl/AdlConfig.h | 27 + .../opencl/primitives/Adl/AdlError.h | 80 + .../opencl/primitives/Adl/AdlKernel.h | 142 ++ .../opencl/primitives/Adl/AdlKernel.inl | 223 +++ .../opencl/primitives/Adl/AdlStopwatch.h | 81 + .../opencl/primitives/Adl/AdlStopwatch.inl | 59 + .../opencl/primitives/Adl/CL/AdlCL.inl | 384 ++++ .../primitives/Adl/CL/AdlKernelUtilsCL.inl | 541 +++++ .../opencl/primitives/Adl/DX11/AdlDX11.inl | 512 +++++ .../Adl/DX11/AdlKernelUtilsDX11.inl | 348 ++++ .../primitives/Adl/DX11/AdlStopwatchDX11.inl | 131 ++ .../opencl/primitives/Adl/Host/AdlHost.inl | 107 + .../primitives/Adl/Host/AdlStopwatchHost.inl | 119 ++ .../primitives/AdlPrimitives/Copy/Copy.h | 73 + .../primitives/AdlPrimitives/Copy/Copy.inl | 151 ++ .../AdlPrimitives/Copy/CopyHost.inl | 85 + .../AdlPrimitives/Copy/CopyKernels.cl | 128 ++ .../AdlPrimitives/Copy/CopyKernels.hlsl | 130 ++ .../AdlPrimitives/Copy/CopyKernelsCL.h | 119 ++ .../AdlPrimitives/Copy/CopyKernelsDX11.h | 120 ++ .../primitives/AdlPrimitives/Fill/Fill.h | 77 + .../primitives/AdlPrimitives/Fill/Fill.inl | 123 ++ .../AdlPrimitives/Fill/FillHost.inl | 99 + .../AdlPrimitives/Fill/FillKernels.cl | 81 + .../AdlPrimitives/Fill/FillKernels.hlsl | 79 + .../AdlPrimitives/Fill/FillKernelsCL.h | 71 + .../AdlPrimitives/Fill/FillKernelsDX11.h | 69 + .../primitives/AdlPrimitives/Math/Array.h | 231 +++ .../primitives/AdlPrimitives/Math/Float2.inl | 173 ++ .../primitives/AdlPrimitives/Math/Float4.inl | 375 ++++ .../primitives/AdlPrimitives/Math/Math.h | 224 +++ .../primitives/AdlPrimitives/Math/MathCL.h | 357 ++++ .../primitives/AdlPrimitives/Math/Matrix3x3.h | 197 ++ .../AdlPrimitives/Math/Quaternion.h | 159 ++ .../AdlPrimitives/Scan/PrefixScan.h | 73 + .../AdlPrimitives/Scan/PrefixScan.inl | 125 ++ .../AdlPrimitives/Scan/PrefixScanHost.inl | 74 + .../AdlPrimitives/Scan/PrefixScanKernels.cl | 153 ++ .../AdlPrimitives/Scan/PrefixScanKernels.hlsl | 157 ++ .../AdlPrimitives/Scan/PrefixScanKernelsCL.h | 143 ++ .../Scan/PrefixScanKernelsDX11.h | 147 ++ .../AdlPrimitives/Search/BoundSearch.h | 73 + .../AdlPrimitives/Search/BoundSearch.inl | 128 ++ .../AdlPrimitives/Search/BoundSearchHost.inl | 111 ++ .../Search/BoundSearchKernels.cl | 112 ++ .../Search/BoundSearchKernels.hlsl | 104 + .../Search/BoundSearchKernelsCL.h | 102 + .../Search/BoundSearchKernelsDX11.h | 94 + .../primitives/AdlPrimitives/Sort/RadixSort.h | 53 + .../AdlPrimitives/Sort/RadixSort.inl | 58 + .../AdlPrimitives/Sort/RadixSort32.h | 98 + .../AdlPrimitives/Sort/RadixSort32.inl | 346 ++++ .../AdlPrimitives/Sort/RadixSort32Host.inl | 163 ++ .../AdlPrimitives/Sort/RadixSort32Kernels.cl | 1104 ++++++++++ .../Sort/RadixSort32Kernels.hlsl | 1011 ++++++++++ .../AdlPrimitives/Sort/RadixSort32KernelsCL.h | 1106 ++++++++++ .../Sort/RadixSort32KernelsDX11.h | 1013 ++++++++++ .../Sort/RadixSortAdvancedKernels.hlsl | 985 +++++++++ .../Sort/RadixSortAdvancedKernelsDX11.h | 987 +++++++++ .../AdlPrimitives/Sort/RadixSortHost.inl | 93 + .../AdlPrimitives/Sort/RadixSortSimpleCL.h | 134 ++ .../AdlPrimitives/Sort/RadixSortSimpleDX11.h | 131 ++ .../Sort/RadixSortSimpleKernels.cl | 147 ++ .../Sort/RadixSortSimpleKernels.hlsl | 133 ++ .../Sort/RadixSortSimpleKernelsCL.h | 149 ++ .../Sort/RadixSortSimpleKernelsDX11.h | 135 ++ .../AdlPrimitives/Sort/RadixSortStandard.inl | 177 ++ .../Sort/RadixSortStandardKernels.cl | 345 ++++ .../Sort/RadixSortStandardKernels.hlsl | 322 +++ .../Sort/RadixSortStandardKernelsCL.h | 347 ++++ .../Sort/RadixSortStandardKernelsDX11.h | 324 +++ .../primitives/AdlPrimitives/Sort/SortData.h | 31 + .../AdlPrimitives/Sort/radixsortadvanced.inl | 146 ++ .../AdlPrimitives/Sort/radixsortsimple.inl | 149 ++ .../primitives/AdlPrimitives/stringify.py | 13 + .../AdlPrimitives/stringifykernels.bat | 22 + .../primitives/AdlTest/AMD/premake4.lua | 31 + .../primitives/AdlTest/Intel/premake4.lua | 31 + .../AdlTest/LaunchOverheadBenchmark.h | 103 + .../primitives/AdlTest/NVIDIA/premake4.lua | 31 + .../primitives/AdlTest/RadixSortBenchmark.h | 121 ++ .../opencl/primitives/AdlTest/UnitTests.h | 801 ++++++++ .../opencl/primitives/AdlTest/main.cpp | 118 ++ .../opencl/primitives/AdlTest/premake4.lua | 4 + .../primitives/benchmark/AMD/premake4.lua | 29 + .../primitives/benchmark/NVIDIA/premake4.lua | 29 + .../opencl/primitives/benchmark/premake4.lua | 2 + .../benchmark/test_large_problem_sorting.cpp | 705 +++++++ .../opencl/vector_add/AMD/premake4.lua | 21 + .../opencl/vector_add/Intel/premake4.lua | 23 + .../opencl/vector_add/NVIDIA/premake4.lua | 23 + .../opencl/vector_add/VectorAddKernels.cl | 16 + .../opencl/vector_add/VectorAddKernels.h | 18 + .../opencl/vector_add/main.cpp | 367 ++++ .../opencl/vector_add/premake4.lua | 4 + .../opencl/vector_add/stringify.py | 13 + .../vector_add/stringifyVectorAddKernel.bat | 8 + 257 files changed, 55106 insertions(+) create mode 100644 Extras/RigidBodyGpuPipeline/bin/glut32.dll create mode 100644 Extras/RigidBodyGpuPipeline/bin/glut64.dll create mode 100644 Extras/RigidBodyGpuPipeline/build/findDirectX11.lua create mode 100644 Extras/RigidBodyGpuPipeline/build/findOpenCL.lua create mode 100644 Extras/RigidBodyGpuPipeline/build/findOpenGLGlewGlut.lua create mode 100644 Extras/RigidBodyGpuPipeline/build/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/build/vs2008.bat create mode 100644 Extras/RigidBodyGpuPipeline/build/vs2010.bat create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/BasicDemo.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/BasicDemo.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/ConvexHeightFieldShape.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/ConvexHeightFieldShape.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CubeMapUtils.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlArray.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollideUtils.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollisionShape.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlConstraint4.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlContact4.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlError.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMath.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMatrix3x3.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlQuaternion.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlRigidBody.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlTransform.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4.inl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4SSE.inl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowPhase.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphase.inl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.inl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/batchingKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/batchingKernels.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringify.py create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernels.bat create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsAll.bat create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsBatching.bat create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsNarrowphase.bat create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsSolver.bat create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/basic_demo/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/DebugCastResult.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32AppMain.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.cpp create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.h create mode 100644 Extras/RigidBodyGpuPipeline/dynamics/testbed/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/MiniCL/MiniCLTaskWrap.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedCode.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedDefs.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedTypes.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpuDefines.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpuUtilsSharedCode.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpuUtilsSharedDefs.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLInclude.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/basic_initialize/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/broadphaseKernel.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/computeAabbKernelOCL.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/integrateKernel.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/globalAtomicsKernel.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/global_atomics.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringify.py create mode 100644 Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringifykernels.bat create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/CommandLineArgs.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btConvexUtility.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btConvexUtility.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/DemoSettings.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/OpenGLInclude.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/ShapeData.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/integration/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/integration/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/integration/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/integration/integrateKernel.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/integration/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/integration/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btOpenCLGLInteropBuffer.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btOpenCLGLInteropBuffer.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btStopwatch.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btStopwatch.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/interopKernel.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/opengl_interop/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlConfig.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlError.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlCL.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlKernelUtilsCL.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlDX11.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlKernelUtilsDX11.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlStopwatchDX11.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Host/AdlHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Host/AdlStopwatchHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/Copy.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/Copy.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Array.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float2.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float4.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Math.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/MathCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Matrix3x3.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Quaternion.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanKernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearch.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearch.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Host.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Kernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Kernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32KernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32KernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortAdvancedKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortAdvancedKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortHost.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleKernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandard.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.hlsl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsCL.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsDX11.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/SortData.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortadvanced.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortsimple.inl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringify.py create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringifykernels.bat create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/LaunchOverheadBenchmark.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/RadixSortBenchmark.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/UnitTests.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/test_large_problem_sorting.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/AMD/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/Intel/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/NVIDIA/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.cl create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.h create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/main.cpp create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/premake4.lua create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/stringify.py create mode 100644 Extras/RigidBodyGpuPipeline/opencl/vector_add/stringifyVectorAddKernel.bat diff --git a/Extras/RigidBodyGpuPipeline/bin/glut32.dll b/Extras/RigidBodyGpuPipeline/bin/glut32.dll new file mode 100644 index 0000000000000000000000000000000000000000..3297bd0783b702755a952ad50e518d3e1a922595 GIT binary patch literal 160256 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 G;J*P491%kR literal 0 HcmV?d00001 diff --git a/Extras/RigidBodyGpuPipeline/build/findDirectX11.lua b/Extras/RigidBodyGpuPipeline/build/findDirectX11.lua new file mode 100644 index 000000000..68771c4a0 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/build/findDirectX11.lua @@ -0,0 +1,36 @@ +function findDirectX11() + local dx11path = os.getenv("DXSDK_DIR") + if (dx11path) then + local filepath = string.format("%s%s",dx11path,"Include/D3D11.h") + headerdx11 = io.open(filepath, "r") + if (headerdx11) then + printf("Found DX11: '%s'", filepath) + return true + end + end + return false + end + +function initDirectX11() + configuration {} + + local dx11path = os.getenv("DXSDK_DIR") + defines { "ADL_ENABLE_DX11"} + includedirs {"$(DXSDK_DIR)/include"} + + configuration "x32" + libdirs {"$(DXSDK_DIR)/Lib/x86"} + configuration "x64" + libdirs {"$(DXSDK_DIR)/Lib/x64"} + configuration {} + links {"d3dcompiler", + "dxerr", + "dxguid", + "d3dx9", + "d3d9", + "winmm", + "comctl32", + "d3dx11" + } + return true +end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/build/findOpenCL.lua b/Extras/RigidBodyGpuPipeline/build/findOpenCL.lua new file mode 100644 index 000000000..913b8406d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/build/findOpenCL.lua @@ -0,0 +1,84 @@ + -- todo: add Apple OpenCL environment vars + + function findOpenCL_AMD() + local amdopenclpath = os.getenv("AMDAPPSDKROOT") + if (amdopenclpath) then + return true + end + return false + end + + function findOpenCL_NVIDIA() + local nvidiaopenclpath = os.getenv("CUDA_PATH") + if (nvidiaopenclpath) then + return true + end + return false + end + + function findOpenCL_Intel() + local intelopenclpath = os.getenv("INTELOCLSDKROOT") + if (intelopenclpath) then + return true + end + return false + end + + function initOpenCL_AMD() + configuration {} + local amdopenclpath = os.getenv("AMDAPPSDKROOT") + if (amdopenclpath) then + defines { "ADL_ENABLE_CL" , "CL_PLATFORM_AMD"} + includedirs { + "$(AMDAPPSDKROOT)/include" + } + configuration "x32" + libdirs {"$(AMDAPPSDKROOT)/lib/x86"} + configuration "x64" + libdirs {"$(AMDAPPSDKROOT)/lib/x86_64"} + configuration {} + links {"OpenCL"} + return true + end + return false + end + + + function initOpenCL_NVIDIA() + configuration {} + local nvidiaopenclpath = os.getenv("CUDA_PATH") + if (nvidiaopenclpath) then + defines { "ADL_ENABLE_CL" , "CL_PLATFORM_NVIDIA"} + includedirs { + "$(CUDA_PATH)/include" + } + configuration "x32" + libdirs {"$(CUDA_PATH)/lib/Win32"} + configuration "x64" + libdirs {"$(CUDA_PATH)/lib/x64"} + configuration {} + links {"OpenCL"} + return true + end + return false + end + + function initOpenCL_Intel() + configuration {} + local intelopenclpath = os.getenv("INTELOCLSDKROOT") + if (intelopenclpath) then + defines { "ADL_ENABLE_CL" , "CL_PLATFORM_INTEL"} + includedirs { + "$(INTELOCLSDKROOT)/include" + } + configuration "x32" + libdirs {"$(INTELOCLSDKROOT)/lib/x86"} + configuration "x64" + libdirs {"$(INTELOCLSDKROOT)/lib/x64"} + configuration {} + links {"OpenCL"} + return true + end + return false + end + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/build/findOpenGLGlewGlut.lua b/Extras/RigidBodyGpuPipeline/build/findOpenGLGlewGlut.lua new file mode 100644 index 000000000..2a04c6d70 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/build/findOpenGLGlewGlut.lua @@ -0,0 +1,52 @@ + -- todo: add Apple OpenCL environment vars + + function initOpenGL() + configuration {} + configuration {"Windows"} + links {"opengl32"} + configuration {"MacOSX"} + links { "Carbon.framework","OpenGL.framework","AGL.framework"} + configuration {"not Windows", "not MacOSX"} + links {"GL","GLU"} + configuration{} + end + + function initGlut() + configuration {} + configuration {"Windows"} + + includedirs { + projectRootDir .. "../../Glut" + } + libdirs { projectRootDir .. "../../Glut"} + configuration {"Windows", "x32"} + links {"glut32"} + configuration {"Windows", "x64"} + links {"glut64"} + + configuration {"MacOSX"} + links { "Glut.framework" } + + configuration {"not Windows", "not MacOSX"} + links {"glut"} + configuration{} + end + + function initGlew() + configuration {} + configuration {"Windows"} + defines { "GLEW_STATIC"} + includedirs { + projectRootDir .. "../../Glut" + } + libdirs { projectRootDir .. "../../Glut"} + configuration {"Windows", "x32"} + links {"glew32s"} + configuration {"Windows", "x64"} + links {"glew64s"} + + configuration{} + end + + + diff --git a/Extras/RigidBodyGpuPipeline/build/premake4.lua b/Extras/RigidBodyGpuPipeline/build/premake4.lua new file mode 100644 index 000000000..b317d35f7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/build/premake4.lua @@ -0,0 +1,55 @@ +solution "0MySolution" + + -- Multithreaded compiling + if _ACTION == "vs2010" then + buildoptions { "/MP" } + end + + + + configurations {"Release", "Debug"} + configuration "Release" + flags { "Optimize", "StaticRuntime", "NoMinimalRebuild", "FloatFast"} + configuration "Debug" + flags { "Symbols", "StaticRuntime" , "NoMinimalRebuild", "NoEditAndContinue" ,"FloatFast"} + + platforms {"x32", "x64"} + + configuration "x64" + targetsuffix "_64" + configuration {"x64", "debug"} + targetsuffix "_x64_debug" + configuration {"x64", "release"} + targetsuffix "_x64" + configuration {"x32", "debug"} + targetsuffix "_debug" + + configuration{} + + flags { "NoRTTI", "NoExceptions"} + defines { "_HAS_EXCEPTIONS=0" } + targetdir "../bin" + location("./" .. _ACTION) + + + projectRootDir = os.getcwd() .. "/../" + print("Project root directroy: " .. projectRootDir); + + dofile ("findOpenCL.lua") + dofile ("findDirectX11.lua") + dofile ("findOpenGLGlewGlut.lua") + + language "C++" + + include "../opencl/gpu_rigidbody_pipeline2" + include "../opencl/gpu_rigidbody_pipeline" + + include "../opencl/basic_initialize" + include "../opencl/vector_add" + + include "../opencl/primitives/AdlTest" + include "../opencl/primitives/benchmark" + include "../opencl/3dGridBroadphase" + include "../opencl/broadphase_benchmark" + + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/build/vs2008.bat b/Extras/RigidBodyGpuPipeline/build/vs2008.bat new file mode 100644 index 000000000..02665f970 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/build/vs2008.bat @@ -0,0 +1,10 @@ + +rem premake4 --no-pelibs vs2008 +rem premake4 --no-pedemos vs2008 +rem premake4 --no-bulletlibs --no-pelibs vs2008 +rem premake4 --with-nacl vs2008 + +..\..\..\msvc\premake4 vs2008 +mkdir vs2008\cache + +pause \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/build/vs2010.bat b/Extras/RigidBodyGpuPipeline/build/vs2010.bat new file mode 100644 index 000000000..9122bab9a --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/build/vs2010.bat @@ -0,0 +1,5 @@ + +..\..\..\msvc\premake4 vs2010 + +mkdir vs2010\cache +pause \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/AMD/premake4.lua new file mode 100644 index 000000000..159e25fbc --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/AMD/premake4.lua @@ -0,0 +1,45 @@ +if os.is("Windows") then + + hasCL = findOpenCL_AMD() + + if (hasCL) then + + project "basic_bullet2_demo_AMD" + + initOpenCL_AMD() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + includedirs { + "..", + "../../../bullet2", + "../../testbed", + "../../../rendering/Gwen", + "../../../opencl/basic_initialize", + "../../../opencl/primitives" + } + + + links { "testbed", + "bullet2", + "gwen" + } + + + initOpenGL() + initGlut() + + + files { + "../**.cpp", + "../**.h", + "../../../opencl/basic_initialize/btOpenCLUtils.cpp", + "../../../opencl/basic_initialize/btOpenCLUtils.h" + } + + end + +end diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/BasicDemo.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/BasicDemo.cpp new file mode 100644 index 000000000..0b6d452ac --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/BasicDemo.cpp @@ -0,0 +1,538 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "BasicDemo.h" +#include "GlutStuff.h" +///btBulletDynamicsCommon.h is the main Bullet include file, contains most common include files. +#include "btBulletDynamicsCommon.h" +#include "CustomConvexShape.h" +#include "CustomConvexPairCollision.h" +#include "CustomCollisionDispatcher.h" + +#include "ConvexHeightFieldShape.h" +#include "GLDebugDrawer.h" +static GLDebugDrawer sDebugDraw; + +#include //printf debugging + +#ifdef CL_PLATFORM_AMD +#include "../../opencl/basic_initialize/btOpenCLUtils.h" + +cl_context g_cxMainContext=0; +cl_command_queue g_cqCommandQue=0; +cl_device_id g_clDevice=0; +#endif + +///create 125 (5x5x5) dynamic object +#define ARRAY_SIZE_X 6 +#define ARRAY_SIZE_Y 6 +#define ARRAY_SIZE_Z 4 + +//maximum number of objects (and allow user to shoot additional boxes) +#define MAX_PROXIES (ARRAY_SIZE_X*ARRAY_SIZE_Y*ARRAY_SIZE_Z + 1024) + +///scaling of the objects (0.1 = 20 centimeter boxes ) +#define SCALING 1. +#define START_POS_X 0 +#define START_POS_Y -0.8 +#define START_POS_Z 0 + +#define BoxVtxCount 8 + +static float BoxVtx[] = { +-0.5,-0.5,-0.5, +-0.5,-0.5,0.5, +-0.5,0.5,-0.5, +-0.5,0.5,0.5, +0.5,-0.5,-0.5, +0.5,-0.5,0.5, +0.5,0.5,-0.5, +0.5,0.5,0.5, +}; + +static float BoxVtx2[] = { +-20.3,-10.3,-20.3, +-20.3,-10.3,20.3, +-20.3,10.3,-20.3, +-20.3,10.3,20.3, +20.3,-10.3,-20.3, +20.3,-10.3,20.3, +20.3,10.3,-20.3, +20.3,10.3,20.3, +}; + + +#define BarrelVtxCount2 57 + +static float BarrelVtx2[] = { +0.0f,-0.5f,0.0f, 0.0f,-1.0f,0.0f, +0.282362f,-0.5f,-0.205148f, 0.0f,-1.0f,0.0f, +0.349018f,-0.5f,0.0f, 0.0f,-1.0f,0.0f, +0.107853f,-0.5f,-0.331936f, 0.0f,-1.0f,0.0f, +-0.107853f,-0.5f,-0.331936f, 0.0f,-1.0f,0.0f, +0.107853f,-0.5f,-0.331936f, 0.0f,-1.0f,0.0f, +-0.282362f,-0.5f,-0.205148f, 0.0f,-1.0f,0.0f, +-0.349018f,-0.5f,0.0f, 0.0f,-1.0f,0.0f, +-0.282362f,-0.5f,0.205148f, 0.0f,-1.0f,0.0f, +-0.107853f,-0.5f,0.331936f, 0.0f,-1.0f,0.0f, +0.107853f,-0.5f,0.331936f, 0.0f,-1.0f,0.0f, +0.282362f,-0.5f,0.205148f, 0.0f,-1.0f,0.0f, +0.0f,0.5f,0.0f, 0.0f,1.0f,0.0f, +0.349018f,0.5f,0.0f, 0.0f,1.0f,0.0f, +0.282362f,0.5f,-0.205148f, 0.0f,1.0f,0.0f, +0.107853f,0.5f,-0.331936f, 0.0f,1.0f,0.0f, +0.107853f,0.5f,-0.331936f, 0.0f,1.0f,0.0f, +-0.107853f,0.5f,-0.331936f, 0.0f,1.0f,0.0f, +-0.282362f,0.5f,-0.205148f, 0.0f,1.0f,0.0f, +-0.349018f,0.5f,0.0f, 0.0f,1.0f,0.0f, +-0.282362f,0.5f,0.205148f, 0.0f,1.0f,0.0f, +-0.107853f,0.5f,0.331936f, 0.0f,1.0f,0.0f, +0.107853f,0.5f,0.331936f, 0.0f,1.0f,0.0f, +0.282362f,0.5f,0.205148f, 0.0f,1.0f,0.0f, +0.349018f,-0.5f,0.0f, 0.957307f,-0.289072f,0.0f, +0.404509f,0.0f,-0.293893f, 0.809017f,0.0f,-0.587785f, +0.5f,0.0f,0.0f, 1.0f,0.0f,0.0f, +0.282362f,-0.5f,-0.205148f, 0.774478f,-0.289072f,-0.562691f, +0.154508f,0.0f,-0.475528f, 0.309017f,0.0f,-0.951057f, +0.107853f,-0.5f,-0.331936f, 0.295824f,-0.289072f,-0.910453f, +0.107853f,-0.5f,-0.331936f, 0.295824f,-0.289072f,-0.910453f, +-0.154509f,0.0f,-0.475528f, -0.309017f,0.0f,-0.951057f, +0.154508f,0.0f,-0.475528f, 0.309017f,0.0f,-0.951057f, +-0.107853f,-0.5f,-0.331936f, -0.295824f,-0.289072f,-0.910453f, +-0.404509f,0.0f,-0.293893f, -0.809017f,0.0f,-0.587785f, +-0.282362f,-0.5f,-0.205148f, -0.774478f,-0.289072f,-0.562691f, +-0.5f,0.0f,0.0f, -1.0f,0.0f,0.0f, +-0.349018f,-0.5f,0.0f, -0.957307f,-0.289072f,0.0f, +-0.404508f,0.0f,0.293893f, -0.809017f,0.0f,0.587785f, +-0.282362f,-0.5f,0.205148f, -0.774478f,-0.289072f,0.562691f, +-0.154509f,0.0f,0.475528f, -0.309017f,0.0f,0.951056f, +-0.107853f,-0.5f,0.331936f, -0.295824f,-0.289072f,0.910453f, +0.154509f,0.0f,0.475528f, 0.309017f,0.0f,0.951056f, +0.107853f,-0.5f,0.331936f, 0.295824f,-0.289072f,0.910453f, +0.404509f,0.0f,0.293892f, 0.809017f,0.0f,0.587785f, +0.282362f,-0.5f,0.205148f, 0.774478f,-0.289072f,0.562691f, +0.282362f,0.5f,-0.205148f, 0.774478f,0.289072f,-0.562691f, +0.349018f,0.5f,0.0f, 0.957307f,0.289072f,0.0f, +0.107853f,0.5f,-0.331936f, 0.295824f,0.289072f,-0.910453f, +-0.107853f,0.5f,-0.331936f, -0.295824f,0.289072f,-0.910453f, +0.107853f,0.5f,-0.331936f, 0.295824f,0.289072f,-0.910453f, +-0.282362f,0.5f,-0.205148f, -0.774478f,0.289072f,-0.562691f, +-0.349018f,0.5f,0.0f, -0.957307f,0.289072f,0.0f, +-0.282362f,0.5f,0.205148f, -0.774478f,0.289072f,0.562691f, +-0.107853f,0.5f,0.331936f, -0.295824f,0.289072f,0.910453f, +0.107853f,0.5f,0.331936f, 0.295824f,0.289072f,0.910453f, +0.282362f,0.5f,0.205148f, 0.774478f,0.289072f,0.562691f, +}; + + +static int BarrelIdx[] = { +0,1,2, +0,3,1, +0,4,5, +0,6,4, +0,7,6, +0,8,7, +0,9,8, +0,10,9, +0,11,10, +0,2,11, +12,13,14, +12,14,15, +12,16,17, +12,17,18, +12,18,19, +12,19,20, +12,20,21, +12,21,22, +12,22,23, +12,23,13, +24,25,26, +24,27,25, +27,28,25, +27,29,28, +30,31,32, +30,33,31, +33,34,31, +33,35,34, +35,36,34, +35,37,36, +37,38,36, +37,39,38, +39,40,38, +39,41,40, +41,42,40, +41,43,42, +43,44,42, +43,45,44, +45,26,44, +45,24,26, +26,46,47, +26,25,46, +25,48,46, +25,28,48, +32,49,50, +32,31,49, +31,51,49, +31,34,51, +34,52,51, +34,36,52, +36,53,52, +36,38,53, +38,54,53, +38,40,54, +40,55,54, +40,42,55, +42,56,55, +42,44,56, +44,47,56, +44,26,47, +}; + + +__inline void glVertexFloat4( const float4& v ) +{ + glVertex3f( v.x, v.y, v.z ); +} + +__inline void drawPointListTransformed(const float4* vtx, int nVtx, const float4& translation, const Quaternion& quat) +{ + glPushMatrix(); + + Matrix3x3 rotMat = mtTranspose( qtGetRotationMatrix( quat ) ); + float transformMat[16] = + { + rotMat.m_row[0].x, rotMat.m_row[0].y, rotMat.m_row[0].z, 0, + rotMat.m_row[1].x, rotMat.m_row[1].y, rotMat.m_row[1].z, 0, + rotMat.m_row[2].x, rotMat.m_row[2].y, rotMat.m_row[2].z, 0, + translation.x, translation.y, translation.z,1 + }; + + glMultMatrixf( transformMat ); + + float4 c = make_float4(1,1,0,0); + + glPointSize(3.f); + glBegin(GL_POINTS); + for(int i=0; igetDebugDrawer()->getDebugMode()& btIDebugDraw::DBG_DrawContactPoints) + for (int i=0;igetCollisionObjectArray().size();i++) + { + btCollisionObject* ob = m_dynamicsWorld->getCollisionObjectArray()[i]; + if (ob->getCollisionShape()->getShapeType() == CUSTOM_POLYHEDRAL_SHAPE_TYPE) + { + CustomConvexShape* customConvex = (CustomConvexShape*)ob->getCollisionShape(); + ConvexHeightField* cvxShape= customConvex->m_ConvexHeightField; + if (!cvxShape) + { + printf("aargh\n"); + } + + float4 bodyApos; + Quaternion bodyAquat; + + + const btVector3& pA = ob->getWorldTransform().getOrigin(); + btQuaternion qA = ob->getWorldTransform().getRotation(); + + bodyApos.x = pA.getX(); + bodyApos.y = pA.getY(); + bodyApos.z = pA.getZ(); + bodyApos.w = 0.f; + bodyAquat.x = qA.getX(); + bodyAquat.y = qA.getY(); + bodyAquat.z = qA.getZ(); + bodyAquat.w = qA.getW(); + + + displaySamples(cvxShape->getSamplePoints(),cvxShape->getNumSamplePoints(),bodyApos,bodyAquat); + + } + + } +} +void BasicDemo::clientMoveAndDisplay() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //simple dynamics world doesn't handle fixed-time-stepping + float ms = getDeltaTimeMicroseconds(); + + ///step the simulation + if (m_dynamicsWorld) + { + m_dynamicsWorld->stepSimulation(ms / 1000000.f); + //optional but useful: debug drawing + m_dynamicsWorld->debugDrawWorld(); + } + + renderme(); + + renderSurfacePoints(); + + + glFlush(); + + swapBuffers(); + +} + + + +void BasicDemo::displayCallback(void) { + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderme(); + + renderSurfacePoints(); + + //optional but useful: debug drawing to detect problems + if (m_dynamicsWorld) + m_dynamicsWorld->debugDrawWorld(); + + glFlush(); + swapBuffers(); +} + + + + + +void BasicDemo::initPhysics() +{ + setTexturing(true); + setShadows(true); + + m_acceleratedRigidBodies = 0; + + setCameraDistance(btScalar(SCALING*20.)); + + ///collision configuration contains default setup for memory, collision setup + m_collisionConfiguration = new btDefaultCollisionConfiguration(); + //m_collisionConfiguration->setConvexConvexMultipointIterations(); + + ///use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded) + m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration); + + +#ifdef CL_PLATFORM_AMD + m_dispatcher = new CustomCollisionDispatcher(m_collisionConfiguration, g_cxMainContext,g_clDevice,g_cqCommandQue); +#else + m_dispatcher = new CustomCollisionDispatcher(m_collisionConfiguration); +#endif + + m_dispatcher->registerCollisionCreateFunc(CUSTOM_POLYHEDRAL_SHAPE_TYPE,CUSTOM_POLYHEDRAL_SHAPE_TYPE,new CustomConvexConvexPairCollision::CreateFunc(m_collisionConfiguration->getSimplexSolver(), m_collisionConfiguration->getPenetrationDepthSolver())); + + m_broadphase = new btDbvtBroadphase(); + + ///the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded) + btSequentialImpulseConstraintSolver* sol = new btSequentialImpulseConstraintSolver; + m_solver = sol; + + m_dynamicsWorld = new btDiscreteDynamicsWorld(m_dispatcher,m_broadphase,m_solver,m_collisionConfiguration); + + m_dynamicsWorld->setGravity(btVector3(0,-10,0)); + + m_dynamicsWorld->setDebugDrawer(&sDebugDraw); + + ///create a few basic rigid bodies + //btCollisionShape* groundShape = new btBoxShape(btVector3(btScalar(50.),btScalar(50.),btScalar(50.))); +#if 1 + CustomConvexShape* groundShape = new CustomConvexShape(BoxVtx2,BoxVtxCount,3*sizeof(float)); + //btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0,1,0),0); + + m_collisionShapes.push_back(groundShape); + + btTransform groundTransform; + groundTransform.setIdentity(); + groundTransform.setOrigin(btVector3(0,-11,0)); + + //We can also use DemoApplication::localCreateRigidBody, but for clarity it is provided here: + { + btScalar mass(0.); + + //rigidbody is dynamic if and only if mass is non zero, otherwise static + bool isDynamic = (mass != 0.f); + + btVector3 localInertia(0,0,0); + if (isDynamic) + groundShape->calculateLocalInertia(mass,localInertia); + + //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects + btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform); + btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,groundShape,localInertia); + btRigidBody* body = new btRigidBody(rbInfo); + + //add the body to the dynamics world + m_dynamicsWorld->addRigidBody(body); + } +#endif + + + { + //create a few dynamic rigidbodies + // Re-using the same collision is better for memory usage and performance + + //btCollisionShape* colShape = new btBoxShape(btVector3(SCALING*1,SCALING*1,SCALING*1)); + //btCollisionShape* colShape = new btSphereShape(btScalar(1.)); +#define USE_CUSTOM_HEIGHTFIELD_SHAPE +#ifdef USE_CUSTOM_HEIGHTFIELD_SHAPE + CustomConvexShape* colShape = new CustomConvexShape(BarrelVtx2,BarrelVtxCount2,6*sizeof(float)); + + //CustomConvexShape* colShape = new CustomConvexShape(BoxVtx,BoxVtxCount,3*sizeof(float)); +#else + btConvexHullShape* colShape = new btConvexHullShape(BarrelVtx2,BarrelVtxCount2,6*sizeof(float)); + colShape->setLocalScaling(btVector3(0.9,0.9,0.9)); + +#endif //USE_CUSTOM_HEIGHTFIELD_SHAPE + btScalar scale = 0.5f; + + //btScalar scale = 1.f; + + //next line is already called inside the CustomConvexShape constructor + //colShape->initializePolyhedralFeatures(); + + m_collisionShapes.push_back(colShape); + + /// Create Dynamic Objects + btTransform startTransform; + startTransform.setIdentity(); + + btScalar mass(1.f); + + //rigidbody is dynamic if and only if mass is non zero, otherwise static + bool isDynamic = (mass != 0.f); + + btVector3 localInertia(0,0,0); + if (isDynamic) + colShape->calculateLocalInertia(mass,localInertia); + + float start_x = START_POS_X - ARRAY_SIZE_X/2; + float start_y = START_POS_Y; + float start_z = START_POS_Z - ARRAY_SIZE_Z/2; + + for (int k=0;k0) && ((j<2) || (j>(ARRAY_SIZE_Z-3)))) + // continue; + // if ((k>0) && ((i<2) || (i>(ARRAY_SIZE_X-3)))) + // continue; + + startTransform.setOrigin(SCALING*btVector3( + btScalar(scale*2.0*i + start_x), + btScalar(scale*1+scale*2.0*k + start_y), + btScalar(scale*2.0*j + start_z))); + + + //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects + btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform); + btRigidBody* body=0; + + if (0)//k==0) + { + btVector3 zeroInertia(0,0,0); + btRigidBody::btRigidBodyConstructionInfo rbInfo(0.f,myMotionState,colShape,zeroInertia); + body = new btRigidBody(rbInfo); + } else + { + btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,colShape,localInertia); + body = new btRigidBody(rbInfo); + } + + //m_acceleratedRigidBodies is used as a mapping to the accelerated rigid body index + body->setCompanionId(m_acceleratedRigidBodies++); + m_dynamicsWorld->addRigidBody(body); + + } + } + } + } + } + + +} +void BasicDemo::clientResetScene() +{ + exitPhysics(); + initPhysics(); +} + + +void BasicDemo::exitPhysics() +{ + + //cleanup in the reverse order of creation/initialization + + //remove the rigidbodies from the dynamics world and delete them + int i; + for (i=m_dynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--) + { + btCollisionObject* obj = m_dynamicsWorld->getCollisionObjectArray()[i]; + btRigidBody* body = btRigidBody::upcast(obj); + if (body && body->getMotionState()) + { + delete body->getMotionState(); + } + m_dynamicsWorld->removeCollisionObject( obj ); + delete obj; + } + + //delete collision shapes + for (int j=0;j m_collisionShapes; + + btBroadphaseInterface* m_broadphase; + + btCollisionDispatcher* m_dispatcher; + + btConstraintSolver* m_solver; + + btDefaultCollisionConfiguration* m_collisionConfiguration; + + int m_acceleratedRigidBodies; + + public: + + BasicDemo() + { + } + virtual ~BasicDemo() + { + exitPhysics(); + } + void initPhysics(); + + void exitPhysics(); + + virtual void clientMoveAndDisplay(); + + virtual void displayCallback(); + virtual void clientResetScene(); + + static DemoApplication* Create() + { + BasicDemo* demo = new BasicDemo; + demo->myinit(); + demo->initPhysics(); + return demo; + } + + void renderSurfacePoints(); + + +}; + +#endif //BASIC_DEMO_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/ConvexHeightFieldShape.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/ConvexHeightFieldShape.cpp new file mode 100644 index 000000000..e5e0d649e --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/ConvexHeightFieldShape.cpp @@ -0,0 +1,507 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#include "ConvexHeightFieldShape.h" +#include "Stubs/AdlCollideUtils.h" +#include "CubeMapUtils.h" +//#include +//#include +//#include "GlutStuff.h" + +//#define USE_OLD + +ConvexHeightField::ConvexHeightField(const float4* vtxBuffer, const int4* idxBuffer, int nTriangles) +: CollisionShape( SHAPE_CONVEX_HEIGHT_FIELD ) +{ + create( vtxBuffer, idxBuffer, nTriangles ); +} + +void ConvexHeightField::create( const float4* vtxBuffer, const int4* idxBuffer, int nTriangles ) +{ + { + float maxDx2 = -1.f; + int maxIdx = -1; + for(int i=0; i maxDx2 ) + { + maxDx2 = dx2; + maxIdx = idx.s[j]; + } + } + } + ADLASSERT( maxIdx != -1 ); + m_scale = sqrtf( maxDx2 ); + } + + // cast ray to find intersectPlaneLineions + { + for(u32 faceIdx=0; faceIdx<6; faceIdx++) + { + for(int i=0; i 0.f ) + { + minFraction = min2( minFraction, fraction ); // todo. have to check if this is the min to replace normal? + float4 ab = vtxBuffer[idxBuffer[itri].y]-vtxBuffer[idxBuffer[itri].x]; + float4 ac = vtxBuffer[idxBuffer[itri].z]-vtxBuffer[idxBuffer[itri].x]; + minNormal = cross3( ab, ac ); + minBCrd = bCrd; + } + } + + if( minFraction == FLT_MAX ) + minFraction = 0.f; + + { + u8 quantizedHeight = (u8)(minFraction*255.f); + sample( (Face)faceIdx, i,j ) = quantizedHeight; + sampleNormal( (Face)faceIdx, i,j ) = normalize3(minNormal); + float minValue = 3.f*(1.f/3.f)*(1.f/3.f); + sampleNormal( (Face)faceIdx, i,j ).w = (dot3F4( minBCrd, minBCrd ) - minValue )/(1.f-minValue); + } + } + } + } + + calcSamplePoints( m_samplePoints ); + + // calc support height using m_samplePoints + { + for(u32 faceIdx=0; faceIdx<6; faceIdx++) for(int i=0; i maxHeight ) maxHeight = h; + } + + { + u8 quantizedHeight = min2((u8)(maxHeight*255.f)+1, 255); + sampleSupport( (Face)faceIdx, i, j ) = quantizedHeight; + } + } + } + + m_aabb.setEmpty(); + for(int i=0; i 0.f ) + { + if( fraction < minFraction ) + { + minFraction = fraction; + minNormal = iEqn; + } + } + } + + ADLASSERT( minFraction != FLT_MAX ); + + minNormal.w = minFraction; + sampleNormal( (Face)faceIdx, i, j ) = minNormal; + } + } + } + + { + m_scale = -FLT_MAX; + for(u32 faceIdx=0; faceIdx<6; faceIdx++) + { + for(int i=0; i1.f) + h=1.f; +// ADLASSERT( h <= 1.f ); + if( h > maxHeight ) maxHeight = h; + } + + { + u8 quantizedHeight = min2((u8)(maxHeight*255.f)+1, 255); + sampleSupport( (Face)faceIdx, i, j ) = quantizedHeight; + } + } + } + + for(int i=0; i<6; i++) + { + m_faceAabbs[i].setEmpty(); + for(int j=0; jm_type == ADL_SHAPE_SPHERE ) + { + SphereShape* sphere = (SphereShape*)shape; + + m_scale = sphere->m_radius; + for(u32 faceIdx=0; faceIdx<6; faceIdx++) + { + for(int i=0; im_radius ); + m_aabb.m_min = make_float4( -sphere->m_radius ); + + m_aabb.expandBy( make_float4( m_collisionMargin ) ); + + for(int i=0; i<6; i++) + { + m_faceAabbs[i].setEmpty(); + for(int j=0; jgetVertexBuffer(), s->getTriangleBuffer(), s->getNumTris() ); + } +} +#endif + +ConvexHeightField::~ConvexHeightField() +{ + +} + +float ConvexHeightField::queryDistance(const float4& p ) const +{ + const float4 majorAxes[] = {make_float4(1,0,0,0), make_float4(0,1,0,0), make_float4(0,0,1,0)}; + + if( dot3F4( p, p ) >= m_scale*m_scale ) return FLT_MAX; + + int faceIdx; + float x, y; + CubeMapUtils::calcCrd( p, faceIdx, x, y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + float height; + { + int xi = (int)(x); + int yi = (int)(y); + float dx = x-xi; + float dy = y-yi; + + { + int xip = min2((int)(HEIGHT_RES-1), xi+1); + int yip = min2((int)(HEIGHT_RES-1), yi+1); + + u8 xy = sample( (Face)faceIdx, xi, yi ); + u8 xpy = sample( (Face)faceIdx, xip, yi ); + u8 xpyp = sample( (Face)faceIdx, xip, yip ); + u8 xyp = sample( (Face)faceIdx, xi, yip ); + + height = (xy*(1.f-dx)+xpy*dx)*(1.f-dy) + (xyp*(1.f-dx)+xpyp*dx)*dy; + height = height/255.f*m_scale; + + height = length3( p ) - height; + } + } + + return height; +} + +float ConvexHeightField::querySupportHeight(const float4& p ) const +{ + const float4 majorAxes[] = {make_float4(1,0,0,0), make_float4(0,1,0,0), make_float4(0,0,1,0)}; + +// if( dot3F4( p, p ) >= m_scale*m_scale ) return FLT_MAX; + + int faceIdx; + float x, y; + CubeMapUtils::calcCrd( p, faceIdx, x, y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + float height; + { + int xi = (int)(x); + int yi = (int)(y); + float dx = x-xi; + float dy = y-yi; + + { + int xip = min2((int)(HEIGHT_RES-1), xi+1); + int yip = min2((int)(HEIGHT_RES-1), yi+1); + + u8 xy = sampleSupport( (Face)faceIdx, xi, yi ); + u8 xpy = sampleSupport( (Face)faceIdx, xip, yi ); + u8 xpyp = sampleSupport( (Face)faceIdx, xip, yip ); + u8 xyp = sampleSupport( (Face)faceIdx, xi, yip ); + + height = max2( xy, max2( xpy, max2( xpyp, xyp ) ) ); + height = height/255.f*m_scale; + } + } + + return height; +} + +float ConvexHeightField::queryW(const float4& p ) const +{ + const float4 majorAxes[] = {make_float4(1,0,0,0), make_float4(0,1,0,0), make_float4(0,0,1,0)}; + + float value; + if( dot3F4( p, p ) >= m_scale*m_scale ) return 0; + + int faceIdx; + float x, y; + CubeMapUtils::calcCrd( p, faceIdx, x, y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + { + int xi = (int)(x); + int yi = (int)(y); + + value = sampleNormal( (Face)faceIdx, xi, yi ).w; + } + return value; +} + +bool ConvexHeightField::queryDistanceWithNormal( const float4& p, float4& normalOut ) const +{ + int faceIdx; + float x, y; + CubeMapUtils::calcCrd( p, faceIdx, x, y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + { + int xi = (int)(x); + int yi = (int)(y); + + normalOut = sampleNormal( (Face)faceIdx, xi, yi ); + } + return true; +} + +void ConvexHeightField::calcSamplePoints(float4* points) const +{ + for(u32 faceIdx=0; faceIdx<6; faceIdx++) + { + for(int i=0; ir2[0])? 1:0; + idx = (r2[2]>r2[idx])? 2:idx; + majorAxis = majorAxes[idx]; + + bool isNeg = dot3F4( p, majorAxis ) < 0.f; + + faceIdxOut = (idx*2+((isNeg)? 0:1)); +//== + float4 abs = make_float4( fabs(p.x), fabs(p.y), fabs(p.z), 0.f ); + + float d; + if( idx == 0 ) + { + x = p.y; + y = p.z; + d = abs.x; + } + else if( idx == 1 ) + { + x = p.z; + y = p.x; + d = abs.y; + } + else + { + x = p.x; + y = p.y; + d = abs.z; + } + + float dInv = (d==0.f)? 0.f: (1.f/d); + x = (x*dInv+1.f)*0.5f; + y = (y*dInv+1.f)*0.5f; + } +} + +__inline +float4 CubeMapUtils::calcVector(int faceIdx, float x, float y) +{ + int dir = faceIdx/2; + float z = (faceIdx%2 == 0)? -1.f:1.f; + + x = x*2.f-1.f; + y = y*2.f-1.f; + + if( dir == 0 ) + { + return make_float4(z, x, y); + } + else if( dir == 1 ) + { + return make_float4(y,z,x); + } + else + { + return make_float4(x,y,z); + } +} + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.cpp new file mode 100644 index 000000000..762afd2cb --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.cpp @@ -0,0 +1,699 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "CustomCollisionDispatcher.h" +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" +#include "BulletCollision/CollisionDispatch/btCollisionObject.h" +#include "CustomConvexShape.h" +#include "CustomConvexPairCollision.h" +#include "LinearMath/btQuickprof.h" + + + +#ifdef CL_PLATFORM_AMD + +#include "Adl/Adl.h" +#include "Stubs/AdlMath.h" +#include "Stubs/AdlContact4.h" +#include "Stubs/AdlQuaternion.h" +#include "Stubs/ChNarrowPhase.h" + +#include "Stubs/Solver.h" + + +struct CustomDispatchData +{ + adl::DeviceCL* m_ddcl; + adl::Device* m_deviceHost; + ShapeDataType m_ShapeBuffer; + + adl::HostBuffer* m_pBufPairsCPU; + adl::Buffer* m_pBufPairsGPU; + adl::Buffer* m_pBufContactOutGPU; + adl::HostBuffer* m_pBufContactOutCPU; + adl::ChNarrowphase::Data* m_Data; + + adl::HostBuffer* m_pBufRBodiesCPU; + adl::Buffer* m_pBufRBodiesGPU; + + adl::Buffer* m_bodyInfoBufferCPU; + adl::Buffer* m_bodyInfoBufferGPU; + + adl::Solver::Data* m_solverDataGPU; + SolverData m_contactCGPU; + void* m_frictionCGPU; + + int m_numAcceleratedShapes; +}; +#endif //CL_PLATFORM_AMD + +CustomCollisionDispatcher::CustomCollisionDispatcher(btCollisionConfiguration* collisionConfiguration +#ifdef CL_PLATFORM_AMD + , cl_context context,cl_device_id device,cl_command_queue queue +#endif //CL_PLATFORM_AMD +):btCollisionDispatcher(collisionConfiguration), +m_internalData(0) +{ +#ifdef CL_PLATFORM_AMD + + if (context && queue) + { + m_internalData = new CustomDispatchData(); + memset(m_internalData,0,sizeof(CustomDispatchData)); + + adl::DeviceUtils::Config cfg; + m_internalData->m_ddcl = new adl::DeviceCL(); + m_internalData->m_ddcl->m_deviceIdx = device; + m_internalData->m_ddcl->m_context = context; + m_internalData->m_ddcl->m_commandQueue = queue; + m_internalData->m_ddcl->m_kernelManager = new adl::KernelManager; + + + m_internalData->m_deviceHost = adl::DeviceUtils::allocate( adl::TYPE_HOST, cfg ); + m_internalData->m_pBufPairsCPU = new adl::HostBuffer(m_internalData->m_deviceHost, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_pBufContactOutCPU = new adl::HostBuffer(m_internalData->m_deviceHost, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_pBufRBodiesCPU = new adl::HostBuffer(m_internalData->m_deviceHost, MAX_CONVEX_BODIES_CL); + + m_internalData->m_bodyInfoBufferCPU = new adl::Buffer(m_internalData->m_deviceHost,MAX_CONVEX_BODIES_CL); + m_internalData->m_pBufContactOutGPU = new adl::Buffer(m_internalData->m_ddcl, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_bodyInfoBufferGPU = new adl::Buffer(m_internalData->m_ddcl,MAX_CONVEX_BODIES_CL); + m_internalData->m_pBufPairsGPU = new adl::Buffer(m_internalData->m_ddcl, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_solverDataGPU = adl::Solver::allocate( m_internalData->m_ddcl, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_pBufRBodiesGPU = new adl::Buffer(m_internalData->m_ddcl, MAX_CONVEX_BODIES_CL); + m_internalData->m_Data = adl::ChNarrowphase::allocate(m_internalData->m_ddcl); + m_internalData->m_ShapeBuffer = adl::ChNarrowphase::allocateShapeBuffer(m_internalData->m_ddcl, MAX_CONVEX_SHAPES_CL); + m_internalData->m_numAcceleratedShapes = 0; + + m_internalData->m_contactCGPU = adl::Solver::allocateConstraint4( m_internalData->m_ddcl, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_frictionCGPU = adl::Solver::allocateFrictionConstraint( m_internalData->m_ddcl, MAX_BROADPHASE_COLLISION_CL); + + } + + + +#endif //CL_PLATFORM_AMD +} + +CustomCollisionDispatcher::~CustomCollisionDispatcher(void) +{ +#ifdef CL_PLATFORM_AMD + if (m_internalData) + { + delete m_internalData->m_pBufPairsCPU; + delete m_internalData->m_pBufPairsGPU; + delete m_internalData->m_pBufContactOutGPU; + delete m_internalData->m_pBufContactOutCPU; + + adl::Solver::deallocateConstraint4( m_internalData->m_contactCGPU ); + adl::Solver::deallocateFrictionConstraint( m_internalData->m_frictionCGPU ); + + + adl::Solver::deallocate(m_internalData->m_solverDataGPU); + + adl::DeviceUtils::deallocate(m_internalData->m_deviceHost); + delete m_internalData->m_ddcl; + delete m_internalData; + } + +#endif //CL_PLATFORM_AMD + +} + + +#ifdef CL_PLATFORM_AMD +#include "BulletDynamics/Dynamics/btRigidBody.h" + +RigidBodyBase::Shape CreateBodyInfo(const btCollisionObject& colObj) +{ + RigidBodyBase::Shape shape; + const btRigidBody* bulletBody = btRigidBody::upcast(&colObj); + if( colObj.isStaticOrKinematicObject() || !bulletBody) + { + + //body.m_quat = qtGetIdentity(); + //body.m_invMass = 0.f; + shape.m_initInvInertia = mtZero(); + shape.m_invInertia = mtZero(); + } + else + { + + btVector3 invLocalInertia = bulletBody->getInvInertiaDiagLocal(); + shape.m_initInvInertia = mtZero(); + shape.m_initInvInertia.m_row[0].x = invLocalInertia.x(); + shape.m_initInvInertia.m_row[1].y = invLocalInertia.y(); + shape.m_initInvInertia.m_row[2].z = invLocalInertia.z(); + + btQuaternion q = colObj.getWorldTransform().getRotation(); + Quaternion qBody; + qBody.x = q.getX(); + qBody.y = q.getY(); + qBody.z = q.getZ(); + qBody.w = q.getW(); + + Matrix3x3 m = qtGetRotationMatrix( qBody); + Matrix3x3 mT = mtTranspose( m ); + shape.m_invInertia = mtMul( mtMul( m, shape.m_initInvInertia ), mT ); + //bulletBody->getInvInertiaTensorWorld(); + + + + + // shape.m_initInvInertia = mtInvert( localInertia ); + } + return shape; +} + +RigidBodyBase::Body CreateRBodyCL(const btCollisionObject& colObj, int shapeIdx) +{ + RigidBodyBase::Body bodyCL; + + + // position + const btVector3& p = colObj.getWorldTransform().getOrigin(); + bodyCL.m_pos.x = p.getX(); + bodyCL.m_pos.y = p.getY(); + bodyCL.m_pos.z = p.getZ(); + bodyCL.m_pos.w = 0.0f; + + // quaternion + btQuaternion q = colObj.getWorldTransform().getRotation(); + bodyCL.m_quat.x = q.getX(); + bodyCL.m_quat.y = q.getY(); + bodyCL.m_quat.z = q.getZ(); + bodyCL.m_quat.w = q.getW(); + + const btRigidBody* bulletBody = btRigidBody::upcast(&colObj); + if( colObj.isStaticOrKinematicObject() || !bulletBody) + { + // linear velocity + bodyCL.m_linVel = make_float4(0.0f, 0.0f, 0.0f); + + // angular velocity + bodyCL.m_angVel = make_float4(0.0f, 0.0f, 0.0f); + bodyCL.m_invMass = 0.f; + } else + { + // linear velocity + const btVector3& lv = bulletBody->getLinearVelocity(); + const btVector3& av = bulletBody->getAngularVelocity(); + + bodyCL.m_linVel = make_float4(lv.x(),lv.y(),lv.z(),0.0f); + // angular velocity + bodyCL.m_angVel = make_float4(av.x(),av.y(),av.z(),0.0f); + bodyCL.m_invMass = bulletBody->getInvMass(); + } + // shape index + bodyCL.m_shapeIdx = shapeIdx; + + + // restituition coefficient + bodyCL.m_restituitionCoeff = colObj.getRestitution(); + + // friction coefficient + bodyCL.m_frictionCoeff = colObj.getFriction(); + + return bodyCL; +} +#endif //CL_PLATFORM_AMD + +void CustomCollisionDispatcher::dispatchAllCollisionPairs(btOverlappingPairCache* pairCache,const btDispatcherInfo& dispatchInfo,btDispatcher* dispatcher) +{ + BT_PROFILE("CustomCollisionDispatcher::dispatchAllCollisionPairs"); + { + btBroadphasePairArray& overlappingPairArray = pairCache->getOverlappingPairArray(); + bool bGPU = (m_internalData != 0); +#ifdef CL_PLATFORM_AMD + if ( !bGPU ) +#endif //CL_PLATFORM_AMD + { + BT_PROFILE("btCollisionDispatcher::dispatchAllCollisionPairs"); + btCollisionDispatcher::dispatchAllCollisionPairs(pairCache,dispatchInfo,dispatcher); + } +#ifdef CL_PLATFORM_AMD + + else + { + { + BT_PROFILE("refreshContactPoints"); + //---------------------------------------------------------------- + // GPU version of convex heightmap narrowphase collision detection + //---------------------------------------------------------------- + for ( int i = 0; i < getNumManifolds(); i++ ) + { + btPersistentManifold* manifold = getManifoldByIndexInternal(i); + + + btCollisionObject* body0 = (btCollisionObject*)manifold->getBody0(); + btCollisionObject* body1 = (btCollisionObject*)manifold->getBody1(); + + manifold->refreshContactPoints(body0->getWorldTransform(),body1->getWorldTransform()); + } + } + + // OpenCL + int nColPairsFromBP = overlappingPairArray.size(); + btAssert(MAX_BROADPHASE_COLLISION_CL >= nColPairsFromBP); + + int maxBodyIndex = -1; + + { + BT_PROFILE("CreateRBodyCL and GPU pairs"); + for ( int i=0; im_pProxy0->m_clientObject; + btCollisionObject* colObj1 = (btCollisionObject*)pair->m_pProxy1->m_clientObject; + + int bodyIndex0 = colObj0->getCompanionId(); + int bodyIndex1 = colObj1->getCompanionId(); + + //keep a one-to-one mapping between Bullet and Adl broadphase pairs + (*m_internalData->m_pBufPairsCPU)[i].x = bodyIndex0; + (*m_internalData->m_pBufPairsCPU)[i].y = bodyIndex1; + + if (bodyIndex0>=0 && bodyIndex1>=0) + { + //create companion shapes (if necessary) + + btAssert(colObj0->getCollisionShape()->getShapeType() == CUSTOM_POLYHEDRAL_SHAPE_TYPE); + btAssert(colObj1->getCollisionShape()->getShapeType() == CUSTOM_POLYHEDRAL_SHAPE_TYPE); + + CustomConvexShape* convexShape0 = (CustomConvexShape*)colObj0->getCollisionShape(); + CustomConvexShape* convexShape1 = (CustomConvexShape*)colObj1->getCollisionShape(); + + if (convexShape0->m_acceleratedCompanionShapeIndex<0) + { + convexShape0->m_acceleratedCompanionShapeIndex = m_internalData->m_numAcceleratedShapes; + adl::ChNarrowphase::setShape(m_internalData->m_ShapeBuffer, convexShape0->m_ConvexHeightField, convexShape0->m_acceleratedCompanionShapeIndex, 0.0f); + m_internalData->m_numAcceleratedShapes++; + } + if (convexShape1->m_acceleratedCompanionShapeIndex<0) + { + convexShape1->m_acceleratedCompanionShapeIndex = m_internalData->m_numAcceleratedShapes; + adl::ChNarrowphase::setShape(m_internalData->m_ShapeBuffer, convexShape1->m_ConvexHeightField, convexShape1->m_acceleratedCompanionShapeIndex, 0.0f); + m_internalData->m_numAcceleratedShapes++; + } + + btAssert(m_internalData->m_numAcceleratedShapesmaxBodyIndex) + maxBodyIndex = bodyIndex0; + if (bodyIndex1>maxBodyIndex) + maxBodyIndex = bodyIndex1; + + btAssert(maxBodyIndex=MAX_CONVEX_BODIES_CL) + { + printf("error: maxBodyIndex(%d)>MAX_CONVEX_BODIES_CL(%d)\n",maxBodyIndex,MAX_CONVEX_BODIES_CL); + } + + (*m_internalData->m_pBufRBodiesCPU)[bodyIndex0] = CreateRBodyCL(*colObj0, convexShape0->m_acceleratedCompanionShapeIndex); + m_internalData->m_bodyInfoBufferCPU->m_ptr[bodyIndex0] = CreateBodyInfo(*colObj0); + (*m_internalData->m_pBufRBodiesCPU)[bodyIndex1] = CreateRBodyCL(*colObj1, convexShape0->m_acceleratedCompanionShapeIndex); + m_internalData->m_bodyInfoBufferCPU->m_ptr[bodyIndex1] = CreateBodyInfo(*colObj1); + } else + { + //TODO: dispatch using default dispatcher + btAssert(0); + } + } + } + + + if (maxBodyIndex>=0) + { + + int numOfConvexRBodies = maxBodyIndex+1; + + + + adl::ChNarrowphaseBase::Config cfgNP; + cfgNP.m_collisionMargin = 0.01f; + int nContactOut = 0; + + { + BT_PROFILE("ChNarrowphase::execute"); + adl::ChNarrowphase::execute(m_internalData->m_Data, m_internalData->m_pBufPairsGPU, nColPairsFromBP, m_internalData->m_pBufRBodiesGPU, m_internalData->m_ShapeBuffer, m_internalData->m_pBufContactOutGPU, nContactOut, cfgNP); + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + } + + + bool useCpu = false;//true; + bool useSolver = true;//true;//false; + + if (useSolver) + { + float dt=1./60.; + adl::SolverBase::ConstraintCfg csCfg( dt ); + csCfg.m_enableParallelSolve = true; + csCfg.m_averageExtent = 0.2f;//@TODO m_averageObjExtent; + csCfg.m_staticIdx = -1;//numOfConvexRBodies-1;//m_nBodies-1; + + + if (useCpu) + { + + { + BT_PROFILE("read m_pBufContactOutGPU"); + m_internalData->m_pBufContactOutGPU->read(m_internalData->m_pBufContactOutCPU->m_ptr, nContactOut);//MAX_BROADPHASE_COLLISION_CL); + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + } + + BT_PROFILE("CPU stuff"); + adl::Solver::Data* solverData = adl::Solver::allocate( m_internalData->m_deviceHost, nContactOut); + + SolverData contactCPU = adl::Solver::allocateConstraint4( + m_internalData->m_deviceHost, + numOfConvexRBodies*MAX_PAIRS_PER_BODY_CL ); + + void* frictionCPU = adl::Solver::allocateFrictionConstraint( + m_internalData->m_deviceHost, + numOfConvexRBodies*MAX_PAIRS_PER_BODY_CL ); + + //write body with current linear/angluar velocities to GPU + m_internalData->m_bodyInfoBufferGPU->write(m_internalData->m_bodyInfoBufferCPU->m_ptr,numOfConvexRBodies); + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + + + if (nContactOut) + { + reorderConvertToConstraints2( + solverData, + m_internalData->m_pBufRBodiesCPU, + m_internalData->m_bodyInfoBufferCPU, + m_internalData->m_pBufContactOutCPU, + contactCPU, + frictionCPU, + nContactOut, + csCfg ); + + bool forceGPU = true; + + if (forceGPU) + { + + SolverData contactCPUcopy = adl::Solver::allocateConstraint4( + m_internalData->m_deviceHost, + numOfConvexRBodies*MAX_PAIRS_PER_BODY_CL ); + + adl::Solver::reorderConvertToConstraints( + m_internalData->m_solverDataGPU, + m_internalData->m_pBufRBodiesGPU, + m_internalData->m_bodyInfoBufferGPU, + m_internalData->m_pBufContactOutGPU, + m_internalData->m_contactCGPU, + m_internalData->m_frictionCGPU, + nContactOut, + csCfg ); + + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + m_internalData->m_contactCGPU->read(contactCPUcopy->m_ptr,nContactOut); + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + + + //m_internalData->m_contactCGPU->write(contactCPU->m_ptr,nContactOut); + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + m_internalData->m_solverDataGPU->m_nIterations = 4; + + adl::Solver::solveContactConstraint( m_internalData->m_solverDataGPU, + m_internalData->m_pBufRBodiesGPU, + m_internalData->m_bodyInfoBufferGPU, + m_internalData->m_contactCGPU, + 0, + nContactOut ); + + adl::DeviceUtils::waitForCompletion( m_internalData->m_ddcl ); + + //read body updated linear/angular velocities back to CPU + m_internalData->m_pBufRBodiesGPU->read( + m_internalData->m_pBufRBodiesCPU->m_ptr,numOfConvexRBodies); + adl::DeviceUtils::waitForCompletion( m_internalData->m_ddcl ); + + } else + { + solverData->m_nIterations = 4; + adl::Solver::solveContactConstraint( solverData, + m_internalData->m_pBufRBodiesCPU, + m_internalData->m_bodyInfoBufferCPU, + contactCPU, + 0, + nContactOut ); + } + + + + } + + adl::Solver::deallocateConstraint4( contactCPU ); + adl::Solver::deallocateFrictionConstraint( frictionCPU ); + adl::Solver::deallocate( solverData ); + + + + } + else + { + + { + BT_PROFILE("rigid body data to GPU buffer"); + // Transfer rigid body data from CPU buffer to GPU buffer + m_internalData->m_pBufRBodiesGPU->write(m_internalData->m_pBufRBodiesCPU->m_ptr, numOfConvexRBodies); + m_internalData->m_pBufPairsGPU->write(m_internalData->m_pBufPairsCPU->m_ptr, MAX_BROADPHASE_COLLISION_CL); + //write body with current linear/angluar velocities to GPU + m_internalData->m_bodyInfoBufferGPU->write(m_internalData->m_bodyInfoBufferCPU->m_ptr,numOfConvexRBodies); + adl::DeviceUtils::waitForCompletion(m_internalData->m_ddcl); + } + { + BT_PROFILE("GPU reorderConvertToConstraints"); + adl::Solver::reorderConvertToConstraints( + m_internalData->m_solverDataGPU, + m_internalData->m_pBufRBodiesGPU, + m_internalData->m_bodyInfoBufferGPU, + m_internalData->m_pBufContactOutGPU, + m_internalData->m_contactCGPU, + m_internalData->m_frictionCGPU, + nContactOut, + csCfg ); + } + + { + BT_PROFILE("GPU solveContactConstraint"); + m_internalData->m_solverDataGPU->m_nIterations = 4; + + adl::Solver::solveContactConstraint( m_internalData->m_solverDataGPU, + m_internalData->m_pBufRBodiesGPU, + m_internalData->m_bodyInfoBufferGPU, + m_internalData->m_contactCGPU, + 0, + nContactOut ); + + adl::DeviceUtils::waitForCompletion( m_internalData->m_ddcl ); + } + { + BT_PROFILE("read body velocities back to CPU"); + //read body updated linear/angular velocities back to CPU + m_internalData->m_pBufRBodiesGPU->read( + m_internalData->m_pBufRBodiesCPU->m_ptr,numOfConvexRBodies); + adl::DeviceUtils::waitForCompletion( m_internalData->m_ddcl ); + } + + + } + +#if 0 + if( !m_useGPUPipeline ) + { // CPU + BT_PROFILE("CPU solve"); + { + BT_PROFILE("CPU reorderConvertToConstraints"); + + SOLVER_CLASS::reorderConvertToConstraints( solver, m_bodyBuffer, m_bodyInfoBufferCPU, (Buffer*)m_contactBuffer, + contactC, frictionC, m_numContacts, csCfg ); + } + { + BT_PROFILE("CPU solveContactConstraint"); + + solver->m_nIterations = 4; + SOLVER_CLASS::solveContactConstraint( solver, m_bodyBuffer, m_bodyInfoBufferCPU, contactC, 0, m_numContacts ); + } + } + else + { + BT_PROFILE("GPU solve"); + { // GPU using host buffers + { + BT_PROFILE("GPU reorderConvertToConstraints"); + + Solver::reorderConvertToConstraints( m_solver, m_bodyBuffer, m_bodyInfoBufferCPU, (Buffer*)m_contactBuffer, + contactC, frictionC, m_numContacts, csCfg ); + } + timerEnd(); + + timerStart(0); + //for(int iter=0; iter<4; iter++) + { + BT_PROFILE("GPU solveContactConstraint"); + + Solver::solveContactConstraint( m_solver, m_bodyBuffer, m_bodyInfoBufferCPU, contactC, frictionC, m_numContacts ); + } + DeviceUtils::waitForCompletion( m_device ); + } + } + timerEnd(); +#endif + + + } + + //if we ran the solver, it will overwrite the batchIdx so we cannot write back the results + //try to make it work by writing velocity back to rigid body + + if (useSolver) + { + + BT_PROFILE("writing velocity back to btRigidBody"); + + for ( int i=0; im_pProxy0->m_clientObject; + btCollisionObject* colObj1 = (btCollisionObject*)pair->m_pProxy1->m_clientObject; + + int bodyIndex0 = colObj0->getCompanionId(); + int bodyIndex1 = colObj1->getCompanionId(); + + RigidBodyBase::Body* bA = &m_internalData->m_pBufRBodiesCPU->m_ptr[bodyIndex0]; + RigidBodyBase::Body* bB = &m_internalData->m_pBufRBodiesCPU->m_ptr[bodyIndex1]; + btRigidBody* bodyA = btRigidBody::upcast(colObj0); + if (bodyA && !bodyA->isStaticOrKinematicObject()) + { + bodyA->setLinearVelocity(btVector3( + bA->m_linVel.x, + bA->m_linVel.y, + bA->m_linVel.z)); + + bodyA->setAngularVelocity(btVector3( + bA->m_angVel.x, + bA->m_angVel.y, + bA->m_angVel.z)); + } + btRigidBody* bodyB = btRigidBody::upcast(colObj1); + if (bodyB && !bodyB->isStaticOrKinematicObject()) + { + bodyB->setLinearVelocity(btVector3( + bB->m_linVel.x, + bB->m_linVel.y, + bB->m_linVel.z)); + bodyB->setAngularVelocity(btVector3( + bB->m_angVel.x, + bB->m_angVel.y, + bB->m_angVel.z)); + + } + + + + + } + } else + { + BT_PROFILE("copy Contact4 to btPersistentManifold"); + // Now we got the narrowphase info from GPU and need to update rigid bodies with the info and go back to the original pipeline in Bullet physics. + for ( int i = 0; i < nContactOut; i++ ) + { + Contact4 contact = (*m_internalData->m_pBufContactOutCPU)[i]; + + int idxBodyA = contact.m_bodyAPtr; + int idxBodyB = contact.m_bodyBPtr; + + btAssert(contact.m_batchIdx>=0); + btAssert(contact.m_batchIdxm_pProxy0->m_clientObject; + btCollisionObject* colObj1 = (btCollisionObject*)pair->m_pProxy1->m_clientObject; + + if (!pair->m_algorithm) + { + pair->m_algorithm = findAlgorithm(colObj0,colObj1,0); + } + + btManifoldResult contactPointResult(colObj0, colObj1); + + + CustomConvexConvexPairCollision* pairAlgo = (CustomConvexConvexPairCollision*) pair->m_algorithm; + + if (!pairAlgo->getManifoldPtr()) + { + pairAlgo->createManifoldPtr(colObj0,colObj1,dispatchInfo); + } + + contactPointResult.setPersistentManifold(pairAlgo->getManifoldPtr()); + + contactPointResult.getPersistentManifold()->refreshContactPoints(colObj0->getWorldTransform(),colObj1->getWorldTransform()); + + const btTransform& transA = colObj0->getWorldTransform(); + const btTransform& transB = colObj1->getWorldTransform(); + + int numPoints = contact.getNPoints(); + + for ( int k=0; k < numPoints; k++ ) + { + btVector3 normalOnBInWorld( + contact.m_worldNormal.x, + contact.m_worldNormal.y, + contact.m_worldNormal.z); + btVector3 pointInWorldOnB( + contact.m_worldPos[k].x, + contact.m_worldPos[k].y, + contact.m_worldPos[k].z); + + btScalar depth = contact.m_worldPos[k].w; + + if (depth<0) + { + const btVector3 deltaC = transB.getOrigin() - transA.getOrigin(); + + normalOnBInWorld.normalize(); + + if((deltaC.dot(normalOnBInWorld))>0.0f) + { + normalOnBInWorld= -normalOnBInWorld; + + contactPointResult.addContactPoint(normalOnBInWorld, pointInWorldOnB, depth); + } + else + { + contactPointResult.addContactPoint(normalOnBInWorld, pointInWorldOnB-normalOnBInWorld*depth, depth); + } + } + } + } + } + } + } +#endif //CL_PLATFORM_AMD + } + +} + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.h new file mode 100644 index 000000000..315a4ba48 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomCollisionDispatcher.h @@ -0,0 +1,70 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef CUSTOM_COLLISION_DISPATCHER_H +#define CUSTOM_COLLISION_DISPATCHER_H + + +#include "BulletCollision/CollisionDispatch/btCollisionDispatcher.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" + + +#define MAX_CONVEX_BODIES_CL 64*1024 +#define MAX_PAIRS_PER_BODY_CL 32 +#define MAX_CONVEX_SHAPES_CL 8192 +#define MAX_BROADPHASE_COLLISION_CL (MAX_CONVEX_BODIES_CL*MAX_PAIRS_PER_BODY_CL) + + + +struct CustomDispatchData; + +#ifdef CL_PLATFORM_AMD +#ifdef __APPLE__ + #ifdef USE_MINICL + #include + #else + #include + #endif +#else //__APPLE__ + #ifdef USE_MINICL + #include + #else + #include + #endif +#endif //__APPLE__ +#endif + +class CustomCollisionDispatcher : public btCollisionDispatcher +{ +public: + CustomCollisionDispatcher (btCollisionConfiguration* collisionConfiguration +#ifdef CL_PLATFORM_AMD + , cl_context context = NULL,cl_device_id device = NULL,cl_command_queue queue = NULL +#endif //CL_PLATFORM_AMD + ); + + virtual ~CustomCollisionDispatcher(void); + +protected: + + CustomDispatchData* m_internalData; + + btBroadphasePair* GetPair(btBroadphasePairArray& pairArray, int idxBodyA, int idxBodyB); + +public: + virtual void dispatchAllCollisionPairs(btOverlappingPairCache* pairCache,const btDispatcherInfo& dispatchInfo,btDispatcher* dispatcher); +}; + +#endif //CUSTOM_COLLISION_DISPATCHER_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.cpp new file mode 100644 index 000000000..d0fd32c31 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.cpp @@ -0,0 +1,409 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "CustomConvexPairCollision.h" +#include "ConvexHeightFieldShape.h" +#include "CustomConvexShape.h" +#include "BulletCollision/CollisionDispatch/btCollisionObject.h" +#include "Stubs/AdlContact4.h" +#include "Stubs/AdlTransform.h" + + +CustomConvexConvexPairCollision::CustomConvexConvexPairCollision(btPersistentManifold* mf,const btCollisionAlgorithmConstructionInfo& ci,btCollisionObject* body0,btCollisionObject* body1, btSimplexSolverInterface* simplexSolver, btConvexPenetrationDepthSolver* pdSolver, int numPerturbationIterations, int minimumPointsPerturbationThreshold) +:btConvexConvexAlgorithm(mf,ci,body0,body1,simplexSolver,pdSolver,numPerturbationIterations, minimumPointsPerturbationThreshold) +{ + +} + +CustomConvexConvexPairCollision::~CustomConvexConvexPairCollision() +{ + +} + + +#include + +template +T atomAdd(const T* ptr, int value) +{ + return (T)InterlockedExchangeAdd((LONG*)ptr, value); +} + + + +#define PARALLEL_SUM(v, n) for(int j=1; j v[i+offset].y)? v[i]: v[i+offset]; } +#define REDUCE_MIN(v, n) {int i=0;\ + for(int offset=0; offset a[ie].x )? a[0].x: a[ie].x; + a[0].y = (a[0].y > a[ie].y )? a[0].y: a[ie].y; + a[0].z = (a[0].z > a[ie].z )? a[0].z: a[ie].z; + a[0].w = (a[0].w > a[ie].w )? a[0].w: a[ie].w; + } + + idx[0] = (int)a[0].x & 0xff; + idx[1] = (int)a[0].y & 0xff; + idx[2] = (int)a[0].z & 0xff; + idx[3] = (int)a[0].w & 0xff; + } + } + + { + float2 h[64]; + PARALLEL_DO( h[ie] = make_float2((float)ie, p[ie].w), nPoints ); + REDUCE_MIN( h, nPoints ); + max00 = h[0]; + } + } + + contactIdx[0] = idx[0]; + contactIdx[1] = idx[1]; + contactIdx[2] = idx[2]; + contactIdx[3] = idx[3]; + +// if( max00.y < 0.0f ) +// contactIdx[0] = (int)max00.x; + + std::sort( contactIdx, contactIdx+4 ); + + return 4; + } +} + +#undef PARALLEL_SUM +#undef PARALLEL_DO +#undef REDUCE_MAX +#undef REDUCE_MIX + +int collideStraight(const ConvexHeightField* shapeA,const ConvexHeightField* shapeB, + const float4& bodyApos, Quaternion& bodyAquat,const float4& bodyBpos,const Quaternion& bodyBquat, + ContactPoint4* contactsOut, int& numContacts, int contactCapacity, + float collisionMargin ) +{ +// Stopwatch sw; + + Transform trA; + trA = trSetTransform(bodyApos,bodyAquat); + Transform trB; + trB = trSetTransform(bodyBpos, bodyBquat); + + Transform B2A; + { + Transform invTrA = trInvert( trA ); + B2A = trMul( invTrA, trB ); + } + + int nContacts = 0; + { // testB against A + float4 p[ConvexHeightField::HEIGHT_RES*ConvexHeightField::HEIGHT_RES*6]; + int nHits = 0; + + const float4* pInB = shapeB->getSamplePoints(); + + float4 baInB = qtInvRotate( bodyBquat, bodyApos - bodyBpos ); + if( shapeA->m_type == CollisionShape::SHAPE_HEIGHT_FIELD ) + baInB = make_float4(0,0,0,0); + +// sw.start(); + for(int iface=0; iface<6; iface++) + { + Aabb aabb = shapeB->m_faceAabbs[iface]; + + aabb.transform( B2A.m_translation, B2A.m_rotation ); + + if( !shapeA->m_aabb.overlaps( aabb ) ) continue; + + for(int ip=0; ipm_aabb.overlaps( pInA ) ) + { +// Stopwatch sw1; +// sw1.start(); + float dist = shapeA->queryDistance( pInA ); +// sw1.stop(); +// m_times[TIME_SAMPLE] += sw1.getMs(); + + if( dist < collisionMargin ) + { + p[nHits] = make_float4(pInA.x, pInA.y, pInA.z, dist); + nHits++; + } + } + } + } +// sw.stop(); +// m_times[TIME_TEST] += sw.getMs(); + +// sw.start(); + if( nHits ) + { + float4 ab = bodyBpos - bodyApos; + ab = qtInvRotate( bodyAquat, ab ); + if( shapeA->m_type == CollisionShape::SHAPE_HEIGHT_FIELD ) + { + //todo. sample normal from height field but just fake here + ab = make_float4(0,1,0,0); + } + + int cIdx[4]; + float4 center; + + nContacts = extractManifold( p, nHits, ab, center, cIdx ); + + float4 contactNormal; + { + shapeA->queryDistanceWithNormal( center, contactNormal ); + contactNormal = normalize3( contactNormal ); + +// u32 cmp = u8vCompress( contactNormal ); +// contactNormal = make_float4( u8vGetX(cmp), u8vGetY(cmp), u8vGetZ(cmp), 0 ); + } + + int writeIdx = atomAdd( &numContacts, 1 ); + if( writeIdx+1 < contactCapacity ) + { + ContactPoint4& c = contactsOut[writeIdx]; + nContacts = min2( nContacts, 4 ); + for(int i=0; igetNewManifold(body0,body1); + m_ownManifold = true; +} + + +void CustomConvexConvexPairCollision::processCollision (btCollisionObject* body0,btCollisionObject* body1,const btDispatcherInfo& dispatchInfo,btManifoldResult* resultOut) +{ +#if 1 + if (!m_manifoldPtr) + { + //swapped? + m_manifoldPtr = m_dispatcher->getNewManifold(body0,body1); + m_ownManifold = true; + } + resultOut->setPersistentManifold(m_manifoldPtr); + + + CustomConvexShape* convex0 = (CustomConvexShape*)body0->getCollisionShape(); + CustomConvexShape* convex1 = (CustomConvexShape*)body1->getCollisionShape(); + + + float4 bodyApos; + float4 bodyBpos; + Quaternion bodyAquat; + Quaternion bodyBquat; + + const btTransform& transA = body0->getWorldTransform(); + const btTransform& transB = body1->getWorldTransform(); + + const btVector3& pA = body0->getWorldTransform().getOrigin(); + const btVector3& pB = body1->getWorldTransform().getOrigin(); + + btQuaternion qA = body0->getWorldTransform().getRotation(); + btQuaternion qB = body1->getWorldTransform().getRotation(); + + bodyApos.x = pA.getX(); + bodyApos.y = pA.getY(); + bodyApos.z = pA.getZ(); + bodyApos.w = 0.f; + + bodyBpos.x = pB.getX(); + bodyBpos.y = pB.getY(); + bodyBpos.z = pB.getZ(); + bodyBpos.w = 0.f; + + bodyAquat.x = qA.getX(); + bodyAquat.y = qA.getY(); + bodyAquat.z = qA.getZ(); + bodyAquat.w = qA.getW(); + + bodyBquat.x = qB.getX(); + bodyBquat.y = qB.getY(); + bodyBquat.z = qB.getZ(); + bodyBquat.w = qB.getW(); + + +#define CAPACITY_CONTACTS 4 + + ContactPoint4 contactsOut[CAPACITY_CONTACTS]; + int freeContactIndex = 0; + int contactCapacity = CAPACITY_CONTACTS; + float collisionMargin = 0.001f; + + m_manifoldPtr->refreshContactPoints(body0->getWorldTransform(),body1->getWorldTransform()); + + collideStraight(convex0->m_ConvexHeightField,convex1->m_ConvexHeightField, + bodyApos, bodyAquat,bodyBpos,bodyBquat, + contactsOut, freeContactIndex, contactCapacity, + collisionMargin ); + collideStraight(convex1->m_ConvexHeightField,convex0->m_ConvexHeightField, + bodyBpos, bodyBquat,bodyApos,bodyAquat, + contactsOut, freeContactIndex, contactCapacity, + collisionMargin ); + + //copy points into manifold + //refresh manifold + + btAssert(freeContactIndex<3); + for (int j=0;j0.0f) + { + normalOnBInWorld= -normalOnBInWorld; + } + normalOnBInWorld.normalize(); + if (j) + { + resultOut->addContactPoint(normalOnBInWorld, pointInWorldOnB, depth); + } else + { + resultOut->addContactPoint(normalOnBInWorld, pointInWorldOnB-normalOnBInWorld*depth, depth); + } + } + } + } +#else + btConvexConvexAlgorithm::processCollision(body0,body1,dispatchInfo,resultOut); +#endif +} + + + +CustomConvexConvexPairCollision::CreateFunc::CreateFunc(btSimplexSolverInterface* simplexSolver, btConvexPenetrationDepthSolver* pdSolver) +:btConvexConvexAlgorithm::CreateFunc(simplexSolver,pdSolver) +{ +} + +CustomConvexConvexPairCollision::CreateFunc::~CreateFunc() +{ + +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.h new file mode 100644 index 000000000..bd3a085ca --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexPairCollision.h @@ -0,0 +1,56 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef CUSTOM_CONVEX_CONVEX_PAIR_COLLISION_H +#define CUSTOM_CONVEX_CONVEX_PAIR_COLLISION_H + + +#include "BulletCollision/CollisionDispatch/btConvexConvexAlgorithm.h" + +class CustomConvexConvexPairCollision : public btConvexConvexAlgorithm +{ + public: + + CustomConvexConvexPairCollision(btPersistentManifold* mf,const btCollisionAlgorithmConstructionInfo& ci,btCollisionObject* body0,btCollisionObject* body1, btSimplexSolverInterface* simplexSolver, btConvexPenetrationDepthSolver* pdSolver, int numPerturbationIterations, int minimumPointsPerturbationThreshold); + virtual ~CustomConvexConvexPairCollision(); + + virtual void processCollision (btCollisionObject* body0,btCollisionObject* body1,const btDispatcherInfo& dispatchInfo,btManifoldResult* resultOut); + + btPersistentManifold* getManifoldPtr() + { + return m_manifoldPtr; + } + + void createManifoldPtr(btCollisionObject* body0,btCollisionObject* body1,const btDispatcherInfo& dispatchInfo); + + struct CreateFunc :public btConvexConvexAlgorithm::CreateFunc + { + + CreateFunc(btSimplexSolverInterface* simplexSolver, btConvexPenetrationDepthSolver* pdSolver); + + virtual ~CreateFunc(); + + virtual btCollisionAlgorithm* CreateCollisionAlgorithm(btCollisionAlgorithmConstructionInfo& ci, btCollisionObject* body0,btCollisionObject* body1) + { + void* mem = ci.m_dispatcher1->allocateCollisionAlgorithm(sizeof(CustomConvexConvexPairCollision)); + return new(mem) CustomConvexConvexPairCollision(ci.m_manifold,ci,body0,body1,m_simplexSolver,m_pdSolver,m_numPerturbationIterations,m_minimumPointsPerturbationThreshold); + } + }; + + +}; + + +#endif //CUSTOM_CONVEX_CONVEX_PAIR_COLLISION_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.cpp new file mode 100644 index 000000000..de7d74dfa --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.cpp @@ -0,0 +1,45 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "CustomConvexShape.h" +#include "ConvexHeightFieldShape.h" +#include "BulletCollision/CollisionShapes/btConvexPolyhedron.h" + + +CustomConvexShape::CustomConvexShape(const btScalar* points,int numPoints, int stride) +:btConvexHullShape(points,numPoints,stride), +m_acceleratedCompanionShapeIndex(-1) +{ + m_shapeType = CUSTOM_POLYHEDRAL_SHAPE_TYPE; + + initializePolyhedralFeatures(); + int numFaces= m_polyhedron->m_faces.size(); + float4* eqn = new float4[numFaces]; + for (int i=0;im_faces[i].m_plane[0]; + eqn[i].y = m_polyhedron->m_faces[i].m_plane[1]; + eqn[i].z = m_polyhedron->m_faces[i].m_plane[2]; + eqn[i].w = m_polyhedron->m_faces[i].m_plane[3]; + } + + m_ConvexHeightField = new ConvexHeightField(eqn,numFaces); + +} + +CustomConvexShape::~CustomConvexShape() +{ + delete m_ConvexHeightField; +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.h new file mode 100644 index 000000000..a514c94e8 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/CustomConvexShape.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef CUSTOM_CONVEX_SHAPE_H +#define CUSTOM_CONVEX_SHAPE_H + +#include "BulletCollision/CollisionShapes/btConvexHullShape.h" + +class CustomConvexShape : public btConvexHullShape +{ + public: + + class ConvexHeightField* m_ConvexHeightField; + + int m_acceleratedCompanionShapeIndex; + + CustomConvexShape(const btScalar* points,int numPoints,int stride); + virtual ~CustomConvexShape(); + +}; + +#endif //CUSTOM_CONVEX_SHAPE_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.h new file mode 100644 index 000000000..0c6709020 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlAabb.h @@ -0,0 +1,230 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#ifndef AABB_H +#define AABB_H + +#include "Stubs/AdlMath.h" +#include "Stubs/AdlQuaternion.h" + +enum AdlCollisionShapeTypes +{ + ADL_SHAPE_SPHERE=2, + ADL_SHAPE_HEIGHT_FIELD, + SHAPE_CONVEX_HEIGHT_FIELD, +}; + +_MEM_CLASSALIGN16 +struct Aabb +{ + public: + _MEM_ALIGNED_ALLOCATOR16; + + __inline + void setEmpty(); + __inline + void includeVolume( const Aabb& aabb ); + __inline + void includePoint( const float4& p ); + __inline + bool overlaps( const float4& p ) const; + __inline + bool overlaps( const Aabb& aabb ) const; + __inline + float4 center() const; + __inline + int getMajorAxis() const; + __inline + float4 getExtent() const; + __inline + void expandBy( const float4& r ); + + __inline + static bool overlaps( const Aabb& a, const Aabb& b ); + + __inline + bool intersect(const float4* from, const float4* to, const float4* invRay) const; + + __inline + void transform(const float4& translation, const Quaternion& quat); + + __inline + void transform(const float4& translation, const Matrix3x3& rot); + + public: + float4 m_max; + float4 m_min; +}; + +void Aabb::setEmpty() +{ + m_max = make_float4( -FLT_MAX ); + m_min = make_float4( FLT_MAX ); +} + +void Aabb::includeVolume(const Aabb& aabb) +{ + m_max.x = max2( m_max.x, aabb.m_max.x ); + m_min.x = min2( m_min.x, aabb.m_min.x ); + + m_max.y = max2( m_max.y, aabb.m_max.y ); + m_min.y = min2( m_min.y, aabb.m_min.y ); + + m_max.z = max2( m_max.z, aabb.m_max.z ); + m_min.z = min2( m_min.z, aabb.m_min.z ); +} + +void Aabb::includePoint( const float4& p ) +{ + m_max.x = max2( m_max.x, p.x ); + m_min.x = min2( m_min.x, p.x ); + + m_max.y = max2( m_max.y, p.y ); + m_min.y = min2( m_min.y, p.y ); + + m_max.z = max2( m_max.z, p.z ); + m_min.z = min2( m_min.z, p.z ); +} + +bool Aabb::overlaps( const float4& p ) const +{ + float4 dx = m_max-p; + float4 dm = p-m_min; + + return (dx.x >= 0 && dx.y >= 0 && dx.z >= 0) + && (dm.x >= 0 && dm.y >= 0 && dm.z >= 0); +} + +bool Aabb::overlaps( const Aabb& in ) const +{ +/* + if( m_max.x < in.m_min.x || m_min.x > in.m_max.x ) return false; + if( m_max.y < in.m_min.y || m_min.y > in.m_max.y ) return false; + if( m_max.z < in.m_min.z || m_min.z > in.m_max.z ) return false; + + return true; +*/ + return overlaps( *this, in ); +} + +bool Aabb::overlaps( const Aabb& a, const Aabb& b ) +{ + if( a.m_max.x < b.m_min.x || a.m_min.x > b.m_max.x ) return false; + if( a.m_max.y < b.m_min.y || a.m_min.y > b.m_max.y ) return false; + if( a.m_max.z < b.m_min.z || a.m_min.z > b.m_max.z ) return false; + + return true; +} + +float4 Aabb::center() const +{ + return 0.5f*(m_max+m_min); +} + +int Aabb::getMajorAxis() const +{ + float4 extent = getExtent(); + + int majorAxis = 0; + if( extent.s[1] > extent.s[0] ) + majorAxis = 1; + if( extent.s[2] > extent.s[majorAxis] ) + majorAxis = 2; + + return majorAxis; +} + +float4 Aabb::getExtent() const +{ + return m_max-m_min; +} + +void Aabb::expandBy( const float4& r ) +{ + m_max += r; + m_min -= r; +} + +bool Aabb::intersect(const float4* from, const float4* to, const float4* invRay) const +{ + float4 dFar; + dFar = (m_max - *from); + dFar *= *invRay; + float4 dNear; + dNear = (m_min - *from); + dNear *= *invRay; + + float4 tFar; + tFar = max2(dFar, dNear); + float4 tNear; + tNear = min2(dFar, dNear); + + float farf[] = { tFar.x, tFar.y, tFar.z }; + + float nearf[] = { tNear.x, tNear.y, tNear.z }; + + float minFar = min2(farf[0], min2(farf[1], farf[2])); + float maxNear = max2(nearf[0], max2(nearf[1], nearf[2])); + + minFar = min2(1.0f, minFar ); + maxNear = max2(0.0f, maxNear); + + return (minFar >= maxNear); +} + +void Aabb::transform(const float4& translation, const Matrix3x3& m) +{ + float4 c = center(); + + Aabb& ans = *this; + + float4 e[] = { m.m_row[0]*m_min, m.m_row[1]*m_min, m.m_row[2]*m_min }; + float4 f[] = { m.m_row[0]*m_max, m.m_row[1]*m_max, m.m_row[2]*m_max }; + ans.m_max = ans.m_min = translation; + + { int j=0; + float4 mi = make_float4( min2( e[j].x, f[j].x ), min2( e[j].y, f[j].y ), min2( e[j].z, f[j].z ) ); + float4 ma = make_float4( max2( e[j].x, f[j].x ), max2( e[j].y, f[j].y ), max2( e[j].z, f[j].z ) ); + + ans.m_min.x += mi.x+mi.y+mi.z; + ans.m_max.x += ma.x+ma.y+ma.z; + } + + { int j=1; + float4 mi = make_float4( min2( e[j].x, f[j].x ), min2( e[j].y, f[j].y ), min2( e[j].z, f[j].z ) ); + float4 ma = make_float4( max2( e[j].x, f[j].x ), max2( e[j].y, f[j].y ), max2( e[j].z, f[j].z ) ); + + ans.m_min.y += mi.x+mi.y+mi.z; + ans.m_max.y += ma.x+ma.y+ma.z; + } + + { int j=2; + float4 mi = make_float4( min2( e[j].x, f[j].x ), min2( e[j].y, f[j].y ), min2( e[j].z, f[j].z ) ); + float4 ma = make_float4( max2( e[j].x, f[j].x ), max2( e[j].y, f[j].y ), max2( e[j].z, f[j].z ) ); + + ans.m_min.z += mi.x+mi.y+mi.z; + ans.m_max.z += ma.x+ma.y+ma.z; + } +} + +void Aabb::transform(const float4& translation, const Quaternion& quat) +{ + Matrix3x3 m = qtGetRotationMatrix( quat ); + + transform( translation, m ); +} + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlArray.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlArray.h new file mode 100644 index 000000000..e7fe5e3d1 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlArray.h @@ -0,0 +1,212 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ARRAY_H +#define ARRAY_H + +#include +#include +#include +#include + + +template +class Array +{ + public: + __inline + Array(); + __inline + Array(int size); + __inline + ~Array(); + __inline + T& operator[] (int idx); + __inline + const T& operator[] (int idx) const; + __inline + void pushBack(const T& elem); + __inline + void popBack(); + __inline + void clear(); + __inline + void setSize(int size); + __inline + int getSize() const; + __inline + T* begin(); + __inline + const T* begin() const; + __inline + int indexOf(const T& data) const; + __inline + void removeAt(int idx); + __inline + T& expandOne(); + + private: + Array(const Array& a){} + + private: + enum + { + DEFAULT_SIZE = 128, + INCREASE_SIZE = 128, + }; + + T* m_data; + int m_size; + int m_capacity; +}; + +template +Array::Array() +{ + m_size = 0; + m_capacity = DEFAULT_SIZE; +// m_data = new T[ m_capacity ]; + m_data = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + for(int i=0; i +Array::Array(int size) +{ + m_size = size; + m_capacity = size; +// m_data = new T[ m_capacity ]; + m_data = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + for(int i=0; i +Array::~Array() +{ + if( m_data ) + { +// delete [] m_data; + _aligned_free( m_data ); + m_data = NULL; + } +} + +template +T& Array::operator[](int idx) +{ + CLASSERT(idx +const T& Array::operator[](int idx) const +{ + CLASSERT(idx +void Array::pushBack(const T& elem) +{ + if( m_size == m_capacity ) + { + int oldCap = m_capacity; + m_capacity += INCREASE_SIZE; +// T* s = new T[m_capacity]; + T* s = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + memcpy( s, m_data, sizeof(T)*oldCap ); +// delete [] m_data; + _aligned_free( m_data ); + m_data = s; + } + m_data[ m_size++ ] = elem; +} + +template +void Array::popBack() +{ + CLASSERT( m_size>0 ); + m_size--; +} + +template +void Array::clear() +{ + m_size = 0; +} + +template +void Array::setSize(int size) +{ + if( size > m_capacity ) + { + int oldCap = m_capacity; + m_capacity = size; +// T* s = new T[m_capacity]; + T* s = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + for(int i=0; i +int Array::getSize() const +{ + return m_size; +} + +template +const T* Array::begin() const +{ + return m_data; +} + +template +T* Array::begin() +{ + return m_data; +} + +template +int Array::indexOf(const T& data) const +{ + for(int i=0; i +void Array::removeAt(int idx) +{ + CLASSERT(idx +T& Array::expandOne() +{ + setSize( m_size+1 ); + return m_data[ m_size-1 ]; +} + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollideUtils.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollideUtils.h new file mode 100644 index 000000000..84d658318 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollideUtils.h @@ -0,0 +1,111 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef COLLIDE_UTILS_H +#define COLLIDE_UTILS_H + +#include "Stubs/AdlMath.h" + + +class CollideUtils +{ + public: + template + static bool collide(const float4& a, const float4& b, const float4& c, const float4& p, float4& normalOut, float margin = 0.f); + + __inline + static float castRay(const float4& v0, const float4& v1, const float4& v2, + const float4& rayFrom, const float4& rayTo, float margin = 0.0f, float4* bCrdOut = NULL); + +}; + + +template +bool CollideUtils::collide(const float4& a, const float4& b, const float4& c, const float4& p, float4& normalOut, float margin) +{ + float4 ab, bc, ca; + ab = b-a; + bc = c-b; + ca = a-c; + + float4 ap, bp, cp; + ap = p-a; + bp = p-b; + cp = p-c; + + float4 n; + n = cross3(ab, -1.f*ca); + + float4 abp = cross3( ab, ap ); + float4 bcp = cross3( bc, bp ); + float4 cap = cross3( ca, cp ); + + float s0 = dot3F4(n,abp); + float s1 = dot3F4(n,bcp); + float s2 = dot3F4(n,cap); + +// if(( s0<0.f && s1<0.f && s2<0.f ) || ( s0>0.f && s1>0.f && s2>0.f )) + if(( s0-margin && s1>-margin && s2>-margin )) + { + n = normalize3( n ); + n.w = dot3F4(n,ap); + + normalOut = (FLIPSIGN)? -n : n; + return true; + } + + return false; +} + +__inline +float CollideUtils::castRay(const float4& v0, const float4& v1, const float4& v2, + const float4& rayFrom, const float4& rayTo, float margin, float4* bCrdOut) +{ + float t, v, w; + float4 ab; ab = v1 - v0; + float4 ac; ac = v2 - v0; + float4 qp; qp = rayFrom - rayTo; + float4 normal = cross3( ab, ac ); + float d = dot3F4( qp, normal ); + float odd = 1.f/d; + float4 ap; ap = rayFrom - v0; + t = dot3F4( ap, normal ); + t *= odd; +// if( t < 0.f || t > 1.f ) return -1; + + float4 e = cross3( qp, ap ); + v = dot3F4( ac, e ); + v *= odd; + if( v < -margin || v > 1.f+margin ) return -1; + w = -dot3F4( ab, e ); + w *= odd; +// if( w < 0.f || w > 1.f ) return -1; + if( w < -margin || w > 1.f+margin ) return -1; + + float u = 1.f-v-w; + if( u < -margin || u > 1.f+margin ) return -1; + + if( bCrdOut ) + { + bCrdOut->x = u; + bCrdOut->y = v; + bCrdOut->z = w; + } + return t; +} + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollisionShape.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollisionShape.h new file mode 100644 index 000000000..834c88c94 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlCollisionShape.h @@ -0,0 +1,49 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef COLLISION_SHAPE_H +#define COLLISION_SHAPE_H + +#include "Stubs/AdlMath.h" +#include "Stubs/AdlAabb.h" + + +_MEM_CLASSALIGN16 +class CollisionShape +{ + public: + _MEM_ALIGNED_ALLOCATOR16; + + enum Type + { + SHAPE_HEIGHT_FIELD, + SHAPE_CONVEX_HEIGHT_FIELD, + SHAPE_PLANE, + MAX_NUM_SHAPE_TYPES, + }; + + CollisionShape( Type type, float collisionMargin = 0.0025f ) : m_type( type ){ m_collisionMargin = collisionMargin; } + virtual ~CollisionShape(){} + virtual float queryDistance(const float4& p) const = 0; + virtual bool queryDistanceWithNormal(const float4& p, float4& normalOut) const = 0; + + public: + Type m_type; + Aabb m_aabb; + float m_collisionMargin; +}; + +#endif diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlConstraint4.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlConstraint4.h new file mode 100644 index 000000000..8f5078122 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlConstraint4.h @@ -0,0 +1,49 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ADL_CONSTRAINT4_H +#define ADL_CONSTRAINT4_H + + + +struct Constraint4 + { + _MEM_ALIGNED_ALLOCATOR16; + + float4 m_linear; + float4 m_worldPos[4]; + float4 m_center; // friction + float m_jacCoeffInv[4]; + float m_b[4]; + float m_appliedRambdaDt[4]; + + float m_fJacCoeffInv[2]; // friction + float m_fAppliedRambdaDt[2]; // friction + + u32 m_bodyA; + u32 m_bodyB; + + u32 m_batchIdx; + u32 m_paddings[1]; + + __inline + void setFrictionCoeff(float value) { m_linear.w = value; } + __inline + float getFrictionCoeff() const { return m_linear.w; } + }; + +#endif //ADL_CONSTRAINT4_H + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlContact4.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlContact4.h new file mode 100644 index 000000000..29e36ade7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlContact4.h @@ -0,0 +1,102 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ADL_CONTACT4_H +#define ADL_CONTACT4_H + +#ifdef CL_PLATFORM_AMD +#include "AdlConstraint4.h" +#include "Adl/Adl.h" + +typedef adl::Buffer* SolverData; +#else +typedef void* SolverData; +#endif + +typedef void* ShapeDataType; + + +struct Contact4 +{ + _MEM_ALIGNED_ALLOCATOR16; + + float4 m_worldPos[4]; + float4 m_worldNormal; +// float m_restituitionCoeff; +// float m_frictionCoeff; + u16 m_restituitionCoeffCmp; + u16 m_frictionCoeffCmp; + int m_batchIdx; + + u32 m_bodyAPtr; + u32 m_bodyBPtr; + + // todo. make it safer + int& getBatchIdx() { return m_batchIdx; } + float getRestituitionCoeff() const { return ((float)m_restituitionCoeffCmp/(float)0xffff); } + void setRestituitionCoeff( float c ) { ADLASSERT( c >= 0.f && c <= 1.f ); m_restituitionCoeffCmp = (u16)(c*0xffff); } + float getFrictionCoeff() const { return ((float)m_frictionCoeffCmp/(float)0xffff); } + void setFrictionCoeff( float c ) { ADLASSERT( c >= 0.f && c <= 1.f ); m_frictionCoeffCmp = (u16)(c*0xffff); } + + float& getNPoints() { return m_worldNormal.w; } + float getNPoints() const { return m_worldNormal.w; } + + float getPenetration(int idx) const { return m_worldPos[idx].w; } + + bool isInvalid() const { return ((u32)m_bodyAPtr+(u32)m_bodyBPtr) == 0; } +}; + +struct ContactPoint4 + { + float4 m_worldPos[4]; + union + { + float4 m_worldNormal; + + struct Data + { + int m_padding[3]; + float m_nPoints; // for cl + }m_data; + + }; + float m_restituitionCoeff; + float m_frictionCoeff; +// int m_nPoints; +// int m_padding0; + + void* m_bodyAPtr; + void* m_bodyBPtr; +// int m_padding1; +// int m_padding2; + + float& getNPoints() { return m_data.m_nPoints; } + float getNPoints() const { return m_data.m_nPoints; } + + float getPenetration(int idx) const { return m_worldPos[idx].w; } + +// __inline +// void load(int idx, const ContactPoint& src); +// __inline +// void store(int idx, ContactPoint& dst) const; + + bool isInvalid() const { return ((u32)m_bodyAPtr+(u32)m_bodyBPtr) == 0; } + + }; + + +#endif //ADL_CONTACT4_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlError.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlError.h new file mode 100644 index 000000000..e1f9ad8e9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlError.h @@ -0,0 +1,80 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef CL_ERROR_H +#define CL_ERROR_H + +#ifdef DX11RENDER +#include +#endif + +#ifdef _DEBUG + #include + #define CLASSERT(x) if(!(x)){__debugbreak(); } + #define ADLASSERT(x) if(!(x)){__debugbreak(); } +#else + #define CLASSERT(x) if(x){} + #define ADLASSERT(x) if(x){} + +#endif + + + + +#ifdef _DEBUG + #define COMPILE_TIME_ASSERT(x) {int compileTimeAssertFailed[x]; compileTimeAssertFailed[0];} +#else + #define COMPILE_TIME_ASSERT(x) +#endif + +#ifdef _DEBUG + #include + #include + __inline + void debugPrintf(const char *fmt, ...) + { + va_list arg; + va_start(arg, fmt); +#ifdef DX11RENDER + char buf[256]; + vsprintf_s( buf, 256, fmt, arg ); +#ifdef UNICODE + WCHAR wbuf[256]; + int sizeWide = MultiByteToWideChar(0,0,buf,-1,wbuf,0); + MultiByteToWideChar(0,0,buf,-1,wbuf,sizeWide); + +// swprintf_s( wbuf, 256, L"%s", buf ); + OutputDebugString( wbuf ); +#else + OutputDebugString( buf ); +#endif +#else + vprintf(fmt, arg); +#endif + va_end(arg); + } +#else + __inline + void debugPrintf(const char *fmt, ...) + { + } +#endif + + +#define WARN(msg) debugPrintf("WARNING: %s\n", msg); + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMath.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMath.h new file mode 100644 index 000000000..a72422047 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMath.h @@ -0,0 +1,216 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef CL_MATH_H +#define CL_MATH_H + +#include +#include +#include +#include + + +#include "AdlError.h" +#include +#define pxSort std::sort + +#define PI 3.14159265358979323846f +#define NEXTMULTIPLEOF(num, alignment) (((num)/(alignment) + (((num)%(alignment)==0)?0:1))*(alignment)) + + +#define _MEM_CLASSALIGN16 __declspec(align(16)) +#define _MEM_ALIGNED_ALLOCATOR16 void* operator new(size_t size) { return _aligned_malloc( size, 16 ); } \ + void operator delete(void *p) { _aligned_free( p ); } \ + void* operator new[](size_t size) { return _aligned_malloc( size, 16 ); } \ + void operator delete[](void *p) { _aligned_free( p ); } \ + void* operator new(size_t size, void* p) { return p; } \ + void operator delete(void *p, void* pp) {} + + + +template +T nextPowerOf2(T n) +{ + n -= 1; + for(int i=0; i>i); + return n+1; +} + + +_MEM_CLASSALIGN16 +struct float4 +{ + _MEM_ALIGNED_ALLOCATOR16; + union + { + struct + { + float x,y,z,w; + }; + struct + { + float s[4]; + }; + __m128 m_quad; + }; +}; + +__forceinline +unsigned int isZero(const float4& a) +{ + return (a.x == 0.f) & (a.y == 0.f) & (a.z == 0.f) & (a.w == 0.f); +} + +_MEM_CLASSALIGN16 +struct int4 +{ + _MEM_ALIGNED_ALLOCATOR16; + union + { + struct + { + int x,y,z,w; + }; + struct + { + int s[4]; + }; + }; +}; + +struct int2 +{ + union + { + struct + { + int x,y; + }; + struct + { + int s[2]; + }; + }; +}; + +struct float2 +{ + union + { + struct + { + float x,y; + }; + struct + { + float s[2]; + }; + }; +}; + + +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + + + +#include "Adlfloat4.inl" +//#include + + + + +template +void swap2(T& a, T& b) +{ + T tmp = a; + a = b; + b = tmp; +} + + +__inline +void randSeed(int seed) +{ + srand( seed ); +} + +template +__inline +T randRange(const T& minV, const T& maxV) +{ + float r = (rand()%10000)/10000.f; + T range = maxV - minV; + return (T)(minV + r*range); +} + +template<> +__inline +float4 randRange(const float4& minV, const float4& maxV) +{ + float4 r = make_float4( (rand()%10000)/10000.f, (rand()%10000)/10000.f, (rand()%10000)/10000.f, (rand()%10000)/10000.f ); + float4 range = maxV - minV; + return (minV + r*range); +} + + +struct SortData +{ + union + { + u32 m_key; + struct { u16 m_key16[2]; }; + }; + u32 m_value; + + friend bool operator <(const SortData& a, const SortData& b) + { + return a.m_key < b.m_key; + } +}; + + + +template +T* addByteOffset(void* baseAddr, u32 offset) +{ + return (T*)(((u32)baseAddr)+offset); +} + + +struct Pair32 +{ + Pair32(){} + Pair32(u32 a, u32 b) : m_a(a), m_b(b){} + + u32 m_a; + u32 m_b; +}; + +struct PtrPair +{ + PtrPair(){} + PtrPair(void* a, void* b) : m_a(a), m_b(b){} + template + PtrPair(T* a, T* b) : m_a((void*)a), m_b((void*)b){} + + void* m_a; + void* m_b; +}; + +#endif diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMatrix3x3.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMatrix3x3.h new file mode 100644 index 000000000..fbd82aac2 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlMatrix3x3.h @@ -0,0 +1,194 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef MATRIX3X3_H +#define MATRIX3X3_H + +#include "AdlMath.h" + +/////////////////////////////////////// +// Matrix3x3 +/////////////////////////////////////// + +typedef +_MEM_CLASSALIGN16 struct +{ + _MEM_ALIGNED_ALLOCATOR16; + float4 m_row[3]; +}Matrix3x3; + +__inline +Matrix3x3 mtZero(); + +__inline +Matrix3x3 mtIdentity(); + +__inline +Matrix3x3 mtDiagonal(float a, float b, float c); + +__inline +Matrix3x3 mtTranspose(const Matrix3x3& m); + +__inline +Matrix3x3 mtMul(const Matrix3x3& a, const Matrix3x3& b); + +__inline +float4 mtMul1(const Matrix3x3& a, const float4& b); + +__inline +Matrix3x3 mtMul2(float a, const Matrix3x3& b); + +__inline +float4 mtMul3(const float4& b, const Matrix3x3& a); + +__inline +Matrix3x3 mtInvert(const Matrix3x3& m); + +__inline +Matrix3x3 mtZero() +{ + Matrix3x3 m; + m.m_row[0] = make_float4(0.f); + m.m_row[1] = make_float4(0.f); + m.m_row[2] = make_float4(0.f); + return m; +} + +__inline +Matrix3x3 mtIdentity() +{ + Matrix3x3 m; + m.m_row[0] = make_float4(1,0,0); + m.m_row[1] = make_float4(0,1,0); + m.m_row[2] = make_float4(0,0,1); + return m; +} + +__inline +Matrix3x3 mtDiagonal(float a, float b, float c) +{ + Matrix3x3 m; + m.m_row[0] = make_float4(a,0,0); + m.m_row[1] = make_float4(0,b,0); + m.m_row[2] = make_float4(0,0,c); + return m; +} + +__inline +Matrix3x3 mtTranspose(const Matrix3x3& m) +{ + Matrix3x3 out; + out.m_row[0] = make_float4(m.m_row[0].s[0], m.m_row[1].s[0], m.m_row[2].s[0], 0.f); + out.m_row[1] = make_float4(m.m_row[0].s[1], m.m_row[1].s[1], m.m_row[2].s[1], 0.f); + out.m_row[2] = make_float4(m.m_row[0].s[2], m.m_row[1].s[2], m.m_row[2].s[2], 0.f); + return out; +} + +__inline +Matrix3x3 mtMul(const Matrix3x3& a, const Matrix3x3& b) +{ + Matrix3x3 transB; + transB = mtTranspose( b ); + Matrix3x3 ans; + for(int i=0; i<3; i++) + { + ans.m_row[i].s[0] = dot3F4(a.m_row[i],transB.m_row[0]); + ans.m_row[i].s[1] = dot3F4(a.m_row[i],transB.m_row[1]); + ans.m_row[i].s[2] = dot3F4(a.m_row[i],transB.m_row[2]); + } + return ans; +} + +__inline +float4 mtMul1(const Matrix3x3& a, const float4& b) +{ + float4 ans; + ans.s[0] = dot3F4( a.m_row[0], b ); + ans.s[1] = dot3F4( a.m_row[1], b ); + ans.s[2] = dot3F4( a.m_row[2], b ); + return ans; +} + +__inline +Matrix3x3 mtMul2(float a, const Matrix3x3& b) +{ + Matrix3x3 ans; + ans.m_row[0] = a*b.m_row[0]; + ans.m_row[1] = a*b.m_row[1]; + ans.m_row[2] = a*b.m_row[2]; + return ans; +} + +__inline +float4 mtMul3(const float4& a, const Matrix3x3& b) +{ + float4 ans; + ans.x = a.x*b.m_row[0].x + a.y*b.m_row[1].x + a.z*b.m_row[2].x; + ans.y = a.x*b.m_row[0].y + a.y*b.m_row[1].y + a.z*b.m_row[2].y; + ans.z = a.x*b.m_row[0].z + a.y*b.m_row[1].z + a.z*b.m_row[2].z; + return ans; +} + +__inline +Matrix3x3 mtInvert(const Matrix3x3& m) +{ + float det = m.m_row[0].s[0]*m.m_row[1].s[1]*m.m_row[2].s[2]+m.m_row[1].s[0]*m.m_row[2].s[1]*m.m_row[0].s[2]+m.m_row[2].s[0]*m.m_row[0].s[1]*m.m_row[1].s[2] + -m.m_row[0].s[0]*m.m_row[2].s[1]*m.m_row[1].s[2]-m.m_row[2].s[0]*m.m_row[1].s[1]*m.m_row[0].s[2]-m.m_row[1].s[0]*m.m_row[0].s[1]*m.m_row[2].s[2]; + + CLASSERT( det ); + + Matrix3x3 ans; + ans.m_row[0].s[0] = m.m_row[1].s[1]*m.m_row[2].s[2] - m.m_row[1].s[2]*m.m_row[2].s[1]; + ans.m_row[0].s[1] = m.m_row[0].s[2]*m.m_row[2].s[1] - m.m_row[0].s[1]*m.m_row[2].s[2]; + ans.m_row[0].s[2] = m.m_row[0].s[1]*m.m_row[1].s[2] - m.m_row[0].s[2]*m.m_row[1].s[1]; + ans.m_row[0].w = 0.f; + + ans.m_row[1].s[0] = m.m_row[1].s[2]*m.m_row[2].s[0] - m.m_row[1].s[0]*m.m_row[2].s[2]; + ans.m_row[1].s[1] = m.m_row[0].s[0]*m.m_row[2].s[2] - m.m_row[0].s[2]*m.m_row[2].s[0]; + ans.m_row[1].s[2] = m.m_row[0].s[2]*m.m_row[1].s[0] - m.m_row[0].s[0]*m.m_row[1].s[2]; + ans.m_row[1].w = 0.f; + + ans.m_row[2].s[0] = m.m_row[1].s[0]*m.m_row[2].s[1] - m.m_row[1].s[1]*m.m_row[2].s[0]; + ans.m_row[2].s[1] = m.m_row[0].s[1]*m.m_row[2].s[0] - m.m_row[0].s[0]*m.m_row[2].s[1]; + ans.m_row[2].s[2] = m.m_row[0].s[0]*m.m_row[1].s[1] - m.m_row[0].s[1]*m.m_row[1].s[0]; + ans.m_row[2].w = 0.f; + + ans = mtMul2((1.0f/det), ans); + return ans; +} + +__inline +Matrix3x3 mtSet( const float4& a, const float4& b, const float4& c ) +{ + Matrix3x3 m; + m.m_row[0] = a; + m.m_row[1] = b; + m.m_row[2] = c; + return m; +} + +__inline +Matrix3x3 operator+(const Matrix3x3& a, const Matrix3x3& b) +{ + Matrix3x3 out; + out.m_row[0] = a.m_row[0] + b.m_row[0]; + out.m_row[1] = a.m_row[1] + b.m_row[1]; + out.m_row[2] = a.m_row[2] + b.m_row[2]; + return out; +} + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlQuaternion.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlQuaternion.h new file mode 100644 index 000000000..979e5fec5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlQuaternion.h @@ -0,0 +1,155 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef QUATERNION_H +#define QUATERNION_H + +#include "AdlMatrix3x3.h" + + +typedef float4 Quaternion; + +__inline +Quaternion qtSet(const float4& axis, float angle); + +__inline +Quaternion qtMul(const Quaternion& a, const Quaternion& b); + +__inline +float4 qtRotate(const Quaternion& q, const float4& vec); + +__inline +float4 qtInvRotate(const Quaternion& q, const float4& vec); + +__inline +Quaternion qtInvert(const Quaternion& q); + +__inline +Matrix3x3 qtGetRotationMatrix(const Quaternion& quat); + +__inline +Quaternion qtNormalize(const Quaternion& q); + +__inline +Quaternion qtGetIdentity() { return make_float4(0,0,0,1); } + +__inline +Quaternion qtSet(const float4& axis, float angle) +{ + float4 nAxis = normalize3( axis ); + + Quaternion q; + q.s[0] = nAxis.s[0]*sin(angle/2); + q.s[1] = nAxis.s[1]*sin(angle/2); + q.s[2] = nAxis.s[2]*sin(angle/2); + q.s[3] = cos(angle/2); + return q; +} + +__inline +Quaternion qtMul(const Quaternion& a, const Quaternion& b) +{ + Quaternion ans; + ans = cross3( a, b ); + ans += a.s[3]*b + b.s[3]*a; + ans.s[3] = a.s[3]*b.s[3] - (a.s[0]*b.s[0]+a.s[1]*b.s[1]+a.s[2]*b.s[2]); + return ans; +} + +__inline +float4 qtRotate(const Quaternion& q, const float4& vec) +{ + Quaternion vecQ = vec; + vecQ.s[3] = 0.f; + Quaternion qInv = qtInvert( q ); + float4 out = qtMul(qtMul(q,vecQ),qInv); + return out; +} + +__inline +float4 qtInvRotate(const Quaternion& q, const float4& vec) +{ + return qtRotate( qtInvert( q ), vec ); +} + +__inline +Quaternion qtInvert(const Quaternion& q) +{ + Quaternion ans; + ans.s[0] = -q.s[0]; + ans.s[1] = -q.s[1]; + ans.s[2] = -q.s[2]; + ans.s[3] = q.s[3]; + return ans; +} + +__inline +Matrix3x3 qtGetRotationMatrix(const Quaternion& quat) +{ + float4 quat2 = make_float4(quat.s[0]*quat.s[0], quat.s[1]*quat.s[1], quat.s[2]*quat.s[2], 0.f); + Matrix3x3 out; + + out.m_row[0].s[0]=1-2*quat2.s[1]-2*quat2.s[2]; + out.m_row[0].s[1]=2*quat.s[0]*quat.s[1]-2*quat.s[3]*quat.s[2]; + out.m_row[0].s[2]=2*quat.s[0]*quat.s[2]+2*quat.s[3]*quat.s[1]; + out.m_row[0].s[3] = 0.f; + + out.m_row[1].s[0]=2*quat.s[0]*quat.s[1]+2*quat.s[3]*quat.s[2]; + out.m_row[1].s[1]=1-2*quat2.s[0]-2*quat2.s[2]; + out.m_row[1].s[2]=2*quat.s[1]*quat.s[2]-2*quat.s[3]*quat.s[0]; + out.m_row[1].s[3] = 0.f; + + out.m_row[2].s[0]=2*quat.s[0]*quat.s[2]-2*quat.s[3]*quat.s[1]; + out.m_row[2].s[1]=2*quat.s[1]*quat.s[2]+2*quat.s[3]*quat.s[0]; + out.m_row[2].s[2]=1-2*quat2.s[0]-2*quat2.s[1]; + out.m_row[2].s[3] = 0.f; + + return out; +} + +__inline +Quaternion qtGetQuaternion(const Matrix3x3* m) +{ + Quaternion q; + q.w = sqrtf( m[0].m_row[0].x + m[0].m_row[1].y + m[0].m_row[2].z + 1 ) * 0.5f; + float inv4w = 1.f/(4.f*q.w); + q.x = (m[0].m_row[2].y-m[0].m_row[1].z)*inv4w; + q.y = (m[0].m_row[0].z-m[0].m_row[2].x)*inv4w; + q.z = (m[0].m_row[1].x-m[0].m_row[0].y)*inv4w; + + return q; +} + +__inline +Quaternion qtNormalize(const Quaternion& q) +{ + return normalize4(q); +} + +__inline +float4 transform(const float4& p, const float4& translation, const Quaternion& orientation) +{ + return qtRotate( orientation, p ) + translation; +} + +__inline +float4 invTransform(const float4& p, const float4& translation, const Quaternion& orientation) +{ + return qtRotate( qtInvert( orientation ), p-translation ); // use qtInvRotate +} + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlRigidBody.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlRigidBody.h new file mode 100644 index 000000000..b374cd032 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlRigidBody.h @@ -0,0 +1,59 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ADL_RIGID_BODY_H +#define ADL_RIGID_BODY_H + +#include "AdlQuaternion.h" + +class RigidBodyBase +{ + public: + + _MEM_CLASSALIGN16 + struct Body + { + _MEM_ALIGNED_ALLOCATOR16; + + float4 m_pos; + Quaternion m_quat; + float4 m_linVel; + float4 m_angVel; + + u32 m_shapeIdx; + u32 m_shapeType; + + float m_invMass; + float m_restituitionCoeff; + float m_frictionCoeff; + + }; + + struct Inertia + { +/* u16 m_shapeType; + u16 m_shapeIdx; + float m_restituitionCoeff; + float m_frictionCoeff; + int m_padding; +*/ + Matrix3x3 m_invInertia; + Matrix3x3 m_initInvInertia; + }; +}; + +#endif// ADL_RIGID_BODY_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlTransform.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlTransform.h new file mode 100644 index 000000000..d9464babf --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/AdlTransform.h @@ -0,0 +1,61 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef _ADL_TRANSFORM_H +#define _ADL_TRANSFORM_H + +#include "AdlMath.h" +#include "AdlQuaternion.h" +#include "AdlMatrix3x3.h" + +struct Transform +{ + float4 m_translation; + Matrix3x3 m_rotation; +}; + +Transform trSetTransform(const float4& translation, const Quaternion& quat) +{ + Transform tr; + tr.m_translation = translation; + tr.m_rotation = qtGetRotationMatrix( quat ); + return tr; +} + +Transform trInvert( const Transform& tr ) +{ + Transform ans; + ans.m_rotation = mtTranspose( tr.m_rotation ); + ans.m_translation = mtMul1( ans.m_rotation, -tr.m_translation ); + return ans; +} + +Transform trMul(const Transform& trA, const Transform& trB) +{ + Transform ans; + ans.m_rotation = mtMul( trA.m_rotation, trB.m_rotation ); + ans.m_translation = mtMul1( trA.m_rotation, trB.m_translation ) + trA.m_translation; + return ans; +} + +float4 trMul1(const Transform& tr, const float4& p) +{ + return mtMul1( tr.m_rotation, p ) + tr.m_translation; +} + + +#endif //_ADL_TRANSFORM_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4.inl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4.inl new file mode 100644 index 000000000..4e98a087a --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4.inl @@ -0,0 +1,373 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +//#define CHECK_ALIGNMENT(a) CLASSERT((u32(&(a)) & 0xf) == 0); +#define CHECK_ALIGNMENT(a) a; + + +__inline +float4 make_float4(float x, float y, float z, float w = 0.f) +{ + float4 v; + v.x = x; v.y = y; v.z = z; v.w = w; + return v; +} + +__inline +float4 make_float4(float x) +{ + return make_float4(x,x,x,x); +} + +__inline +float4 make_float4(const int4& x) +{ + return make_float4((float)x.s[0], (float)x.s[1], (float)x.s[2], (float)x.s[3]); +} + +__inline +float2 make_float2(float x, float y) +{ + float2 v; + v.s[0] = x; v.s[1] = y; + return v; +} + +__inline +float2 make_float2(float x) +{ + return make_float2(x,x); +} + +__inline +float2 make_float2(const int2& x) +{ + return make_float2((float)x.s[0], (float)x.s[1]); +} + +__inline +int4 make_int4(int x, int y, int z, int w = 0) +{ + int4 v; + v.s[0] = x; v.s[1] = y; v.s[2] = z; v.s[3] = w; + return v; +} + +__inline +int4 make_int4(int x) +{ + return make_int4(x,x,x,x); +} + +__inline +int4 make_int4(const float4& x) +{ + return make_int4((int)x.x, (int)x.y, (int)x.z, (int)x.w); +} + +__inline +int2 make_int2(int a, int b) +{ + int2 ans; ans.x = a; ans.y = b; + return ans; +} + +__inline +float4 operator-(const float4& a) +{ + return make_float4(-a.x, -a.y, -a.z, -a.w); +} + +__inline +float4 operator*(const float4& a, const float4& b) +{ + CLASSERT((u32(&a) & 0xf) == 0); + + float4 out; + out.s[0] = a.s[0]*b.s[0]; + out.s[1] = a.s[1]*b.s[1]; + out.s[2] = a.s[2]*b.s[2]; + out.s[3] = a.s[3]*b.s[3]; + return out; +} + +__inline +float4 operator*(float a, const float4& b) +{ + return make_float4(a*b.s[0], a*b.s[1], a*b.s[2], a*b.s[3]); +} + +__inline +float4 operator*(const float4& b, float a) +{ + CHECK_ALIGNMENT(b); + + return make_float4(a*b.s[0], a*b.s[1], a*b.s[2], a*b.s[3]); +} + +__inline +void operator*=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]*=b.s[0]; + a.s[1]*=b.s[1]; + a.s[2]*=b.s[2]; + a.s[3]*=b.s[3]; +} + +__inline +void operator*=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]*=b; + a.s[1]*=b; + a.s[2]*=b; + a.s[3]*=b; +} + +// +__inline +float4 operator/(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]/b.s[0]; + out.s[1] = a.s[1]/b.s[1]; + out.s[2] = a.s[2]/b.s[2]; + out.s[3] = a.s[3]/b.s[3]; + return out; +} + +__inline +float4 operator/(const float4& b, float a) +{ + CHECK_ALIGNMENT(b); + + return make_float4(b.s[0]/a, b.s[1]/a, b.s[2]/a, b.s[3]/a); +} + +__inline +void operator/=(float4& a, const float4& b) +{ + a.s[0]/=b.s[0]; + a.s[1]/=b.s[1]; + a.s[2]/=b.s[2]; + a.s[3]/=b.s[3]; +} + +__inline +void operator/=(float4& a, float b) +{ + CLASSERT((u32(&a) & 0xf) == 0); + + a.s[0]/=b; + a.s[1]/=b; + a.s[2]/=b; + a.s[3]/=b; +} +// + +__inline +float4 operator+(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]+b.s[0]; + out.s[1] = a.s[1]+b.s[1]; + out.s[2] = a.s[2]+b.s[2]; + out.s[3] = a.s[3]+b.s[3]; + return out; +} + +__inline +float4 operator+(const float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]+b; + out.s[1] = a.s[1]+b; + out.s[2] = a.s[2]+b; + out.s[3] = a.s[3]+b; + return out; +} + +__inline +float4 operator-(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]-b.s[0]; + out.s[1] = a.s[1]-b.s[1]; + out.s[2] = a.s[2]-b.s[2]; + out.s[3] = a.s[3]-b.s[3]; + return out; +} + +__inline +float4 operator-(const float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]-b; + out.s[1] = a.s[1]-b; + out.s[2] = a.s[2]-b; + out.s[3] = a.s[3]-b; + return out; +} + +__inline +void operator+=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]+=b.s[0]; + a.s[1]+=b.s[1]; + a.s[2]+=b.s[2]; + a.s[3]+=b.s[3]; +} + +__inline +void operator+=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]+=b; + a.s[1]+=b; + a.s[2]+=b; + a.s[3]+=b; +} + +__inline +void operator-=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]-=b.s[0]; + a.s[1]-=b.s[1]; + a.s[2]-=b.s[2]; + a.s[3]-=b.s[3]; +} + +__inline +void operator-=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]-=b; + a.s[1]-=b; + a.s[2]-=b; + a.s[3]-=b; +} + + + + + +__inline +float4 cross3(const float4& a, const float4& b) +{ + return make_float4(a.s[1]*b.s[2]-a.s[2]*b.s[1], + a.s[2]*b.s[0]-a.s[0]*b.s[2], + a.s[0]*b.s[1]-a.s[1]*b.s[0], + 0); +} + +__inline +float dot3F4(const float4& a, const float4& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z; +} + +__inline +float length3(const float4& a) +{ + return sqrtf(dot3F4(a,a)); +} + +__inline +float dot4(const float4& a, const float4& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w; +} + +// for height +__inline +float dot3w1(const float4& point, const float4& eqn) +{ + return point.x*eqn.x+point.y*eqn.y+point.z*eqn.z+eqn.w; +} + +__inline +float4 normalize3(const float4& a) +{ + float length = sqrtf(dot3F4(a, a)); + return 1.f/length * a; +} + +__inline +float4 normalize4(const float4& a) +{ + float length = sqrtf(dot4(a, a)); + return 1.f/length * a; +} + +__inline +float4 createEquation(const float4& a, const float4& b, const float4& c) +{ + float4 eqn; + float4 ab = b-a; + float4 ac = c-a; + eqn = normalize3( cross3(ab, ac) ); + eqn.w = -dot3F4(eqn,a); + return eqn; +} + + +template +__inline +T max2(const T& a, const T& b) +{ + return (a>b)? a:b; +} + +template +__inline +T min2(const T& a, const T& b) +{ + return (a +__inline +float4 max2(const float4& a, const float4& b) +{ + return make_float4( max2(a.x,b.x), max2(a.y,b.y), max2(a.z,b.z), max2(a.w,b.w) ); +} + +template<> +__inline +float4 min2(const float4& a, const float4& b) +{ + return make_float4( min2(a.x,b.x), min2(a.y,b.y), min2(a.z,b.z), min2(a.w,b.w) ); +} + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4SSE.inl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4SSE.inl new file mode 100644 index 000000000..a10211e06 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Adlfloat4SSE.inl @@ -0,0 +1,381 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +//#define CHECK_ALIGNMENT(a) CLASSERT((u32(&(a)) & 0xf) == 0); +#define CHECK_ALIGNMENT(a) a; + + +__inline +float4 make_float4(float x, float y, float z, float w = 0.f) +{ + float4 v; + v.m_quad = _mm_set_ps(w,z,y,x); + + return v; +} + +__inline +float4 make_float4(float x) +{ + return make_float4(x,x,x,x); +} + +__inline +float4 make_float4(const int4& x) +{ + return make_float4((float)x.s[0], (float)x.s[1], (float)x.s[2], (float)x.s[3]); +} + +__inline +float2 make_float2(float x, float y) +{ + float2 v; + v.s[0] = x; v.s[1] = y; + return v; +} + +__inline +float2 make_float2(float x) +{ + return make_float2(x,x); +} + +__inline +float2 make_float2(const int2& x) +{ + return make_float2((float)x.s[0], (float)x.s[1]); +} + +__inline +int4 make_int4(int x, int y, int z, int w = 0) +{ + int4 v; + v.s[0] = x; v.s[1] = y; v.s[2] = z; v.s[3] = w; + return v; +} + +__inline +int4 make_int4(int x) +{ + return make_int4(x,x,x,x); +} + +__inline +int4 make_int4(const float4& x) +{ + return make_int4((int)x.x, (int)x.y, (int)x.z, (int)x.w); +} + +__inline +int2 make_int2(int a, int b) +{ + int2 ans; ans.x = a; ans.y = b; + return ans; +} + +__inline +float4 operator-(const float4& a) +{ + float4 zero; zero.m_quad = _mm_setzero_ps(); + float4 ans; ans.m_quad = _mm_sub_ps( zero.m_quad, a.m_quad ); + return ans; +} + +__inline +float4 operator*(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.m_quad = _mm_mul_ps( a.m_quad, b.m_quad ); + return out; +} + +__inline +float4 operator*(float a, const float4& b) +{ + float4 av; av.m_quad = _mm_set1_ps( a ); + return av*b; +} + +__inline +float4 operator*(const float4& b, float a) +{ + CHECK_ALIGNMENT(b); + + float4 av; av.m_quad = _mm_set1_ps( a ); + return av*b; +} + +__inline +void operator*=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a = a*b; +} + +__inline +void operator*=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 bv; bv.m_quad = _mm_set1_ps( b ); + a = a*bv; +} + +// +__inline +float4 operator/(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.m_quad = _mm_div_ps( a.m_quad, b.m_quad ); + return out; +} + +__inline +float4 operator/(const float4& b, float a) +{ + CHECK_ALIGNMENT(b); + + float4 av; av.m_quad = _mm_set1_ps( a ); + float4 out; + out = b/av; + return out; +} + +__inline +void operator/=(float4& a, const float4& b) +{ + a = a/b; +} + +__inline +void operator/=(float4& a, float b) +{ + CLASSERT((u32(&a) & 0xf) == 0); + + float4 bv; bv.m_quad = _mm_set1_ps( b ); + a = a/bv; +} +// + +__inline +float4 operator+(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.m_quad = _mm_add_ps( a.m_quad, b.m_quad ); + return out; +} + +__inline +float4 operator+(const float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 bv; bv.m_quad = _mm_set1_ps( b ); + return a+bv; +} + +__inline +float4 operator-(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.m_quad = _mm_sub_ps( a.m_quad, b.m_quad ); + return out; +} + +__inline +float4 operator-(const float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 bv; bv.m_quad = _mm_set1_ps( b ); + return a-bv; +} + +__inline +void operator+=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a = a + b; +} + +__inline +void operator+=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 bv; bv.m_quad = _mm_set1_ps( b ); + + a = a + bv; +} + +__inline +void operator-=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a = a - b; +} + +__inline +void operator-=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 bv; bv.m_quad = _mm_set1_ps( b ); + + a = a - bv; +} + + + + + +__inline +float4 cross3(const float4& a, const float4& b) +{ // xnamathvector.inl + union IntVec + { + unsigned int m_i[4]; + __m128 m_v; + }; + + IntVec mask3 = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000}; + __m128 V1 = a.m_quad; + __m128 V2 = b.m_quad; + + __m128 vTemp1 = _mm_shuffle_ps(V1,V1,_MM_SHUFFLE(3,0,2,1)); + // z2,x2,y2,w2 + __m128 vTemp2 = _mm_shuffle_ps(V2,V2,_MM_SHUFFLE(3,1,0,2)); + // Perform the left operation + __m128 vResult = _mm_mul_ps(vTemp1,vTemp2); + // z1,x1,y1,w1 + vTemp1 = _mm_shuffle_ps(vTemp1,vTemp1,_MM_SHUFFLE(3,0,2,1)); + // y2,z2,x2,w2 + vTemp2 = _mm_shuffle_ps(vTemp2,vTemp2,_MM_SHUFFLE(3,1,0,2)); + // Perform the right operation + vTemp1 = _mm_mul_ps(vTemp1,vTemp2); + // Subract the right from left, and return answer + vResult = _mm_sub_ps(vResult,vTemp1); + // Set w to zero + float4 ans; ans.m_quad = _mm_and_ps(vResult,mask3.m_v); + return ans; +} + +__inline +float dot3F4(const float4& a, const float4& b) +{ +// return a.x*b.x+a.y*b.y+a.z*b.z; + // Perform the dot product + __m128 V1 = a.m_quad; + __m128 V2 = b.m_quad; + + __m128 vDot = _mm_mul_ps(V1,V2); + // x=Dot.vector4_f32[1], y=Dot.vector4_f32[2] + __m128 vTemp = _mm_shuffle_ps(vDot,vDot,_MM_SHUFFLE(2,1,2,1)); + // Result.vector4_f32[0] = x+y + vDot = _mm_add_ss(vDot,vTemp); + // x=Dot.vector4_f32[2] + vTemp = _mm_shuffle_ps(vTemp,vTemp,_MM_SHUFFLE(1,1,1,1)); + // Result.vector4_f32[0] = (x+y)+z + vDot = _mm_add_ss(vDot,vTemp); + // Splat x + float4 ans; ans.m_quad = _mm_shuffle_ps(vDot,vDot,_MM_SHUFFLE(0,0,0,0)); + return ans.x; +} + +__inline +float length3(const float4& a) +{ + return sqrtf(dot3F4(a,a)); +} + +__inline +float dot4(const float4& a, const float4& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w; +} + +// for height +__inline +float dot3w1(const float4& point, const float4& eqn) +{ + return point.x*eqn.x+point.y*eqn.y+point.z*eqn.z+eqn.w; +} + +__inline +float4 normalize3(const float4& a) +{ + float length = sqrtf(dot3F4(a, a)); + return 1.f/length * a; +} + +__inline +float4 normalize4(const float4& a) +{ + float length = sqrtf(dot4(a, a)); + return 1.f/length * a; +} + +__inline +float4 createEquation(const float4& a, const float4& b, const float4& c) +{ + float4 eqn; + float4 ab = b-a; + float4 ac = c-a; + eqn = normalize3( cross3(ab, ac) ); + eqn.w = -dot3F4(eqn,a); + return eqn; +} + + +template +__inline +T max2(const T& a, const T& b) +{ + return (a>b)? a:b; +} + +template +__inline +T min2(const T& a, const T& b) +{ + return (a +__inline +float4 max2(const float4& a, const float4& b) +{ + return make_float4( max2(a.x,b.x), max2(a.y,b.y), max2(a.z,b.z), max2(a.w,b.w) ); +} + +template<> +__inline +float4 min2(const float4& a, const float4& b) +{ + return make_float4( min2(a.x,b.x), min2(a.y,b.y), min2(a.z,b.z), min2(a.w,b.w) ); +} + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowPhase.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowPhase.h new file mode 100644 index 000000000..4ad551f51 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowPhase.h @@ -0,0 +1,154 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma once + +#include +//#include + +#include "AdlMath.h" +#include "AdlContact4.h" +#include "AdlRigidBody.h" + +#include "../ConvexHeightFieldShape.h" + +//#include "TypeDefinition.h" +//#include "RigidBody.h" +//#include "ConvexHeightFieldShape.h" + +namespace adl +{ +class ShapeBase; + +class ChNarrowphaseBase +{ + public: + struct Config + { + float m_collisionMargin; + }; +/* + typedef struct + { + // m_normal.w == height in u8 + float4 m_normal[HEIGHT_RES*HEIGHT_RES*6]; + u32 m_height4[HEIGHT_RES*HEIGHT_RES*6]; + + float m_scale; + float m_padding0; + float m_padding1; + float m_padding2; + } ShapeData; +*/ +}; + +template +class ChNarrowphase : public ChNarrowphaseBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct Data + { + const Device* m_device; + Kernel* m_supportCullingKernel; + Kernel* m_narrowphaseKernel; + Kernel* m_narrowphaseWithPlaneKernel; + + Buffer* m_counterBuffer; + }; + + enum + { + N_TASKS = 4, + HEIGHT_RES = ConvexHeightField::HEIGHT_RES, + }; + + struct ShapeData + { + float4 m_normal[HEIGHT_RES*HEIGHT_RES*6]; + u32 m_height4[HEIGHT_RES*HEIGHT_RES*6]; + u32 m_supportHeight4[HEIGHT_RES*HEIGHT_RES*6]; + + float m_scale; + float m_padding0; + float m_padding1; + float m_padding2; + }; + + struct ConstData + { + int m_nPairs; + float m_collisionMargin; + int m_capacity; + int m_paddings[1]; + }; + + static + Data* allocate( const Device* device ); + + static + void deallocate( Data* data ); +/* + static + Buffer* allocateShapeBuffer( const Device* device, int capacity ); + + static + void deallocateShapeBuffer( Buffer* shapeBuf ); + + static + void setShape( Buffer* shapeBuf, ShapeBase* shape, int idx, float collisionMargin ); +*/ + static + ShapeDataType allocateShapeBuffer( const Device* device, int capacity ); + + static + void deallocateShapeBuffer( ShapeDataType shapeBuf ); + + static + void setShape( ShapeDataType shapeBuf, ShapeBase* shape, int idx, float collisionMargin = 0.f ); + + static + void setShape( ShapeDataType shapeBuf, ConvexHeightField* cvxShape, int idx, float collisionMargin = 0.f ); + + // Run NarrowphaseKernel + //template + static + void execute( Data* data, const Buffer* pairs, int nPairs, + const Buffer* bodyBuf, const ShapeDataType shapeBuf, + Buffer* contactOut, int& nContacts, const Config& cfg ); + + // Run NarrowphaseWithPlaneKernel + //template + static + void execute( Data* data, const Buffer* pairs, int nPairs, + const Buffer* bodyBuf, const ShapeDataType shapeBuf, + const Buffer* vtxBuf, const Buffer* idxBuf, + Buffer* contactOut, int& nContacts, const Config& cfg ); + + // Run SupportCullingKernel + //template + static + int culling( Data* data, const Buffer* pairs, int nPairs, const Buffer* bodyBuf, + const ShapeDataType shapeBuf, const Buffer* pairsOut, const Config& cfg ); +}; + +//#include +//#include + +#include "ChNarrowphase.inl" + +}; diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphase.inl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphase.inl new file mode 100644 index 000000000..00ffbda24 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphase.inl @@ -0,0 +1,303 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +//#define PATH "..\\..\\dynamics\\basic_demo\\Stubs\\ChNarrowphaseKernels" +#define PATH "..\\..\\dynamics\\basic_demo\\Stubs\\ChNarrowphaseKernels" +#define KERNEL0 "SupportCullingKernel" +#define KERNEL1 "NarrowphaseKernel" + +#include "ChNarrowphaseKernels.h" + +class ChNarrowphaseImp +{ +public: + static + __inline + u32 u32Pack(u8 x, u8 y, u8 z, u8 w) + { + return (x) | (y<<8) | (z<<16) | (w<<24); + } + +}; + +template +typename ChNarrowphase::Data* ChNarrowphase::allocate( const Device* device ) +{ + char options[100]; + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {narrowphaseKernelsCL, 0}; +#else + {0,0}; +#endif + + + + + //sprintf(options, "-I ..\\..\\ -Wf,--c++"); + sprintf(options, "-I .\\NarrowPhaseCL\\"); + + Data* data = new Data; + data->m_device = device; + data->m_supportCullingKernel = device->getKernel( PATH, KERNEL0, options,src[TYPE] ); + data->m_narrowphaseKernel = device->getKernel( PATH, KERNEL1, options, src[TYPE]); + data->m_narrowphaseWithPlaneKernel = device->getKernel( PATH, "NarrowphaseWithPlaneKernel", options,src[TYPE]); + data->m_counterBuffer = new Buffer( device, 1 ); + + return data; +} + + +template +void ChNarrowphase::deallocate( Data* data ) +{ + delete data->m_counterBuffer; + + delete data; +} + +template +ShapeDataType ChNarrowphase::allocateShapeBuffer( const Device* device, int capacity ) +{ + ADLASSERT( device->m_type == TYPE ); + + return new Buffer( device, capacity ); +} + +template +void ChNarrowphase::deallocateShapeBuffer( ShapeDataType shapeBuf ) +{ + Buffer* s = (Buffer*)shapeBuf; + delete s; +} + +template +void ChNarrowphase::setShape( ShapeDataType shapeBuf, ShapeBase* shape, int idx, float collisionMargin ) +{ + ConvexHeightField* cvxShape = new ConvexHeightField( shape ); + Buffer* dst = (Buffer*)shapeBuf; + cvxShape->m_aabb.expandBy( make_float4( collisionMargin ) ); + { + ShapeData s; + { + for(int j=0; jm_normal[j]; + } + for(int j=0; jm_data[4*j], cvxShape->m_data[4*j+1], cvxShape->m_data[4*j+2], cvxShape->m_data[4*j+3] ); + s.m_supportHeight4[j] = ChNarrowphaseImp::u32Pack( cvxShape->m_supportHeight[4*j], cvxShape->m_supportHeight[4*j+1], cvxShape->m_supportHeight[4*j+2], cvxShape->m_supportHeight[4*j+3] ); + } + s.m_scale = cvxShape->m_scale; + } + dst->write( &s, 1, idx ); + DeviceUtils::waitForCompletion( dst->m_device ); + } + delete cvxShape; +} + +template +void ChNarrowphase::setShape( ShapeDataType shapeBuf, ConvexHeightField* cvxShape, int idx, float collisionMargin ) +{ + Buffer* dst = (Buffer*)shapeBuf; + cvxShape->m_aabb.expandBy( make_float4( collisionMargin ) ); + { + ShapeData s; + { + for(int j=0; jm_normal[j]; + } + for(int j=0; jm_data[4*j], cvxShape->m_data[4*j+1], cvxShape->m_data[4*j+2], cvxShape->m_data[4*j+3] ); + s.m_supportHeight4[j] = ChNarrowphaseImp::u32Pack( cvxShape->m_supportHeight[4*j], cvxShape->m_supportHeight[4*j+1], cvxShape->m_supportHeight[4*j+2], cvxShape->m_supportHeight[4*j+3] ); + } + s.m_scale = cvxShape->m_scale; + } + dst->write( &s, 1, idx ); + DeviceUtils::waitForCompletion( dst->m_device ); + } +} + +// Run NarrowphaseKernel +template +//template +void ChNarrowphase::execute( Data* data, const Buffer* pairs, int nPairs, const Buffer* bodyBuf, + const ShapeDataType shapeBuf, + Buffer* contactOut, int& nContacts, const Config& cfg ) +{ + if( nPairs == 0 ) return; + + Buffer* shapeBuffer = (Buffer*)shapeBuf; + ADLASSERT( shapeBuffer->getType() == TYPE ); + + const Device* device = data->m_device; + + Buffer* gPairsInNative + = BufferUtils::map( data->m_device, pairs ); + Buffer* gBodyInNative + = BufferUtils::map( data->m_device, bodyBuf ); + Buffer* gContactOutNative + = BufferUtils::map( data->m_device, contactOut ); // this might not be empty + + Buffer constBuffer( device, 1, BufferBase::BUFFER_CONST ); + + ConstData cdata; + cdata.m_nPairs = nPairs; + cdata.m_collisionMargin = cfg.m_collisionMargin; + cdata.m_capacity = contactOut->getSize() - nContacts; + + u32 n = nContacts; + data->m_counterBuffer->write( &n, 1 ); +// DeviceUtils::waitForCompletion( device ); + + { + BufferInfo bInfo[] = { BufferInfo( gPairsInNative, true ), BufferInfo( shapeBuffer ), BufferInfo( gBodyInNative ), + BufferInfo( gContactOutNative ), + BufferInfo( data->m_counterBuffer ) }; + Launcher launcher( data->m_device, data->m_narrowphaseKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nPairs*64, 64 ); + } + + data->m_counterBuffer->read( &n, 1 ); + DeviceUtils::waitForCompletion( device ); + + BufferUtils::unmap( gPairsInNative, pairs ); + BufferUtils::unmap( gBodyInNative, bodyBuf ); + BufferUtils::unmap( gContactOutNative, contactOut ); + + nContacts = min2((int)n, contactOut->getSize() ); +} + +// Run NarrowphaseWithPlaneKernel +template +//template +void ChNarrowphase::execute( Data* data, const Buffer* pairs, int nPairs, + const Buffer* bodyBuf, const ShapeDataType shapeBuf, + const Buffer* vtxBuf, const Buffer* idxBuf, + Buffer* contactOut, int& nContacts, const Config& cfg ) +{ + if( nPairs == 0 ) return; + + Buffer* shapeBuffer = (Buffer*)shapeBuf; + ADLASSERT( shapeBuffer->getType() == TYPE ); + + const Device* device = data->m_device; + + Buffer* gPairsInNative + = BufferUtils::map( data->m_device, pairs ); + Buffer* gBodyInNative + = BufferUtils::map( data->m_device, bodyBuf ); + Buffer* gContactOutNative + = BufferUtils::map( data->m_device, contactOut ); // this might not be empty + + Buffer constBuffer( device, 1, BufferBase::BUFFER_CONST ); + + ConstData cdata; + cdata.m_nPairs = nPairs; + cdata.m_collisionMargin = cfg.m_collisionMargin; + cdata.m_capacity = contactOut->getSize() - nContacts; + + u32 n = nContacts; + data->m_counterBuffer->write( &n, 1 ); +// DeviceUtils::waitForCompletion( device ); + + { + BufferInfo bInfo[] = { BufferInfo( gPairsInNative, true ), BufferInfo( shapeBuffer ), BufferInfo( gBodyInNative ), + BufferInfo( gContactOutNative ), + BufferInfo( data->m_counterBuffer ) }; + Launcher launcher( data->m_device, data->m_narrowphaseWithPlaneKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nPairs*64, 64 ); + } + + data->m_counterBuffer->read( &n, 1 ); + DeviceUtils::waitForCompletion( device ); + + BufferUtils::unmap( gPairsInNative, pairs ); + BufferUtils::unmap( gBodyInNative, bodyBuf ); + BufferUtils::unmap( gContactOutNative, contactOut ); + + nContacts = min2((int)n, contactOut->getSize() ); +} + +// Run SupportCullingKernel +template +//template +int ChNarrowphase::culling( Data* data, const Buffer* pairs, int nPairs, const Buffer* bodyBuf, + const ShapeDataType shapeBuf, const Buffer* pairsOut, const Config& cfg ) +{ + if( nPairs == 0 ) return 0; + + Buffer* shapeBuffer = (Buffer*)shapeBuf; + ADLASSERT( shapeBuffer->getType() == TYPE ); + + const Device* device = data->m_device; + + Buffer* gPairsInNative + = BufferUtils::map( data->m_device, pairs ); + Buffer* gBodyInNative + = BufferUtils::map( data->m_device, bodyBuf ); + Buffer* gPairsOutNative + = BufferUtils::map( data->m_device, pairsOut ); + + // + Buffer constBuffer( device, 1, BufferBase::BUFFER_CONST ); + + ConstData cdata; + cdata.m_nPairs = nPairs; + cdata.m_collisionMargin = cfg.m_collisionMargin; + cdata.m_capacity = pairsOut->getSize(); + + u32 n = 0; + data->m_counterBuffer->write( &n, 1 ); +// DeviceUtils::waitForCompletion( device ); + { + BufferInfo bInfo[] = { BufferInfo( gPairsInNative, true ), BufferInfo( shapeBuffer ), BufferInfo( gBodyInNative ), + BufferInfo( gPairsOutNative ), BufferInfo( data->m_counterBuffer ) }; + Launcher launcher( data->m_device, data->m_supportCullingKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nPairs, 64 ); + } + data->m_counterBuffer->read( &n, 1 ); + DeviceUtils::waitForCompletion( device ); +/* + if( gPairsInNative != pairs ) delete gPairsInNative; + if( gBodyInNative != bodyBuf ) delete gBodyInNative; + if( gPairsOutNative != pairsOut ) + { + gPairsOutNative->read( pairsOut->m_ptr, n ); + DeviceUtils::waitForCompletion( device ); + delete gPairsOutNative; + } +*/ + BufferUtils::unmap( gPairsInNative, pairs ); + BufferUtils::unmap( gBodyInNative, bodyBuf ); + BufferUtils::unmap( gPairsOutNative, pairsOut ); + + return min2((int)n, pairsOut->getSize() ); +} + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.cl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.cl new file mode 100644 index 000000000..af177a836 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.cl @@ -0,0 +1,1629 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_extended_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable + +#ifdef cl_ext_atomic_counters_32 +#pragma OPENCL EXTENSION cl_ext_atomic_counters_32 : enable +#else +#define counter32_t volatile global int* +#endif + + +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GET_NUM_GROUPS get_num_groups(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) +#define AppendInc(x, out) out = atomic_inc(x) +#define AtomAdd(x, value) atom_add(&(x), value) +#define AtomCmpxhg(x, cmp, value) atom_cmpxchg( &(x), cmp, value ) +#define AtomXhg(x, value) atom_xchg ( &(x), value ) + + +#define SELECT_UINT4( b, a, condition ) select( b,a,condition ) + +#define make_float4 (float4) +#define make_float2 (float2) +#define make_uint4 (uint4) +#define make_int4 (int4) +#define make_uint2 (uint2) +#define make_int2 (int2) + + +#define max2 max +#define min2 min + + +/////////////////////////////////////// +// Vector +/////////////////////////////////////// +__inline +float fastDiv(float numerator, float denominator) +{ + return native_divide(numerator, denominator); +// return numerator/denominator; +} + +__inline +float4 fastDiv4(float4 numerator, float4 denominator) +{ + return native_divide(numerator, denominator); +} + +__inline +float fastSqrtf(float f2) +{ + return native_sqrt(f2); +// return sqrt(f2); +} + +__inline +float fastRSqrt(float f2) +{ + return native_rsqrt(f2); +} + +__inline +float fastLength4(float4 v) +{ + return fast_length(v); +} + +__inline +float4 fastNormalize4(float4 v) +{ + return fast_normalize(v); +} + + +__inline +float sqrtf(float a) +{ +// return sqrt(a); + return native_sqrt(a); +} + +__inline +float4 cross3(float4 a, float4 b) +{ + return cross(a,b); +} + +__inline +float dot3F4(float4 a, float4 b) +{ + float4 a1 = make_float4(a.xyz,0.f); + float4 b1 = make_float4(b.xyz,0.f); + return dot(a1, b1); +} + +__inline +float length3(const float4 a) +{ + return sqrtf(dot3F4(a,a)); +} + +__inline +float dot4(const float4 a, const float4 b) +{ + return dot( a, b ); +} + +// for height +__inline +float dot3w1(const float4 point, const float4 eqn) +{ + return dot3F4(point,eqn) + eqn.w; +} + +__inline +float4 normalize3(const float4 a) +{ + float4 n = make_float4(a.x, a.y, a.z, 0.f); + return fastNormalize4( n ); +// float length = sqrtf(dot3F4(a, a)); +// return 1.f/length * a; +} + +__inline +float4 normalize4(const float4 a) +{ + float length = sqrtf(dot4(a, a)); + return 1.f/length * a; +} + +__inline +float4 createEquation(const float4 a, const float4 b, const float4 c) +{ + float4 eqn; + float4 ab = b-a; + float4 ac = c-a; + eqn = normalize3( cross3(ab, ac) ); + eqn.w = -dot3F4(eqn,a); + return eqn; +} + +/////////////////////////////////////// +// Matrix3x3 +/////////////////////////////////////// + +typedef struct +{ + float4 m_row[3]; +}Matrix3x3; + +__inline +Matrix3x3 mtZero(); + +__inline +Matrix3x3 mtIdentity(); + +__inline +Matrix3x3 mtTranspose(Matrix3x3 m); + +__inline +Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b); + +__inline +float4 mtMul1(Matrix3x3 a, float4 b); + +__inline +float4 mtMul3(float4 a, Matrix3x3 b); + +__inline +Matrix3x3 mtZero() +{ + Matrix3x3 m; + m.m_row[0] = (float4)(0.f); + m.m_row[1] = (float4)(0.f); + m.m_row[2] = (float4)(0.f); + return m; +} + +__inline +Matrix3x3 mtIdentity() +{ + Matrix3x3 m; + m.m_row[0] = (float4)(1,0,0,0); + m.m_row[1] = (float4)(0,1,0,0); + m.m_row[2] = (float4)(0,0,1,0); + return m; +} + +__inline +Matrix3x3 mtTranspose(Matrix3x3 m) +{ + Matrix3x3 out; + out.m_row[0] = (float4)(m.m_row[0].x, m.m_row[1].x, m.m_row[2].x, 0.f); + out.m_row[1] = (float4)(m.m_row[0].y, m.m_row[1].y, m.m_row[2].y, 0.f); + out.m_row[2] = (float4)(m.m_row[0].z, m.m_row[1].z, m.m_row[2].z, 0.f); + return out; +} + +__inline +Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b) +{ + Matrix3x3 transB; + transB = mtTranspose( b ); + Matrix3x3 ans; + // why this doesn't run when 0ing in the for{} + a.m_row[0].w = 0.f; + a.m_row[1].w = 0.f; + a.m_row[2].w = 0.f; + for(int i=0; i<3; i++) + { +// a.m_row[i].w = 0.f; + ans.m_row[i].x = dot3F4(a.m_row[i],transB.m_row[0]); + ans.m_row[i].y = dot3F4(a.m_row[i],transB.m_row[1]); + ans.m_row[i].z = dot3F4(a.m_row[i],transB.m_row[2]); + ans.m_row[i].w = 0.f; + } + return ans; +} + +__inline +float4 mtMul1(Matrix3x3 a, float4 b) +{ + float4 ans; + ans.x = dot3F4( a.m_row[0], b ); + ans.y = dot3F4( a.m_row[1], b ); + ans.z = dot3F4( a.m_row[2], b ); + ans.w = 0.f; + return ans; +} + +__inline +float4 mtMul3(float4 a, Matrix3x3 b) +{ + float4 colx = make_float4(b.m_row[0].x, b.m_row[1].x, b.m_row[2].x, 0); + float4 coly = make_float4(b.m_row[0].y, b.m_row[1].y, b.m_row[2].y, 0); + float4 colz = make_float4(b.m_row[0].z, b.m_row[1].z, b.m_row[2].z, 0); + + float4 ans; + ans.x = dot3F4( a, colx ); + ans.y = dot3F4( a, coly ); + ans.z = dot3F4( a, colz ); + return ans; +} + +/////////////////////////////////////// +// Quaternion +/////////////////////////////////////// + +typedef float4 Quaternion; + +__inline +Quaternion qtMul(Quaternion a, Quaternion b); + +__inline +Quaternion qtNormalize(Quaternion in); + +__inline +float4 qtRotate(Quaternion q, float4 vec); + +__inline +Quaternion qtInvert(Quaternion q); + +__inline +Matrix3x3 qtGetRotationMatrix(Quaternion q); + + + +__inline +Quaternion qtMul(Quaternion a, Quaternion b) +{ + Quaternion ans; + ans = cross3( a, b ); + ans += a.w*b+b.w*a; +// ans.w = a.w*b.w - (a.x*b.x+a.y*b.y+a.z*b.z); + ans.w = a.w*b.w - dot3F4(a, b); + return ans; +} + +__inline +Quaternion qtNormalize(Quaternion in) +{ + return fastNormalize4(in); +// in /= length( in ); +// return in; +} +__inline +float4 qtRotate(Quaternion q, float4 vec) +{ + Quaternion qInv = qtInvert( q ); + float4 vcpy = vec; + vcpy.w = 0.f; + float4 out = qtMul(qtMul(q,vcpy),qInv); + return out; +} + +__inline +Quaternion qtInvert(Quaternion q) +{ + return (Quaternion)(-q.xyz, q.w); +} + +__inline +float4 qtInvRotate(const Quaternion q, float4 vec) +{ + return qtRotate( qtInvert( q ), vec ); +} + +__inline +Matrix3x3 qtGetRotationMatrix(Quaternion quat) +{ + float4 quat2 = (float4)(quat.x*quat.x, quat.y*quat.y, quat.z*quat.z, 0.f); + Matrix3x3 out; + + out.m_row[0].x=1-2*quat2.y-2*quat2.z; + out.m_row[0].y=2*quat.x*quat.y-2*quat.w*quat.z; + out.m_row[0].z=2*quat.x*quat.z+2*quat.w*quat.y; + out.m_row[0].w = 0.f; + + out.m_row[1].x=2*quat.x*quat.y+2*quat.w*quat.z; + out.m_row[1].y=1-2*quat2.x-2*quat2.z; + out.m_row[1].z=2*quat.y*quat.z-2*quat.w*quat.x; + out.m_row[1].w = 0.f; + + out.m_row[2].x=2*quat.x*quat.z-2*quat.w*quat.y; + out.m_row[2].y=2*quat.y*quat.z+2*quat.w*quat.x; + out.m_row[2].z=1-2*quat2.x-2*quat2.y; + out.m_row[2].w = 0.f; + + return out; +} + + +#define WG_SIZE 64 +#define HEIGHT_RES 4 +#define SHAPE_CONVEX_HEIGHT_FIELD 1//keep this in sync with AdlCollisionShape.h! + +typedef struct +{ + float4 m_normal[HEIGHT_RES*HEIGHT_RES*6]; + u32 m_height4[HEIGHT_RES*HEIGHT_RES*6]; + u32 m_supportHeight4[HEIGHT_RES*HEIGHT_RES*6]; + + float m_scale; + float m_padding0; + float m_padding1; + float m_padding2; +} ShapeData; + +typedef struct +{ + u32 m_height4[HEIGHT_RES*HEIGHT_RES*6/4]; + + float m_scale; +} ShapeDeviceData; + +typedef struct +{ + float4 m_pos; + float4 m_quat; + float4 m_linVel; + float4 m_angVel; + + u32 m_shapeIdx; + u32 m_shapeType; + + float m_invMass; + float m_restituitionCoeff; + float m_frictionCoeff; +} BodyData; + +typedef struct +{ + float4 m_worldPos[4]; + float4 m_worldNormal; // w: m_nPoints +// float m_restituitionCoeff; +// float m_frictionCoeff; + u32 m_coeffs; + u32 m_batchIdx; +// int m_nPoints; +// int m_padding0; + + u32 m_bodyAPtr;//x:m_bodyAPtr, y:m_bodyBPtr + u32 m_bodyBPtr; +} Contact4; + +#define GET_NPOINTS(x) (x).m_worldNormal.w + + +typedef struct +{ + int m_nPairs; + float m_collisionMargin; + int m_capacity; + int m_paddings[1]; +} ConstBuffer; + +__inline +float4 transform(const float4* p, const float4* translation, const Quaternion* orientation) +{ + return qtRotate( *orientation, *p ) + (*translation); +} + +__inline +float4 invTransform(const float4* p, const float4* translation, const Quaternion* orientation) +{ + return qtRotate( qtInvert( *orientation ), (*p)-(*translation) ); // use qtInvRotate +} + +void CubeMapUtilsCalcCrd(const float4 p, int* faceIdxOut, float* x, float* y) +{ + { + int idx; + float r2[] = {p.x*p.x, p.y*p.y, p.z*p.z}; + + if (r2[1]>r2[0]) + { + if (r2[2]>r2[1]) + { + idx = 2; + + } else + { + idx = 1; + } + + } else + { + if (r2[2]>r2[0]) + { + idx = 2; + } else + { + idx = 0; + } + } + + *faceIdxOut = (idx*2); +//== + float4 abs = make_float4( fabs(p.x), fabs(p.y), fabs(p.z), 0.f ); + + float d; + if( idx == 0 ) + { + *x = p.y; + *y = p.z; + d = abs.x; + *faceIdxOut += (p.x < 0.f)? 0: 1.f; + } + else if( idx == 1 ) + { + *x = p.z; + *y = p.x; + d = abs.y; + *faceIdxOut += (p.y < 0.f)? 0: 1.f; + } + else + { + *x = p.x; + *y = p.y; + d = abs.z; + *faceIdxOut += (p.z < 0.f)? 0: 1.f; + } + + float dInv = (d==0.f)? 0.f: fastDiv(1.f,d); + *x = (*x*dInv+1.f)*0.5f; + *y = (*y*dInv+1.f)*0.5f; + } +} + +float4 CubeMapUtilsCalcVector(int faceIdx, float x, float y) +{ + int dir = faceIdx/2; + float z = (faceIdx%2 == 0)? -1.f:1.f; + + x = x*2.f-1.f; + y = y*2.f-1.f; + + if( dir == 0 ) + { + return make_float4(z, x, y, 0.f); + } + else if( dir == 1 ) + { + return make_float4(y,z,x, 0.f); + } + else + { + return make_float4(x,y,z, 0.f); + } +} + +typedef int Face; + +u32 sample(__local ShapeDeviceData* shape, int face, int x, int y) +{ + + int idx = HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES; + __local u8* height = (__local u8*)shape->m_height4; + return height[idx]; +} + +u32 sampleSupportGlobal(__global ShapeData* shape, int face, int x, int y) +{ + + int idx = HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES; + __global u8* height = (__global u8*)shape->m_supportHeight4; + return height[idx]; +} + +float4 sampleNormal(__local ShapeData* shape, int face, int x, int y) +{ + return shape->m_normal[HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES]; +} + +float4 sampleNormalGlobal(const __global ShapeData* shape, int face, int x, int y) +{ + return shape->m_normal[HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES]; +} + +float4 ShapeDataCalcSamplePoint( __local const ShapeDeviceData* shape, int sIdx )//u8 height, int sIdx, float scale ) +{ + const float oneOver255 = 1.f/255.f; + + int faceIdx = fastDiv(sIdx,(HEIGHT_RES*HEIGHT_RES)); + int r = (sIdx%(HEIGHT_RES*HEIGHT_RES)); + int i = r/HEIGHT_RES; + int j = r%HEIGHT_RES; + + float4 v; + float x = fastDiv((i+0.5f),(float)HEIGHT_RES); + float y = fastDiv((j+0.5f),(float)HEIGHT_RES); + v = CubeMapUtilsCalcVector(faceIdx, x, y); + v = normalize3( v ); + + int quantizedHeight = sample( shape, faceIdx, i, j ); + float rheight = quantizedHeight*oneOver255*shape->m_scale; + return rheight*v; +} + +float ShapeDataQueryDistance(__local const ShapeDeviceData* shape, float4 p ) +{ + if( dot3F4( p, p ) >= shape->m_scale*shape->m_scale ) return FLT_MAX; + + const float oneOver255 = 1.f/255.f; + + int faceIdx; + float x, y; + CubeMapUtilsCalcCrd( p, &faceIdx, &x, &y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + float height; + { + int xi = (int)(x); + int yi = (int)(y); + float dx = x-xi; + float dy = y-yi; + + { + int xip = min2((int)(HEIGHT_RES-1), xi+1); + int yip = min2((int)(HEIGHT_RES-1), yi+1); + + u32 xy = sample( shape, faceIdx, xi, yi ); + u32 xpy = sample( shape, faceIdx, xip, yi ); + u32 xpyp = sample( shape, faceIdx, xip, yip ); + u32 xyp = sample( shape, faceIdx, xi, yip ); + + height = (xy*(1.f-dx)+xpy*dx)*(1.f-dy) + (xyp*(1.f-dx)+xpyp*dx)*dy; + height = height*oneOver255*shape->m_scale; + + p.w = 0.f; + + height = fastLength4( p ) - height; + } + } + + return height; +} + +float ShapeDataQuerySupportHeight(__global ShapeData* shape, float4 p ) +{ + int faceIdx; + float x, y; + CubeMapUtilsCalcCrd( p, &faceIdx, &x, &y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + float height; + { + int xi = (int)(x); + int yi = (int)(y); + + { + int xip = min2((int)(HEIGHT_RES-1), xi+1); + int yip = min2((int)(HEIGHT_RES-1), yi+1); + + u32 xy = sampleSupportGlobal( shape, faceIdx, xi, yi ); + u32 xpy = sampleSupportGlobal( shape, faceIdx, xip, yi ); + u32 xpyp = sampleSupportGlobal( shape, faceIdx, xip, yip ); + u32 xyp = sampleSupportGlobal( shape, faceIdx, xi, yip ); + + height = max2( xy, max2( xpy, max2( xpyp, xyp ) ) ); + height = height/255.f*shape->m_scale; + } + } + + return height; + +} + +float4 ShapeDataQueryNormal(__global const ShapeData* shape, float4 p ) +{ + int faceIdx; + float x, y; + CubeMapUtilsCalcCrd( p, &faceIdx, &x, &y ); + x = (x*HEIGHT_RES) - 0.5f; + y = (y*HEIGHT_RES) - 0.5f; + + float4 normalOut; + { + int xi = (int)(x); + int yi = (int)(y); + + normalOut = sampleNormalGlobal( shape, faceIdx, xi, yi ); + } + return normalOut; +} + + + +// kernels + + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void SupportCullingKernel( __global int2* restrict gPairsIn, __global ShapeData* gShapes, + __global BodyData* gBodies, + __global int2* gPairsOut, + counter32_t gNPairs, + ConstBuffer cb ) +{ + int gIdx = GET_GLOBAL_IDX; + if( gIdx >= cb.m_nPairs ) return; + + const float collisionMargin = cb.m_collisionMargin; + const int capacity = cb.m_capacity; + + int2 pair = gPairsIn[gIdx]; + BodyData bodyA = gBodies[pair.x]; + BodyData bodyB = gBodies[pair.y]; + int shapeAIdx = bodyA.m_shapeIdx; + int shapeBIdx = bodyB.m_shapeIdx; + + + bool collide = false; + + //only collide if one of the two bodies has a non-zero mass + if (bodyA.m_invMass==0.f && bodyB.m_invMass==0.f) + return; + + + if (bodyA.m_shapeType == SHAPE_CONVEX_HEIGHT_FIELD && bodyB.m_shapeType==SHAPE_CONVEX_HEIGHT_FIELD) + { + float4 abInA, baInB; + float4 ab = bodyB.m_pos - bodyA.m_pos; + { + abInA = qtInvRotate( bodyA.m_quat, ab ); + baInB = qtInvRotate( bodyB.m_quat, -ab ); + } + float hA = ShapeDataQuerySupportHeight( gShapes+shapeAIdx, abInA ); + float hB = ShapeDataQuerySupportHeight( gShapes+shapeBIdx, baInB ); + + float h2 = dot3F4( ab, ab ); + + collide = ( hA + hB + collisionMargin > sqrtf(h2) ); + } + + if( collide ) + { + int dstIdx; + AppendInc( gNPairs, dstIdx ); + if( dstIdx < capacity ) + gPairsOut[dstIdx] = pair; + } +} + + +#define PARALLEL_DO(execution, n) for(int ie=0; ie h[lIdx+1].y)? h[lIdx]: h[lIdx+1];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] = (h[lIdx].y > h[lIdx+2].y)? h[lIdx]: h[lIdx+2];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] = (h[lIdx].y > h[lIdx+4].y)? h[lIdx]: h[lIdx+4];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] = (h[lIdx].y > h[lIdx+8].y)? h[lIdx]: h[lIdx+8];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] = (h[lIdx].y > h[lIdx+16].y)? h[lIdx]: h[lIdx+16];\ + }} + +#define PARALLEL_REDUCE32(h) \ + {int lIdx = GET_LOCAL_IDX;\ + if( lIdx < 32 )\ + {\ + h[lIdx] += h[lIdx+1];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] += h[lIdx+2];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] += h[lIdx+4];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] += h[lIdx+8];\ + mem_fence( CLK_LOCAL_MEM_FENCE );\ + h[lIdx] += h[lIdx+16];\ + }} + + +float4 extractManifold(__local float4* p, __local float4* h, __local int* nPointsPtr, float4 nearNormal) +{ + int nPoints = *nPointsPtr; + float4 center = make_float4(0,0,0,0); + { // calculate center + nPoints = min2( nPoints, 32 ); + { + int lIdx = GET_LOCAL_IDX; + h[lIdx] = p[lIdx]; + h[lIdx] = (lIdx= nPoints ) a[ie] = make_int4(-0xfffffff, -0xfffffff, -0xfffffff, -0xfffffff); + } + } + + GROUP_LDS_BARRIER; + + { // vector reduce, h[64] + int lIdx = GET_LOCAL_IDX; + if( lIdx < 32 ) + { + h[lIdx] = max2( h[lIdx], h[lIdx+1] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+2] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+4] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+8] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+16] ); + } + } + + GROUP_LDS_BARRIER; + } + { + { // set to idx + idx[0] = (int)a[0].x & 0xff; + idx[1] = (int)a[0].y & 0xff; + idx[2] = (int)a[0].z & 0xff; + idx[3] = (int)a[0].w & 0xff; + } + + GROUP_LDS_BARRIER; + float4 selection; + if( GET_LOCAL_IDX < 4 ) selection = p[idx[GET_LOCAL_IDX]]; + + GROUP_LDS_BARRIER; + if( GET_LOCAL_IDX < 4 ) p[GET_LOCAL_IDX] = selection; + } + + + return center; +} + +void extractManifold1(__local float4* p, __local float4* h, __local int* nPointsPtr, float4 center) +{ + __local int* a = (__local int*)h; + { + GROUP_LDS_BARRIER; + float4 selection; + if( GET_LOCAL_IDX < 4 ) + { + int idx = (int)a[GET_LOCAL_IDX] & 0xff; + selection = p[idx]; + } + + GROUP_LDS_BARRIER; + if( GET_LOCAL_IDX < 4 ) p[GET_LOCAL_IDX] = selection; + } + +} + +void extractManifold2( __local float4* p0, __local int* nPointsPtr0, float4 nearNormal0, + __local float4* p1, __local int* nPointsPtr1, float4 nearNormal1, + __local float4* h, float4 centerOut[2]) +{ + + int nPoints[2]; + nPoints[0] = *nPointsPtr0; + nPoints[1] = *nPointsPtr1; + float4 center[2]; + center[0] = make_float4(0,0,0,0); + center[1] = make_float4(0,0,0,0); + { // calculate center + nPoints[0] = min2( nPoints[0], 32 ); + nPoints[1] = min2( nPoints[1], 32 ); + { + int lIdx = GET_LOCAL_IDX; + h[lIdx] = (lIdx= nPoints[setIdx] ) a[ie + setIdx*64] = make_int4(-0xfffffff, -0xfffffff, -0xfffffff, -0xfffffff); + + a[ie + 32] = make_int4(-0xfffffff, -0xfffffff, -0xfffffff, -0xfffffff); + } + } + } + GROUP_LDS_BARRIER; + + { // vector reduce, h[64] + int bIdx = GET_LOCAL_IDX/32; + int eIdx = GET_LOCAL_IDX%32; + int lIdx = eIdx + bIdx*64; + { + h[lIdx] = max2( h[lIdx], h[lIdx+1] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+2] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+4] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+8] ); + mem_fence( CLK_LOCAL_MEM_FENCE ); + h[lIdx] = max2( h[lIdx], h[lIdx+16] ); + } + } + + GROUP_LDS_BARRIER; + } + __local int* a = (__local int*)h; + { + GROUP_LDS_BARRIER; + + float4 selection; + + int bIdx = GET_LOCAL_IDX/32; + int eIdx = GET_LOCAL_IDX%32; + + if( eIdx < 4 ) + { + int idx = (int)a[eIdx+64*4*bIdx] & 0xff; + selection = p0[idx+32*bIdx]; + } + + GROUP_LDS_BARRIER; + if( eIdx < 4 ) p0[eIdx+32*bIdx] = selection; + } +} + +/* +1. Query Normal +2. Fill Normal +3. A->B, B->A +*/ + +void testVtx(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr, + __local ShapeDeviceData* shapeAPtr, __local ShapeDeviceData* shapeBPtr, + __local int* lNContacts, __local float4* lCPoints) +{ + int pIdx = GET_LOCAL_IDX; + float4 bodyAPos = bodyAPtr->m_pos; + float4 bodyBPos = bodyBPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + Quaternion bodyBQuat = bodyBPtr->m_quat; + while( pIdx < HEIGHT_RES*HEIGHT_RES*6 ) + { + float4 pInB = ShapeDataCalcSamplePoint( shapeBPtr, pIdx ); + + float4 pInW = transform( &pInB, &bodyBPos, &bodyBQuat ); +// Aabb bodyAAabb = bodyAPtr->m_aabb; +// if( AabbOverlapsPoint( &bodyAAabb, pInW ) ) + { + float4 pInA = invTransform( &pInW, &bodyAPos, &bodyAQuat ); + + float dist = ShapeDataQueryDistance( shapeAPtr, pInA ); + if( dist < 0.010f ) + { + int dstIdx = atom_add( lNContacts, 1 ); + if( dstIdx < 32 ) + { + lCPoints[ dstIdx ] = make_float4( pInA.x, pInA.y, pInA.z, dist ); + } + } + } + + pIdx += GET_GROUP_SIZE; + } +} + +void testVtx2(__local const BodyData* bodyA, __local const BodyData* bodyB, + __local const ShapeDeviceData* shapeA, __local const ShapeDeviceData* shapeB, + __local int* lNContactsA, __local float4* lCPointsA, + __local int* lNContactsB, __local float4* lCPointsB, float collisionMargin ) +{ + int pIdx = GET_LOCAL_IDX; + + while( pIdx < HEIGHT_RES*HEIGHT_RES*6*2 ) + { + __local const BodyData* bodyAPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?bodyA:bodyB; + __local const BodyData* bodyBPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?bodyB:bodyA; + __local const ShapeDeviceData* shapeAPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?shapeA:shapeB; + __local const ShapeDeviceData* shapeBPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?shapeB:shapeA; + __local int* lNContacts =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?lNContactsA:lNContactsB; + __local float4* lCPoints =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?lCPointsA:lCPointsB; + + float4 bodyAPos = bodyAPtr->m_pos; + float4 bodyBPos = bodyBPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + Quaternion bodyBQuat = bodyBPtr->m_quat; + + float4 pInB = ShapeDataCalcSamplePoint( shapeBPtr, pIdx%(HEIGHT_RES*HEIGHT_RES*6) ); + + float4 pInW = transform( &pInB, &bodyBPos, &bodyBQuat ); +// Aabb bodyAAabb = bodyAPtr->m_aabb; +// if( AabbOverlapsPoint( &bodyAAabb, pInW ) ) + { + float4 pInA = invTransform( &pInW, &bodyAPos, &bodyAQuat ); + + float dist = ShapeDataQueryDistance( shapeAPtr, pInA ); + if( dist < collisionMargin ) + { + int dstIdx = atom_add( lNContacts, 1 ); + if( dstIdx < 32 ) + { + lCPoints[ dstIdx ] = make_float4( pInA.x, pInA.y, pInA.z, dist ); + } + } + } + + pIdx += GET_GROUP_SIZE; + } +} + +void testVtxWithPlane(__local BodyData* bodyA, __local BodyData* bodyB, + float4 nA, __local ShapeDeviceData* shapeB, + __local int* lNContactsA, __local float4* lCPointsA, float collisionMargin) +{ + int pIdx = GET_LOCAL_IDX; + + while( pIdx < HEIGHT_RES*HEIGHT_RES*6 ) + { + __local BodyData* bodyAPtr =bodyA; + __local BodyData* bodyBPtr =bodyB; + __local ShapeDeviceData* shapeBPtr =shapeB; + __local int* lNContacts =lNContactsA; + __local float4* lCPoints =lCPointsA; + + float4 bodyAPos = bodyAPtr->m_pos; + float4 bodyBPos = bodyBPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + Quaternion bodyBQuat = bodyBPtr->m_quat; + + float4 pInB = ShapeDataCalcSamplePoint( shapeBPtr, pIdx%(HEIGHT_RES*HEIGHT_RES*6) ); + + float4 pInW = transform( &pInB, &bodyBPos, &bodyBQuat ); + { + float4 pInA = invTransform( &pInW, &bodyAPos, &bodyAQuat ); + + float dist = dot3w1( pInA, nA );//ShapeDataQueryDistance( shapeAPtr, pInA ); + if( dist < collisionMargin ) + { + int dstIdx = atom_add( lNContacts, 1 ); + if( dstIdx < 32 ) + { + lCPoints[ dstIdx ] = make_float4( pInA.x, pInA.y, pInA.z, dist ); + } + } + } + + pIdx += GET_GROUP_SIZE; + } +} + +#define GET_SHAPE_IDX(x) (int)((x).m_shapeIdx) + +void output(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr, + __local int2* iPair, + __local int* lNContacts, __local float4* lCPoints, + float4 center, + __global ShapeData* shapeData, __global Contact4* contactsOut, float collisionMargin) +{ + if( *lNContacts != 0 ) + { + int nContacts = min2( *lNContacts, 4 ); + + __global Contact4* c = contactsOut; + + if( GET_LOCAL_IDX < nContacts ) + { + int i = GET_LOCAL_IDX; + float4 p = lCPoints[i]; + float4 bodyAPos = bodyAPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + + c->m_worldPos[i] = transform( &p, &bodyAPos, &bodyAQuat ); + c->m_worldPos[i].w = lCPoints[i].w - collisionMargin; + } + + if( GET_LOCAL_IDX == 0 ) + { + float4 contactNormal; + contactNormal = ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center ); + contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) ); + + c->m_worldNormal = contactNormal; +// c->m_restituitionCoeff = 0.f; +// c->m_frictionCoeff = 0.7f; + c->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16); + GET_NPOINTS(*c) = nContacts; + c->m_bodyAPtr = iPair[0].x; + c->m_bodyBPtr = iPair[0].y; + } + } + else + { + if( GET_LOCAL_IDX == 0 ) + GET_NPOINTS(contactsOut[0]) = 0; + } +} + +// todo. make it better +void output2(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr, + int pair0, int pair1, + __local int* lNContacts, __local float4* lCPoints, + float4 center, + const __global ShapeData* shapeData, __global Contact4* contactsOut, counter32_t nContactsOut, int capacity, + float collisionMargin ) +{ + int lIdx = GET_LOCAL_IDX%32; + int nContacts = min2( *lNContacts, 4 ); + + GROUP_LDS_BARRIER; + + if( lIdx == 0 && nContacts) + { + int dstIdx; + AppendInc( nContactsOut, dstIdx ); + *lNContacts = dstIdx; + + if( dstIdx >= capacity ) + *lNContacts = -1; + } + + GROUP_LDS_BARRIER; + + bool canWrite = (*lNContacts!=-1); + + if( nContacts && canWrite ) + { + __global Contact4* c = contactsOut + (*lNContacts); + + if( lIdx < nContacts ) + { + int i = lIdx; + float4 p = lCPoints[i]; + float4 bodyAPos = bodyAPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + + p = transform( &p, &bodyAPos, &bodyAQuat ); + p.w = lCPoints[i].w - collisionMargin; + c->m_worldPos[i] = p; + } + + if( lIdx == 0 ) + { + if( nContacts ) + { + float4 contactNormal; + contactNormal = ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center ); + contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) ); + + c->m_worldNormal = contactNormal; +// c->m_restituitionCoeff = 0.f; +// c->m_frictionCoeff = 0.7f; + c->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16); + c->m_bodyAPtr = pair0; + c->m_bodyBPtr = pair1; + } + GET_NPOINTS(*c) = nContacts; + } + } +} + +__inline +void output2LDS(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr, + int pair0, int pair1, + int lNContacts, __local float4* lCPoints, + float4 center, + const __global ShapeData* shapeData, __local Contact4* contactsOut, + float collisionMargin ) +{ + int lIdx = GET_LOCAL_IDX%32; +// int lIdx = GET_LOCAL_IDX; +// int groupIdx = 0; + + int nContacts = min2( lNContacts, 4 ); + + GROUP_LDS_BARRIER; + + if( nContacts != 0 ) + { + if( lIdx < nContacts ) + { + int i = lIdx; + float4 p = lCPoints[i]; + float4 bodyAPos = bodyAPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + + p = transform( &p, &bodyAPos, &bodyAQuat ); + p.w = lCPoints[i].w - collisionMargin; + contactsOut->m_worldPos[i] = p; + } + } + + if( lIdx == 0 ) + { + if( nContacts != 0 ) + { + float4 contactNormal; + contactNormal = ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center ); + contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) ); + + contactsOut->m_worldNormal = contactNormal; +// contactsOut->m_worldNormal = make_float4(1.5f,1.4f,1.3f,0.f); +// contactsOut->m_restituitionCoeff = 0.f; +// contactsOut->m_frictionCoeff = 0.7f; + contactsOut->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16); + contactsOut->m_bodyAPtr = pair0; + contactsOut->m_bodyBPtr = pair1; + } + GET_NPOINTS(*contactsOut) = nContacts;//nContacts; + } + +// contactsOut[groupIdx].m_worldNormal = make_float4(1.5f,1.4f,1.3f,0.f); +} + +void output2_1(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr, + int pair0, int pair1, + __local int* lNContacts, __local float4* lCPoints, + float4 center, float4 nA, + const __global ShapeData* shapeData, __global Contact4* contactsOut, counter32_t nContactsOut, int capacity, float collisionMargin ) +{ + int lIdx = GET_LOCAL_IDX; + int nContacts = min2( *lNContacts, 4 ); + + GROUP_LDS_BARRIER; + + if( lIdx == 0 && nContacts) + { + int dstIdx; + AppendInc( nContactsOut, dstIdx ); + *lNContacts = dstIdx; + + if( dstIdx >= capacity ) + *lNContacts = -1; + } + + GROUP_LDS_BARRIER; + + bool canWrite = (*lNContacts!=-1); + + if( nContacts && canWrite ) + { + __global Contact4* c = contactsOut + (*lNContacts); + + if( lIdx < nContacts ) + { + int i = lIdx; + float4 p = lCPoints[i]; + float4 bodyAPos = bodyAPtr->m_pos; + Quaternion bodyAQuat = bodyAPtr->m_quat; + + p = transform( &p, &bodyAPos, &bodyAQuat ); + p.w = lCPoints[i].w - collisionMargin; + c->m_worldPos[i] = p; + } + + if( lIdx == 0 ) + { + if( nContacts ) + { + float4 contactNormal; + contactNormal = nA;//ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center ); + contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) ); + + c->m_worldNormal = contactNormal; +// c->m_restituitionCoeff = 0.f; +// c->m_frictionCoeff = 0.7f; + c->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16); + c->m_bodyAPtr = pair0; + c->m_bodyBPtr = pair1; + } + GET_NPOINTS(*c) = nContacts; + } + } +} + +__kernel +void manifold(__global float4* vIn, __global float4* vOut) +{ + __local float4 lCPoints[32]; + __local float4 lManifoldBuffer[64]; + __local int lNContacts; + __local float4 ab; + + if( GET_LOCAL_IDX<32 ) + { + lCPoints[GET_LOCAL_IDX] = vIn[GET_GLOBAL_IDX]; + } + + if( GET_LOCAL_IDX == 0 ) + { + lNContacts = 32; + ab = vIn[GET_GLOBAL_IDX]; + } + + GROUP_LDS_BARRIER; + + float4 center = extractManifold( lCPoints, lManifoldBuffer, &lNContacts, ab ); + + if( GET_LOCAL_IDX < lNContacts ) + { + vOut[4*GET_GROUP_IDX+GET_LOCAL_IDX] = lCPoints[GET_LOCAL_IDX]; + } + +} + +//#define COMBINE_REDUCTION + +__kernel +__attribute__((reqd_work_group_size(64, 1, 1))) +void NarrowphaseKernel( const __global int2* restrict pairs, const __global ShapeData* shapeData, const __global BodyData* restrict bodyDatas, + __global Contact4* restrict contactsOut, + counter32_t nContactsOut, ConstBuffer cb ) +{ + // 2.5K LDS + __local Contact4 ldsContacts[2]; + __local BodyData bodyA; + __local BodyData bodyB; + __local ShapeDeviceData shapeA; + __local ShapeDeviceData shapeB; + __local float4 lCPointsA[32*2]; + __local int lNContactsA; + __local float4* lCPointsB = lCPointsA+32; + __local int lNContactsB; +#ifdef COMBINE_REDUCTION + __local float4 lManifoldBuffer[64*2]; +#else + __local float4 lManifoldBuffer[64]; +#endif + __local int2 iPairAB; + + const int capacity = cb.m_capacity; + const float collisionMargin = cb.m_collisionMargin; + + + int pairIdx = GET_GROUP_IDX; +// for(int pairIdx = GET_GROUP_IDX; pairIdxm_height4[idx] = shapeData[ myShapeIdx ].m_height4[idx]; + + idx+=32; + } + } + + GROUP_LDS_BARRIER; + + testVtx2( &bodyA, &bodyB, &shapeA, &shapeB, &lNContactsA, lCPointsA, &lNContactsB, lCPointsB, collisionMargin ); + + GROUP_LDS_BARRIER; + + float4 ab = bodyB.m_pos - bodyA.m_pos; + float4 center[2]; + + if( lNContactsA != 0 || lNContactsB != 0 ) + { + float4 abInA; + abInA = qtInvRotate( bodyA.m_quat, ab ); + + float4 abInB; + abInB = qtInvRotate( bodyB.m_quat, ab ); + +#ifdef COMBINE_REDUCTION + extractManifold2( lCPointsA, &lNContactsA, abInA, + lCPointsB, &lNContactsB, abInB, + lManifoldBuffer, center ); +#else + if( lNContactsA != 0 ) + center[0] = extractManifold( lCPointsA, lManifoldBuffer, &lNContactsA, abInA ); + if( lNContactsB != 0 ) + center[1] = extractManifold( lCPointsB, lManifoldBuffer, &lNContactsB, abInB ); +#endif + } + + int firstSet = GET_LOCAL_IDX/32; + +/* + if( GET_LOCAL_IDX == 0 ) // for debug + { + ldsContacts[0].m_worldNormal = make_float4(-1,-1,-1,0); + ldsContacts[0].m_bodyAPtr = 0; + ldsContacts[0].m_bodyBPtr = 0; + ldsContacts[0].m_batchIdx = 111; + ldsContacts[1].m_worldNormal = make_float4(-1,-1,-1,0); + ldsContacts[1].m_bodyAPtr = 0; + ldsContacts[1].m_bodyBPtr = 0; + ldsContacts[1].m_batchIdx = 111; + } +*/ + bool doReduction = true; + if( doReduction ) + { + GROUP_LDS_BARRIER; + + output2LDS( (firstSet)?&bodyA: &bodyB, (firstSet)?&bodyB : &bodyA, + (firstSet)?iPairAB.x : iPairAB.y, (firstSet)?iPairAB.y : iPairAB.x, + (firstSet)?lNContactsA : lNContactsB, (firstSet)?lCPointsA:lCPointsB, + (firstSet)?center[0] : center[1], shapeData, (firstSet)?&ldsContacts[0]: &ldsContacts[1], collisionMargin ); + + GROUP_LDS_BARRIER; + + if( GET_LOCAL_IDX == 0 ) + { + if( lNContactsA && lNContactsB ) + { + float nDotn = dot3F4( ldsContacts[0].m_worldNormal, ldsContacts[1].m_worldNormal ); + if( nDotn < -(1.f-0.01f) ) + { + if( ldsContacts[0].m_bodyAPtr > ldsContacts[1].m_bodyAPtr ) + lNContactsA = 0; + else + lNContactsB = 0; + } + } + } + + if( GET_LOCAL_IDX == 0 ) + { + int n = lNContactsA; + if( n != 0 ) + { + int dstIdx; + AppendInc( nContactsOut, dstIdx ); + if( dstIdx < capacity ) + { int idx = 0; + contactsOut[ dstIdx ] = ldsContacts[idx]; + contactsOut[ dstIdx].m_batchIdx = pairIdx; + } + } + + n = lNContactsB; + if( n != 0 ) + { + int dstIdx; + AppendInc( nContactsOut, dstIdx ); + if( dstIdx < capacity ) + { int idx = 1; + contactsOut[ dstIdx ] = ldsContacts[idx]; + contactsOut[ dstIdx].m_batchIdx = pairIdx; + } + } + } + + GROUP_LDS_BARRIER; + } + else + { + //output2( (firstSet)?&bodyA: &bodyB, (firstSet)?&bodyB : &bodyA, + // (firstSet)?iPairAB.x : iPairAB.y, (firstSet)?iPairAB.y : iPairAB.x, + // (firstSet)?&lNContactsA : &lNContactsB, (firstSet)?lCPointsA:lCPointsB, + // (firstSet)?center[0] : center[1], shapeData, contactsOut, nContactsOut, capacity, collisionMargin ); + } + } +} + + +__kernel +__attribute__((reqd_work_group_size(64, 1, 1))) +void NarrowphaseWithPlaneKernel( const __global int2* restrict pairs, const __global ShapeData* shapeData, const __global BodyData* restrict bodyDatas, + __global Contact4* restrict contactsOut, + counter32_t nContactsOut, ConstBuffer cb ) +{ + // 2.5K LDS + __local BodyData bodyA; + __local BodyData bodyB; + __local ShapeDeviceData shapeA; + __local ShapeDeviceData shapeB; + __local float4 lCPointsA[32*2]; + __local int lNContactsA; +// __local float4* lCPointsB = lCPointsA+32; +// __local int lNContactsB; + __local float4 lManifoldBuffer[64]; + __local int2 iPairAB; + + const int capacity = cb.m_capacity; + const float collisionMargin = cb.m_collisionMargin; + + int pairIdx = GET_GROUP_IDX; + { + if( GET_LOCAL_IDX == 0 ) // load Bodies + { + int2 pair = pairs[pairIdx]; + iPairAB = make_int2(pair.x, pair.y); + bodyA = bodyDatas[ pair.x ]; + bodyB = bodyDatas[ pair.y ]; + shapeA.m_scale = shapeData[ GET_SHAPE_IDX(bodyA) ].m_scale; + shapeB.m_scale = shapeData[ GET_SHAPE_IDX(bodyB) ].m_scale; + lNContactsA = 0; +// lNContactsB = 0; + } + + GROUP_LDS_BARRIER; + + if (bodyB.m_invMass == 0.f) + return; + + // todo. can check if the shape is the same to previous one. If same, dont read + { // load shape data + int idx = GET_LOCAL_IDX%32; + int bIdx = GET_LOCAL_IDX/32; + __local ShapeDeviceData* myShape = (bIdx==0)?&shapeA: &shapeB; + int myShapeIdx = (bIdx==0)?GET_SHAPE_IDX(bodyA): GET_SHAPE_IDX(bodyB); + + while( idx < HEIGHT_RES*HEIGHT_RES*6/4 ) + { + myShape->m_height4[idx] = shapeData[ myShapeIdx ].m_height4[idx]; + + idx+=32; + } + } + + GROUP_LDS_BARRIER; + + float4 nA = make_float4(0,1,0,0); + + +// testVtx2( &bodyA, &bodyB, &shapeA, &shapeB, &lNContactsA, lCPointsA, &lNContactsB, lCPointsB ); + testVtxWithPlane( &bodyA, &bodyB, nA, &shapeB, &lNContactsA, lCPointsA, collisionMargin ); + + GROUP_LDS_BARRIER; + +// float4 ab = bodyB.m_pos - bodyA.m_pos; + float4 center[2]; + + if( lNContactsA != 0 ) + { + float4 abInA; + abInA = nA;//qtInvRotate( bodyA.m_quat, ab ); + + if( lNContactsA != 0 ) + center[0] = extractManifold( lCPointsA, lManifoldBuffer, &lNContactsA, abInA ); + } + +// int firstSet = GET_LOCAL_IDX/32; + + output2_1( &bodyA, &bodyB, + iPairAB.x, iPairAB.y, + &lNContactsA, lCPointsA, + center[0], nA, shapeData, contactsOut, nContactsOut, capacity, collisionMargin ); + } +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.h new file mode 100644 index 000000000..ff846cb60 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/ChNarrowphaseKernels.h @@ -0,0 +1,1616 @@ +static const char* narrowphaseKernelsCL= \ +"\n" +"#pragma OPENCL EXTENSION cl_amd_printf : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable\n" +"#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_extended_atomics : enable\n" +"#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable\n" +"\n" +"#ifdef cl_ext_atomic_counters_32\n" +"#pragma OPENCL EXTENSION cl_ext_atomic_counters_32 : enable\n" +"#else\n" +"#define counter32_t volatile global int*\n" +"#endif\n" +"\n" +"\n" +"typedef unsigned int u32;\n" +"typedef unsigned short u16;\n" +"typedef unsigned char u8;\n" +"\n" +"#define GET_GROUP_IDX get_group_id(0)\n" +"#define GET_LOCAL_IDX get_local_id(0)\n" +"#define GET_GLOBAL_IDX get_global_id(0)\n" +"#define GET_GROUP_SIZE get_local_size(0)\n" +"#define GET_NUM_GROUPS get_num_groups(0)\n" +"#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE)\n" +"#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE)\n" +"#define AtomInc(x) atom_inc(&(x))\n" +"#define AtomInc1(x, out) out = atom_inc(&(x))\n" +"#define AppendInc(x, out) out = atomic_inc(x)\n" +"#define AtomAdd(x, value) atom_add(&(x), value)\n" +"#define AtomCmpxhg(x, cmp, value) atom_cmpxchg( &(x), cmp, value )\n" +"#define AtomXhg(x, value) atom_xchg ( &(x), value )\n" +"\n" +"\n" +"#define SELECT_UINT4( b, a, condition ) select( b,a,condition )\n" +"\n" +"#define make_float4 (float4)\n" +"#define make_float2 (float2)\n" +"#define make_uint4 (uint4)\n" +"#define make_int4 (int4)\n" +"#define make_uint2 (uint2)\n" +"#define make_int2 (int2)\n" +"\n" +"\n" +"#define max2 max\n" +"#define min2 min\n" +"\n" +"\n" +"///////////////////////////////////////\n" +"// Vector\n" +"///////////////////////////////////////\n" +"__inline\n" +"float fastDiv(float numerator, float denominator)\n" +"{\n" +" return native_divide(numerator, denominator); \n" +"// return numerator/denominator; \n" +"}\n" +"\n" +"__inline\n" +"float4 fastDiv4(float4 numerator, float4 denominator)\n" +"{\n" +" return native_divide(numerator, denominator); \n" +"}\n" +"\n" +"__inline\n" +"float fastSqrtf(float f2)\n" +"{\n" +" return native_sqrt(f2);\n" +"// return sqrt(f2);\n" +"}\n" +"\n" +"__inline\n" +"float fastRSqrt(float f2)\n" +"{\n" +" return native_rsqrt(f2);\n" +"}\n" +"\n" +"__inline\n" +"float fastLength4(float4 v)\n" +"{\n" +" return fast_length(v);\n" +"}\n" +"\n" +"__inline\n" +"float4 fastNormalize4(float4 v)\n" +"{\n" +" return fast_normalize(v);\n" +"}\n" +"\n" +"\n" +"__inline\n" +"float sqrtf(float a)\n" +"{\n" +"// return sqrt(a);\n" +" return native_sqrt(a);\n" +"}\n" +"\n" +"__inline\n" +"float4 cross3(float4 a, float4 b)\n" +"{\n" +" return cross(a,b);\n" +"}\n" +"\n" +"__inline\n" +"float dot3F4(float4 a, float4 b)\n" +"{\n" +" float4 a1 = make_float4(a.xyz,0.f);\n" +" float4 b1 = make_float4(b.xyz,0.f);\n" +" return dot(a1, b1);\n" +"}\n" +"\n" +"__inline\n" +"float length3(const float4 a)\n" +"{\n" +" return sqrtf(dot3F4(a,a));\n" +"}\n" +"\n" +"__inline\n" +"float dot4(const float4 a, const float4 b)\n" +"{\n" +" return dot( a, b );\n" +"}\n" +"\n" +"// for height\n" +"__inline\n" +"float dot3w1(const float4 point, const float4 eqn)\n" +"{\n" +" return dot3F4(point,eqn) + eqn.w;\n" +"}\n" +"\n" +"__inline\n" +"float4 normalize3(const float4 a)\n" +"{\n" +" float4 n = make_float4(a.x, a.y, a.z, 0.f);\n" +" return fastNormalize4( n );\n" +"// float length = sqrtf(dot3F4(a, a));\n" +"// return 1.f/length * a;\n" +"}\n" +"\n" +"__inline\n" +"float4 normalize4(const float4 a)\n" +"{\n" +" float length = sqrtf(dot4(a, a));\n" +" return 1.f/length * a;\n" +"}\n" +"\n" +"__inline\n" +"float4 createEquation(const float4 a, const float4 b, const float4 c)\n" +"{\n" +" float4 eqn;\n" +" float4 ab = b-a;\n" +" float4 ac = c-a;\n" +" eqn = normalize3( cross3(ab, ac) );\n" +" eqn.w = -dot3F4(eqn,a);\n" +" return eqn;\n" +"}\n" +"\n" +"///////////////////////////////////////\n" +"// Matrix3x3\n" +"///////////////////////////////////////\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_row[3];\n" +"}Matrix3x3;\n" +"\n" +"__inline\n" +"Matrix3x3 mtZero();\n" +"\n" +"__inline\n" +"Matrix3x3 mtIdentity();\n" +"\n" +"__inline\n" +"Matrix3x3 mtTranspose(Matrix3x3 m);\n" +"\n" +"__inline\n" +"Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b);\n" +"\n" +"__inline\n" +"float4 mtMul1(Matrix3x3 a, float4 b);\n" +"\n" +"__inline\n" +"float4 mtMul3(float4 a, Matrix3x3 b);\n" +"\n" +"__inline\n" +"Matrix3x3 mtZero()\n" +"{\n" +" Matrix3x3 m;\n" +" m.m_row[0] = (float4)(0.f);\n" +" m.m_row[1] = (float4)(0.f);\n" +" m.m_row[2] = (float4)(0.f);\n" +" return m;\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 mtIdentity()\n" +"{\n" +" Matrix3x3 m;\n" +" m.m_row[0] = (float4)(1,0,0,0);\n" +" m.m_row[1] = (float4)(0,1,0,0);\n" +" m.m_row[2] = (float4)(0,0,1,0);\n" +" return m;\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 mtTranspose(Matrix3x3 m)\n" +"{\n" +" Matrix3x3 out;\n" +" out.m_row[0] = (float4)(m.m_row[0].x, m.m_row[1].x, m.m_row[2].x, 0.f);\n" +" out.m_row[1] = (float4)(m.m_row[0].y, m.m_row[1].y, m.m_row[2].y, 0.f);\n" +" out.m_row[2] = (float4)(m.m_row[0].z, m.m_row[1].z, m.m_row[2].z, 0.f);\n" +" return out;\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b)\n" +"{\n" +" Matrix3x3 transB;\n" +" transB = mtTranspose( b );\n" +" Matrix3x3 ans;\n" +" // why this doesn't run when 0ing in the for{}\n" +" a.m_row[0].w = 0.f;\n" +" a.m_row[1].w = 0.f;\n" +" a.m_row[2].w = 0.f;\n" +" for(int i=0; i<3; i++)\n" +" {\n" +"// a.m_row[i].w = 0.f;\n" +" ans.m_row[i].x = dot3F4(a.m_row[i],transB.m_row[0]);\n" +" ans.m_row[i].y = dot3F4(a.m_row[i],transB.m_row[1]);\n" +" ans.m_row[i].z = dot3F4(a.m_row[i],transB.m_row[2]);\n" +" ans.m_row[i].w = 0.f;\n" +" }\n" +" return ans;\n" +"}\n" +"\n" +"__inline\n" +"float4 mtMul1(Matrix3x3 a, float4 b)\n" +"{\n" +" float4 ans;\n" +" ans.x = dot3F4( a.m_row[0], b );\n" +" ans.y = dot3F4( a.m_row[1], b );\n" +" ans.z = dot3F4( a.m_row[2], b );\n" +" ans.w = 0.f;\n" +" return ans;\n" +"}\n" +"\n" +"__inline\n" +"float4 mtMul3(float4 a, Matrix3x3 b)\n" +"{\n" +" float4 colx = make_float4(b.m_row[0].x, b.m_row[1].x, b.m_row[2].x, 0);\n" +" float4 coly = make_float4(b.m_row[0].y, b.m_row[1].y, b.m_row[2].y, 0);\n" +" float4 colz = make_float4(b.m_row[0].z, b.m_row[1].z, b.m_row[2].z, 0);\n" +"\n" +" float4 ans;\n" +" ans.x = dot3F4( a, colx );\n" +" ans.y = dot3F4( a, coly );\n" +" ans.z = dot3F4( a, colz );\n" +" return ans;\n" +"}\n" +"\n" +"///////////////////////////////////////\n" +"// Quaternion\n" +"///////////////////////////////////////\n" +"\n" +"typedef float4 Quaternion;\n" +"\n" +"__inline\n" +"Quaternion qtMul(Quaternion a, Quaternion b);\n" +"\n" +"__inline\n" +"Quaternion qtNormalize(Quaternion in);\n" +"\n" +"__inline\n" +"float4 qtRotate(Quaternion q, float4 vec);\n" +"\n" +"__inline\n" +"Quaternion qtInvert(Quaternion q);\n" +"\n" +"__inline\n" +"Matrix3x3 qtGetRotationMatrix(Quaternion q);\n" +"\n" +"\n" +"\n" +"__inline\n" +"Quaternion qtMul(Quaternion a, Quaternion b)\n" +"{\n" +" Quaternion ans;\n" +" ans = cross3( a, b );\n" +" ans += a.w*b+b.w*a;\n" +"// ans.w = a.w*b.w - (a.x*b.x+a.y*b.y+a.z*b.z);\n" +" ans.w = a.w*b.w - dot3F4(a, b);\n" +" return ans;\n" +"}\n" +"\n" +"__inline\n" +"Quaternion qtNormalize(Quaternion in)\n" +"{\n" +" return fastNormalize4(in);\n" +"// in /= length( in );\n" +"// return in;\n" +"}\n" +"__inline\n" +"float4 qtRotate(Quaternion q, float4 vec)\n" +"{\n" +" Quaternion qInv = qtInvert( q );\n" +" float4 vcpy = vec;\n" +" vcpy.w = 0.f;\n" +" float4 out = qtMul(qtMul(q,vcpy),qInv);\n" +" return out;\n" +"}\n" +"\n" +"__inline\n" +"Quaternion qtInvert(Quaternion q)\n" +"{\n" +" return (Quaternion)(-q.xyz, q.w);\n" +"}\n" +"\n" +"__inline\n" +"float4 qtInvRotate(const Quaternion q, float4 vec)\n" +"{\n" +" return qtRotate( qtInvert( q ), vec );\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 qtGetRotationMatrix(Quaternion quat)\n" +"{\n" +" float4 quat2 = (float4)(quat.x*quat.x, quat.y*quat.y, quat.z*quat.z, 0.f);\n" +" Matrix3x3 out;\n" +"\n" +" out.m_row[0].x=1-2*quat2.y-2*quat2.z;\n" +" out.m_row[0].y=2*quat.x*quat.y-2*quat.w*quat.z;\n" +" out.m_row[0].z=2*quat.x*quat.z+2*quat.w*quat.y;\n" +" out.m_row[0].w = 0.f;\n" +"\n" +" out.m_row[1].x=2*quat.x*quat.y+2*quat.w*quat.z;\n" +" out.m_row[1].y=1-2*quat2.x-2*quat2.z;\n" +" out.m_row[1].z=2*quat.y*quat.z-2*quat.w*quat.x;\n" +" out.m_row[1].w = 0.f;\n" +"\n" +" out.m_row[2].x=2*quat.x*quat.z-2*quat.w*quat.y;\n" +" out.m_row[2].y=2*quat.y*quat.z+2*quat.w*quat.x;\n" +" out.m_row[2].z=1-2*quat2.x-2*quat2.y;\n" +" out.m_row[2].w = 0.f;\n" +"\n" +" return out;\n" +"}\n" +"\n" +"\n" +"#define WG_SIZE 64\n" +"#define HEIGHT_RES 4\n" +"#define SHAPE_CONVEX_HEIGHT_FIELD 1//keep this in sync with AdlCollisionShape.h!\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_normal[HEIGHT_RES*HEIGHT_RES*6];\n" +" u32 m_height4[HEIGHT_RES*HEIGHT_RES*6];\n" +" u32 m_supportHeight4[HEIGHT_RES*HEIGHT_RES*6];\n" +"\n" +" float m_scale;\n" +" float m_padding0;\n" +" float m_padding1;\n" +" float m_padding2;\n" +"} ShapeData;\n" +"\n" +"typedef struct\n" +"{\n" +" u32 m_height4[HEIGHT_RES*HEIGHT_RES*6/4];\n" +"\n" +" float m_scale;\n" +"} ShapeDeviceData;\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_pos;\n" +" float4 m_quat;\n" +" float4 m_linVel;\n" +" float4 m_angVel;\n" +"\n" +" u32 m_shapeIdx;\n" +" u32 m_shapeType;\n" +" \n" +" float m_invMass;\n" +" float m_restituitionCoeff;\n" +" float m_frictionCoeff;\n" +"} BodyData;\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_worldPos[4];\n" +" float4 m_worldNormal; // w: m_nPoints\n" +"// float m_restituitionCoeff;\n" +"// float m_frictionCoeff;\n" +" u32 m_coeffs;\n" +" u32 m_batchIdx;\n" +"// int m_nPoints;\n" +"// int m_padding0;\n" +"\n" +" u32 m_bodyAPtr;//x:m_bodyAPtr, y:m_bodyBPtr\n" +" u32 m_bodyBPtr;\n" +"} Contact4;\n" +"\n" +"#define GET_NPOINTS(x) (x).m_worldNormal.w\n" +"\n" +"\n" +"typedef struct\n" +"{\n" +" int m_nPairs;\n" +" float m_collisionMargin;\n" +" int m_capacity;\n" +" int m_paddings[1];\n" +"} ConstBuffer;\n" +"\n" +"__inline\n" +"float4 transform(const float4* p, const float4* translation, const Quaternion* orientation)\n" +"{\n" +" return qtRotate( *orientation, *p ) + (*translation);\n" +"}\n" +"\n" +"__inline\n" +"float4 invTransform(const float4* p, const float4* translation, const Quaternion* orientation)\n" +"{\n" +" return qtRotate( qtInvert( *orientation ), (*p)-(*translation) ); // use qtInvRotate\n" +"}\n" +"\n" +"void CubeMapUtilsCalcCrd(const float4 p, int* faceIdxOut, float* x, float* y)\n" +"{\n" +" {\n" +" int idx;\n" +" float r2[] = {p.x*p.x, p.y*p.y, p.z*p.z};\n" +"\n" +" if (r2[1]>r2[0])\n" +" {\n" +" if (r2[2]>r2[1])\n" +" {\n" +" idx = 2;\n" +" \n" +" } else\n" +" {\n" +" idx = 1;\n" +" }\n" +" \n" +" } else\n" +" {\n" +" if (r2[2]>r2[0])\n" +" {\n" +" idx = 2;\n" +" } else\n" +" {\n" +" idx = 0;\n" +" }\n" +" }\n" +"\n" +" *faceIdxOut = (idx*2);\n" +"//==\n" +" float4 abs = make_float4( fabs(p.x), fabs(p.y), fabs(p.z), 0.f );\n" +"\n" +" float d;\n" +" if( idx == 0 )\n" +" {\n" +" *x = p.y;\n" +" *y = p.z;\n" +" d = abs.x;\n" +" *faceIdxOut += (p.x < 0.f)? 0: 1.f;\n" +" }\n" +" else if( idx == 1 )\n" +" {\n" +" *x = p.z;\n" +" *y = p.x;\n" +" d = abs.y;\n" +" *faceIdxOut += (p.y < 0.f)? 0: 1.f;\n" +" }\n" +" else\n" +" {\n" +" *x = p.x;\n" +" *y = p.y;\n" +" d = abs.z;\n" +" *faceIdxOut += (p.z < 0.f)? 0: 1.f;\n" +" }\n" +"\n" +" float dInv = (d==0.f)? 0.f: fastDiv(1.f,d);\n" +" *x = (*x*dInv+1.f)*0.5f;\n" +" *y = (*y*dInv+1.f)*0.5f;\n" +" }\n" +"}\n" +"\n" +"float4 CubeMapUtilsCalcVector(int faceIdx, float x, float y)\n" +"{\n" +" int dir = faceIdx/2;\n" +" float z = (faceIdx%2 == 0)? -1.f:1.f;\n" +"\n" +" x = x*2.f-1.f;\n" +" y = y*2.f-1.f;\n" +" \n" +" if( dir == 0 )\n" +" {\n" +" return make_float4(z, x, y, 0.f);\n" +" }\n" +" else if( dir == 1 )\n" +" {\n" +" return make_float4(y,z,x, 0.f);\n" +" }\n" +" else\n" +" {\n" +" return make_float4(x,y,z, 0.f);\n" +" }\n" +"}\n" +"\n" +"typedef int Face;\n" +"\n" +"u32 sample(__local ShapeDeviceData* shape, int face, int x, int y)\n" +"{\n" +"\n" +" int idx = HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES;\n" +" __local u8* height = (__local u8*)shape->m_height4;\n" +" return height[idx];\n" +"}\n" +"\n" +"u32 sampleSupportGlobal(__global ShapeData* shape, int face, int x, int y)\n" +"{\n" +"\n" +" int idx = HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES;\n" +" __global u8* height = (__global u8*)shape->m_supportHeight4;\n" +" return height[idx];\n" +"}\n" +"\n" +"float4 sampleNormal(__local ShapeData* shape, int face, int x, int y)\n" +"{\n" +" return shape->m_normal[HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES];\n" +"}\n" +"\n" +"float4 sampleNormalGlobal(const __global ShapeData* shape, int face, int x, int y)\n" +"{\n" +" return shape->m_normal[HEIGHT_RES*HEIGHT_RES*face + x + y*HEIGHT_RES];\n" +"}\n" +"\n" +"float4 ShapeDataCalcSamplePoint( __local const ShapeDeviceData* shape, int sIdx )//u8 height, int sIdx, float scale )\n" +"{\n" +" const float oneOver255 = 1.f/255.f;\n" +"\n" +" int faceIdx = fastDiv(sIdx,(HEIGHT_RES*HEIGHT_RES));\n" +" int r = (sIdx%(HEIGHT_RES*HEIGHT_RES));\n" +" int i = r/HEIGHT_RES;\n" +" int j = r%HEIGHT_RES;\n" +"\n" +" float4 v;\n" +" float x = fastDiv((i+0.5f),(float)HEIGHT_RES);\n" +" float y = fastDiv((j+0.5f),(float)HEIGHT_RES);\n" +" v = CubeMapUtilsCalcVector(faceIdx, x, y);\n" +" v = normalize3( v );\n" +"\n" +" int quantizedHeight = sample( shape, faceIdx, i, j );\n" +" float rheight = quantizedHeight*oneOver255*shape->m_scale;\n" +" return rheight*v;\n" +"}\n" +"\n" +"float ShapeDataQueryDistance(__local const ShapeDeviceData* shape, float4 p )\n" +"{\n" +" if( dot3F4( p, p ) >= shape->m_scale*shape->m_scale ) return FLT_MAX;\n" +"\n" +" const float oneOver255 = 1.f/255.f;\n" +"\n" +" int faceIdx;\n" +" float x, y;\n" +" CubeMapUtilsCalcCrd( p, &faceIdx, &x, &y );\n" +" x = (x*HEIGHT_RES) - 0.5f;\n" +" y = (y*HEIGHT_RES) - 0.5f;\n" +"\n" +" float height;\n" +" {\n" +" int xi = (int)(x);\n" +" int yi = (int)(y);\n" +" float dx = x-xi;\n" +" float dy = y-yi;\n" +"\n" +" {\n" +" int xip = min2((int)(HEIGHT_RES-1), xi+1);\n" +" int yip = min2((int)(HEIGHT_RES-1), yi+1);\n" +"\n" +" u32 xy = sample( shape, faceIdx, xi, yi );\n" +" u32 xpy = sample( shape, faceIdx, xip, yi );\n" +" u32 xpyp = sample( shape, faceIdx, xip, yip );\n" +" u32 xyp = sample( shape, faceIdx, xi, yip );\n" +"\n" +" height = (xy*(1.f-dx)+xpy*dx)*(1.f-dy) + (xyp*(1.f-dx)+xpyp*dx)*dy;\n" +" height = height*oneOver255*shape->m_scale;\n" +"\n" +" p.w = 0.f;\n" +"\n" +" height = fastLength4( p ) - height;\n" +" }\n" +" }\n" +"\n" +" return height;\n" +"}\n" +"\n" +"float ShapeDataQuerySupportHeight(__global ShapeData* shape, float4 p )\n" +"{\n" +" int faceIdx;\n" +" float x, y;\n" +" CubeMapUtilsCalcCrd( p, &faceIdx, &x, &y );\n" +" x = (x*HEIGHT_RES) - 0.5f;\n" +" y = (y*HEIGHT_RES) - 0.5f;\n" +"\n" +" float height;\n" +" {\n" +" int xi = (int)(x);\n" +" int yi = (int)(y);\n" +"\n" +" {\n" +" int xip = min2((int)(HEIGHT_RES-1), xi+1);\n" +" int yip = min2((int)(HEIGHT_RES-1), yi+1);\n" +"\n" +" u32 xy = sampleSupportGlobal( shape, faceIdx, xi, yi );\n" +" u32 xpy = sampleSupportGlobal( shape, faceIdx, xip, yi );\n" +" u32 xpyp = sampleSupportGlobal( shape, faceIdx, xip, yip );\n" +" u32 xyp = sampleSupportGlobal( shape, faceIdx, xi, yip );\n" +"\n" +" height = max2( xy, max2( xpy, max2( xpyp, xyp ) ) );\n" +" height = height/255.f*shape->m_scale;\n" +" }\n" +" }\n" +"\n" +" return height;\n" +"\n" +"}\n" +"\n" +"float4 ShapeDataQueryNormal(__global const ShapeData* shape, float4 p )\n" +"{\n" +" int faceIdx;\n" +" float x, y;\n" +" CubeMapUtilsCalcCrd( p, &faceIdx, &x, &y );\n" +" x = (x*HEIGHT_RES) - 0.5f;\n" +" y = (y*HEIGHT_RES) - 0.5f;\n" +"\n" +" float4 normalOut;\n" +" {\n" +" int xi = (int)(x);\n" +" int yi = (int)(y);\n" +"\n" +" normalOut = sampleNormalGlobal( shape, faceIdx, xi, yi );\n" +" }\n" +" return normalOut;\n" +"}\n" +"\n" +"\n" +"\n" +"// kernels\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void SupportCullingKernel( __global int2* restrict gPairsIn, __global ShapeData* gShapes, \n" +" __global BodyData* gBodies, \n" +" __global int2* gPairsOut, \n" +" counter32_t gNPairs,\n" +" ConstBuffer cb )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" if( gIdx >= cb.m_nPairs ) return;\n" +"\n" +" const float collisionMargin = cb.m_collisionMargin;\n" +" const int capacity = cb.m_capacity;\n" +"\n" +" int2 pair = gPairsIn[gIdx];\n" +" BodyData bodyA = gBodies[pair.x];\n" +" BodyData bodyB = gBodies[pair.y];\n" +" int shapeAIdx = bodyA.m_shapeIdx;\n" +" int shapeBIdx = bodyB.m_shapeIdx;\n" +"\n" +"\n" +" bool collide = false;\n" +" \n" +" //only collide if one of the two bodies has a non-zero mass\n" +" if (bodyA.m_invMass==0.f && bodyB.m_invMass==0.f)\n" +" return;\n" +" \n" +" \n" +" if (bodyA.m_shapeType == SHAPE_CONVEX_HEIGHT_FIELD && bodyB.m_shapeType==SHAPE_CONVEX_HEIGHT_FIELD)\n" +" {\n" +" float4 abInA, baInB;\n" +" float4 ab = bodyB.m_pos - bodyA.m_pos;\n" +" {\n" +" abInA = qtInvRotate( bodyA.m_quat, ab );\n" +" baInB = qtInvRotate( bodyB.m_quat, -ab );\n" +" }\n" +" float hA = ShapeDataQuerySupportHeight( gShapes+shapeAIdx, abInA );\n" +" float hB = ShapeDataQuerySupportHeight( gShapes+shapeBIdx, baInB );\n" +"\n" +" float h2 = dot3F4( ab, ab );\n" +"\n" +" collide = ( hA + hB + collisionMargin > sqrtf(h2) );\n" +" }\n" +"\n" +" if( collide )\n" +" {\n" +" int dstIdx;\n" +" AppendInc( gNPairs, dstIdx );\n" +" if( dstIdx < capacity )\n" +" gPairsOut[dstIdx] = pair;\n" +" }\n" +"}\n" +"\n" +"\n" +"#define PARALLEL_DO(execution, n) for(int ie=0; ie h[lIdx+1].y)? h[lIdx]: h[lIdx+1];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] = (h[lIdx].y > h[lIdx+2].y)? h[lIdx]: h[lIdx+2];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] = (h[lIdx].y > h[lIdx+4].y)? h[lIdx]: h[lIdx+4];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] = (h[lIdx].y > h[lIdx+8].y)? h[lIdx]: h[lIdx+8];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] = (h[lIdx].y > h[lIdx+16].y)? h[lIdx]: h[lIdx+16];" +" }}\n" +"\n" +"#define PARALLEL_REDUCE32(h) " +" {int lIdx = GET_LOCAL_IDX;" +" if( lIdx < 32 )" +" {" +" h[lIdx] += h[lIdx+1];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] += h[lIdx+2];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] += h[lIdx+4];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] += h[lIdx+8];" +" mem_fence( CLK_LOCAL_MEM_FENCE );" +" h[lIdx] += h[lIdx+16];" +" }}\n" +"\n" +"\n" +"float4 extractManifold(__local float4* p, __local float4* h, __local int* nPointsPtr, float4 nearNormal)\n" +"{\n" +" int nPoints = *nPointsPtr;\n" +" float4 center = make_float4(0,0,0,0);\n" +" { // calculate center\n" +" nPoints = min2( nPoints, 32 );\n" +" {\n" +" int lIdx = GET_LOCAL_IDX;\n" +" h[lIdx] = p[lIdx];\n" +" h[lIdx] = (lIdx= nPoints ) a[ie] = make_int4(-0xfffffff, -0xfffffff, -0xfffffff, -0xfffffff);\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" { // vector reduce, h[64]\n" +" int lIdx = GET_LOCAL_IDX;\n" +" if( lIdx < 32 )\n" +" {\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+1] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+2] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+4] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+8] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+16] );\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" {\n" +" { // set to idx\n" +" idx[0] = (int)a[0].x & 0xff;\n" +" idx[1] = (int)a[0].y & 0xff;\n" +" idx[2] = (int)a[0].z & 0xff;\n" +" idx[3] = (int)a[0].w & 0xff;\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" float4 selection;\n" +" if( GET_LOCAL_IDX < 4 ) selection = p[idx[GET_LOCAL_IDX]];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" if( GET_LOCAL_IDX < 4 ) p[GET_LOCAL_IDX] = selection;\n" +" }\n" +"\n" +"\n" +" return center;\n" +"}\n" +"\n" +"void extractManifold1(__local float4* p, __local float4* h, __local int* nPointsPtr, float4 center)\n" +"{\n" +" __local int* a = (__local int*)h;\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +" float4 selection;\n" +" if( GET_LOCAL_IDX < 4 )\n" +" {\n" +" int idx = (int)a[GET_LOCAL_IDX] & 0xff;\n" +" selection = p[idx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" if( GET_LOCAL_IDX < 4 ) p[GET_LOCAL_IDX] = selection;\n" +" }\n" +"\n" +"}\n" +"\n" +"void extractManifold2( __local float4* p0, __local int* nPointsPtr0, float4 nearNormal0,\n" +" __local float4* p1, __local int* nPointsPtr1, float4 nearNormal1,\n" +" __local float4* h, float4 centerOut[2])\n" +"{\n" +"\n" +" int nPoints[2];\n" +" nPoints[0] = *nPointsPtr0;\n" +" nPoints[1] = *nPointsPtr1;\n" +" float4 center[2];\n" +" center[0] = make_float4(0,0,0,0);\n" +" center[1] = make_float4(0,0,0,0);\n" +" { // calculate center\n" +" nPoints[0] = min2( nPoints[0], 32 );\n" +" nPoints[1] = min2( nPoints[1], 32 );\n" +" {\n" +" int lIdx = GET_LOCAL_IDX;\n" +" h[lIdx] = (lIdx= nPoints[setIdx] ) a[ie + setIdx*64] = make_int4(-0xfffffff, -0xfffffff, -0xfffffff, -0xfffffff);\n" +"\n" +" a[ie + 32] = make_int4(-0xfffffff, -0xfffffff, -0xfffffff, -0xfffffff);\n" +" }\n" +" }\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" { // vector reduce, h[64]\n" +" int bIdx = GET_LOCAL_IDX/32;\n" +" int eIdx = GET_LOCAL_IDX%32;\n" +" int lIdx = eIdx + bIdx*64;\n" +" {\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+1] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+2] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+4] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+8] );\n" +" mem_fence( CLK_LOCAL_MEM_FENCE );\n" +" h[lIdx] = max2( h[lIdx], h[lIdx+16] );\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" __local int* a = (__local int*)h;\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" float4 selection;\n" +"\n" +" int bIdx = GET_LOCAL_IDX/32;\n" +" int eIdx = GET_LOCAL_IDX%32;\n" +"\n" +" if( eIdx < 4 )\n" +" {\n" +" int idx = (int)a[eIdx+64*4*bIdx] & 0xff;\n" +" selection = p0[idx+32*bIdx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" if( eIdx < 4 ) p0[eIdx+32*bIdx] = selection;\n" +" }\n" +"}\n" +"\n" +"/*\n" +"1. Query Normal\n" +"2. Fill Normal\n" +"3. A->B, B->A\n" +"*/\n" +"\n" +"void testVtx(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr,\n" +" __local ShapeDeviceData* shapeAPtr, __local ShapeDeviceData* shapeBPtr,\n" +" __local int* lNContacts, __local float4* lCPoints)\n" +"{\n" +" int pIdx = GET_LOCAL_IDX;\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" float4 bodyBPos = bodyBPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +" Quaternion bodyBQuat = bodyBPtr->m_quat;\n" +" while( pIdx < HEIGHT_RES*HEIGHT_RES*6 )\n" +" {\n" +" float4 pInB = ShapeDataCalcSamplePoint( shapeBPtr, pIdx );\n" +"\n" +" float4 pInW = transform( &pInB, &bodyBPos, &bodyBQuat );\n" +"// Aabb bodyAAabb = bodyAPtr->m_aabb;\n" +"// if( AabbOverlapsPoint( &bodyAAabb, pInW ) )\n" +" {\n" +" float4 pInA = invTransform( &pInW, &bodyAPos, &bodyAQuat );\n" +"\n" +" float dist = ShapeDataQueryDistance( shapeAPtr, pInA );\n" +" if( dist < 0.010f )\n" +" {\n" +" int dstIdx = atom_add( lNContacts, 1 );\n" +" if( dstIdx < 32 )\n" +" {\n" +" lCPoints[ dstIdx ] = make_float4( pInA.x, pInA.y, pInA.z, dist );\n" +" }\n" +" }\n" +" }\n" +"\n" +" pIdx += GET_GROUP_SIZE;\n" +" }\n" +"}\n" +"\n" +"void testVtx2(__local const BodyData* bodyA, __local const BodyData* bodyB,\n" +" __local const ShapeDeviceData* shapeA, __local const ShapeDeviceData* shapeB,\n" +" __local int* lNContactsA, __local float4* lCPointsA,\n" +" __local int* lNContactsB, __local float4* lCPointsB, float collisionMargin )\n" +"{\n" +" int pIdx = GET_LOCAL_IDX;\n" +"\n" +" while( pIdx < HEIGHT_RES*HEIGHT_RES*6*2 )\n" +" {\n" +" __local const BodyData* bodyAPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?bodyA:bodyB;\n" +" __local const BodyData* bodyBPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?bodyB:bodyA;\n" +" __local const ShapeDeviceData* shapeAPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?shapeA:shapeB;\n" +" __local const ShapeDeviceData* shapeBPtr =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?shapeB:shapeA;\n" +" __local int* lNContacts =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?lNContactsA:lNContactsB;\n" +" __local float4* lCPoints =( pIdx < HEIGHT_RES*HEIGHT_RES*6 )?lCPointsA:lCPointsB;\n" +"\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" float4 bodyBPos = bodyBPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +" Quaternion bodyBQuat = bodyBPtr->m_quat;\n" +"\n" +" float4 pInB = ShapeDataCalcSamplePoint( shapeBPtr, pIdx%(HEIGHT_RES*HEIGHT_RES*6) );\n" +"\n" +" float4 pInW = transform( &pInB, &bodyBPos, &bodyBQuat );\n" +"// Aabb bodyAAabb = bodyAPtr->m_aabb;\n" +"// if( AabbOverlapsPoint( &bodyAAabb, pInW ) )\n" +" {\n" +" float4 pInA = invTransform( &pInW, &bodyAPos, &bodyAQuat );\n" +"\n" +" float dist = ShapeDataQueryDistance( shapeAPtr, pInA );\n" +" if( dist < collisionMargin )\n" +" {\n" +" int dstIdx = atom_add( lNContacts, 1 );\n" +" if( dstIdx < 32 )\n" +" {\n" +" lCPoints[ dstIdx ] = make_float4( pInA.x, pInA.y, pInA.z, dist );\n" +" }\n" +" }\n" +" }\n" +"\n" +" pIdx += GET_GROUP_SIZE;\n" +" }\n" +"}\n" +"\n" +"void testVtxWithPlane(__local BodyData* bodyA, __local BodyData* bodyB,\n" +" float4 nA, __local ShapeDeviceData* shapeB,\n" +" __local int* lNContactsA, __local float4* lCPointsA, float collisionMargin)\n" +"{\n" +" int pIdx = GET_LOCAL_IDX;\n" +"\n" +" while( pIdx < HEIGHT_RES*HEIGHT_RES*6 )\n" +" {\n" +" __local BodyData* bodyAPtr =bodyA;\n" +" __local BodyData* bodyBPtr =bodyB;\n" +" __local ShapeDeviceData* shapeBPtr =shapeB;\n" +" __local int* lNContacts =lNContactsA;\n" +" __local float4* lCPoints =lCPointsA;\n" +"\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" float4 bodyBPos = bodyBPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +" Quaternion bodyBQuat = bodyBPtr->m_quat;\n" +"\n" +" float4 pInB = ShapeDataCalcSamplePoint( shapeBPtr, pIdx%(HEIGHT_RES*HEIGHT_RES*6) );\n" +"\n" +" float4 pInW = transform( &pInB, &bodyBPos, &bodyBQuat );\n" +" {\n" +" float4 pInA = invTransform( &pInW, &bodyAPos, &bodyAQuat );\n" +"\n" +" float dist = dot3w1( pInA, nA );//ShapeDataQueryDistance( shapeAPtr, pInA );\n" +" if( dist < collisionMargin )\n" +" {\n" +" int dstIdx = atom_add( lNContacts, 1 );\n" +" if( dstIdx < 32 )\n" +" {\n" +" lCPoints[ dstIdx ] = make_float4( pInA.x, pInA.y, pInA.z, dist );\n" +" }\n" +" }\n" +" }\n" +"\n" +" pIdx += GET_GROUP_SIZE;\n" +" }\n" +"}\n" +"\n" +"#define GET_SHAPE_IDX(x) (int)((x).m_shapeIdx)\n" +"\n" +"void output(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr,\n" +" __local int2* iPair,\n" +" __local int* lNContacts, __local float4* lCPoints,\n" +" float4 center, \n" +" __global ShapeData* shapeData, __global Contact4* contactsOut, float collisionMargin)\n" +"{\n" +" if( *lNContacts != 0 )\n" +" {\n" +" int nContacts = min2( *lNContacts, 4 );\n" +"\n" +" __global Contact4* c = contactsOut;\n" +"\n" +" if( GET_LOCAL_IDX < nContacts )\n" +" {\n" +" int i = GET_LOCAL_IDX;\n" +" float4 p = lCPoints[i];\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +"\n" +" c->m_worldPos[i] = transform( &p, &bodyAPos, &bodyAQuat );\n" +" c->m_worldPos[i].w = lCPoints[i].w - collisionMargin;\n" +" }\n" +"\n" +" if( GET_LOCAL_IDX == 0 )\n" +" {\n" +" float4 contactNormal;\n" +" contactNormal = ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center );\n" +" contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) );\n" +"\n" +" c->m_worldNormal = contactNormal;\n" +"// c->m_restituitionCoeff = 0.f;\n" +"// c->m_frictionCoeff = 0.7f;\n" +" c->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16);\n" +" GET_NPOINTS(*c) = nContacts;\n" +" c->m_bodyAPtr = iPair[0].x;\n" +" c->m_bodyBPtr = iPair[0].y;\n" +" }\n" +" }\n" +" else\n" +" {\n" +" if( GET_LOCAL_IDX == 0 )\n" +" GET_NPOINTS(contactsOut[0]) = 0;\n" +" }\n" +"}\n" +"\n" +"// todo. make it better\n" +"void output2(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr,\n" +" int pair0, int pair1,\n" +" __local int* lNContacts, __local float4* lCPoints,\n" +" float4 center, \n" +" const __global ShapeData* shapeData, __global Contact4* contactsOut, counter32_t nContactsOut, int capacity,\n" +" float collisionMargin )\n" +"{\n" +" int lIdx = GET_LOCAL_IDX%32;\n" +" int nContacts = min2( *lNContacts, 4 );\n" +" \n" +" GROUP_LDS_BARRIER;\n" +"\n" +" if( lIdx == 0 && nContacts)\n" +" {\n" +" int dstIdx;\n" +" AppendInc( nContactsOut, dstIdx );\n" +" *lNContacts = dstIdx;\n" +"\n" +" if( dstIdx >= capacity )\n" +" *lNContacts = -1;\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" bool canWrite = (*lNContacts!=-1);\n" +"\n" +" if( nContacts && canWrite )\n" +" {\n" +" __global Contact4* c = contactsOut + (*lNContacts);\n" +"\n" +" if( lIdx < nContacts )\n" +" {\n" +" int i = lIdx;\n" +" float4 p = lCPoints[i];\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +"\n" +" p = transform( &p, &bodyAPos, &bodyAQuat );\n" +" p.w = lCPoints[i].w - collisionMargin;\n" +" c->m_worldPos[i] = p;\n" +" }\n" +"\n" +" if( lIdx == 0 )\n" +" {\n" +" if( nContacts )\n" +" {\n" +" float4 contactNormal;\n" +" contactNormal = ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center );\n" +" contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) );\n" +"\n" +" c->m_worldNormal = contactNormal;\n" +"// c->m_restituitionCoeff = 0.f;\n" +"// c->m_frictionCoeff = 0.7f;\n" +" c->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16);\n" +" c->m_bodyAPtr = pair0;\n" +" c->m_bodyBPtr = pair1;\n" +" }\n" +" GET_NPOINTS(*c) = nContacts;\n" +" }\n" +" }\n" +"}\n" +"\n" +"__inline\n" +"void output2LDS(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr,\n" +" int pair0, int pair1,\n" +" int lNContacts, __local float4* lCPoints,\n" +" float4 center, \n" +" const __global ShapeData* shapeData, __local Contact4* contactsOut,\n" +" float collisionMargin )\n" +"{\n" +" int lIdx = GET_LOCAL_IDX%32;\n" +"// int lIdx = GET_LOCAL_IDX;\n" +"// int groupIdx = 0;\n" +"\n" +" int nContacts = min2( lNContacts, 4 );\n" +" \n" +" GROUP_LDS_BARRIER;\n" +"\n" +" if( nContacts != 0 )\n" +" {\n" +" if( lIdx < nContacts )\n" +" {\n" +" int i = lIdx;\n" +" float4 p = lCPoints[i];\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +"\n" +" p = transform( &p, &bodyAPos, &bodyAQuat );\n" +" p.w = lCPoints[i].w - collisionMargin;\n" +" contactsOut->m_worldPos[i] = p;\n" +" }\n" +" }\n" +"\n" +" if( lIdx == 0 )\n" +" {\n" +" if( nContacts != 0 )\n" +" {\n" +" float4 contactNormal;\n" +" contactNormal = ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center );\n" +" contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) );\n" +"\n" +" contactsOut->m_worldNormal = contactNormal;\n" +"// contactsOut->m_worldNormal = make_float4(1.5f,1.4f,1.3f,0.f);\n" +"// contactsOut->m_restituitionCoeff = 0.f;\n" +"// contactsOut->m_frictionCoeff = 0.7f;\n" +" contactsOut->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16);\n" +" contactsOut->m_bodyAPtr = pair0;\n" +" contactsOut->m_bodyBPtr = pair1;\n" +" }\n" +" GET_NPOINTS(*contactsOut) = nContacts;//nContacts;\n" +" }\n" +"\n" +"// contactsOut[groupIdx].m_worldNormal = make_float4(1.5f,1.4f,1.3f,0.f);\n" +"}\n" +"\n" +"void output2_1(__local BodyData* bodyAPtr, __local BodyData* bodyBPtr,\n" +" int pair0, int pair1,\n" +" __local int* lNContacts, __local float4* lCPoints,\n" +" float4 center, float4 nA, \n" +" const __global ShapeData* shapeData, __global Contact4* contactsOut, counter32_t nContactsOut, int capacity, float collisionMargin )\n" +"{\n" +" int lIdx = GET_LOCAL_IDX;\n" +" int nContacts = min2( *lNContacts, 4 );\n" +" \n" +" GROUP_LDS_BARRIER;\n" +"\n" +" if( lIdx == 0 && nContacts)\n" +" {\n" +" int dstIdx;\n" +" AppendInc( nContactsOut, dstIdx );\n" +" *lNContacts = dstIdx;\n" +"\n" +" if( dstIdx >= capacity )\n" +" *lNContacts = -1;\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" bool canWrite = (*lNContacts!=-1);\n" +"\n" +" if( nContacts && canWrite )\n" +" {\n" +" __global Contact4* c = contactsOut + (*lNContacts);\n" +"\n" +" if( lIdx < nContacts )\n" +" {\n" +" int i = lIdx;\n" +" float4 p = lCPoints[i];\n" +" float4 bodyAPos = bodyAPtr->m_pos;\n" +" Quaternion bodyAQuat = bodyAPtr->m_quat;\n" +"\n" +" p = transform( &p, &bodyAPos, &bodyAQuat );\n" +" p.w = lCPoints[i].w - collisionMargin;\n" +" c->m_worldPos[i] = p;\n" +" }\n" +"\n" +" if( lIdx == 0 )\n" +" {\n" +" if( nContacts )\n" +" {\n" +" float4 contactNormal;\n" +" contactNormal = nA;//ShapeDataQueryNormal( &shapeData[GET_SHAPE_IDX(*bodyAPtr)], center );\n" +" contactNormal = normalize3( qtRotate( bodyAPtr->m_quat, contactNormal ) );\n" +"\n" +" c->m_worldNormal = contactNormal;\n" +"// c->m_restituitionCoeff = 0.f;\n" +"// c->m_frictionCoeff = 0.7f;\n" +" c->m_coeffs = (u32)(0.f*0xffff) | ((u32)(0.7f*0xffff)<<16);\n" +" c->m_bodyAPtr = pair0;\n" +" c->m_bodyBPtr = pair1;\n" +" }\n" +" GET_NPOINTS(*c) = nContacts;\n" +" }\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"void manifold(__global float4* vIn, __global float4* vOut)\n" +"{\n" +" __local float4 lCPoints[32];\n" +" __local float4 lManifoldBuffer[64];\n" +" __local int lNContacts;\n" +" __local float4 ab;\n" +"\n" +" if( GET_LOCAL_IDX<32 )\n" +" {\n" +" lCPoints[GET_LOCAL_IDX] = vIn[GET_GLOBAL_IDX];\n" +" }\n" +"\n" +" if( GET_LOCAL_IDX == 0 ) \n" +" {\n" +" lNContacts = 32;\n" +" ab = vIn[GET_GLOBAL_IDX];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" float4 center = extractManifold( lCPoints, lManifoldBuffer, &lNContacts, ab );\n" +"\n" +" if( GET_LOCAL_IDX < lNContacts )\n" +" {\n" +" vOut[4*GET_GROUP_IDX+GET_LOCAL_IDX] = lCPoints[GET_LOCAL_IDX];\n" +" }\n" +"\n" +"}\n" +"\n" +"//#define COMBINE_REDUCTION \n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64, 1, 1)))\n" +"void NarrowphaseKernel( const __global int2* restrict pairs, const __global ShapeData* shapeData, const __global BodyData* restrict bodyDatas, \n" +" __global Contact4* restrict contactsOut,\n" +" counter32_t nContactsOut, ConstBuffer cb ) \n" +"{\n" +" // 2.5K LDS\n" +" __local Contact4 ldsContacts[2];\n" +" __local BodyData bodyA;\n" +" __local BodyData bodyB;\n" +" __local ShapeDeviceData shapeA;\n" +" __local ShapeDeviceData shapeB;\n" +" __local float4 lCPointsA[32*2];\n" +" __local int lNContactsA;\n" +" __local float4* lCPointsB = lCPointsA+32;\n" +" __local int lNContactsB;\n" +"#ifdef COMBINE_REDUCTION\n" +" __local float4 lManifoldBuffer[64*2];\n" +"#else\n" +" __local float4 lManifoldBuffer[64];\n" +"#endif\n" +" __local int2 iPairAB;\n" +"\n" +" const int capacity = cb.m_capacity;\n" +" const float collisionMargin = cb.m_collisionMargin;\n" +"\n" +"\n" +" int pairIdx = GET_GROUP_IDX;\n" +"// for(int pairIdx = GET_GROUP_IDX; pairIdxm_height4[idx] = shapeData[ myShapeIdx ].m_height4[idx];\n" +"\n" +" idx+=32;\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" testVtx2( &bodyA, &bodyB, &shapeA, &shapeB, &lNContactsA, lCPointsA, &lNContactsB, lCPointsB, collisionMargin );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" float4 ab = bodyB.m_pos - bodyA.m_pos;\n" +" float4 center[2];\n" +"\n" +" if( lNContactsA != 0 || lNContactsB != 0 )\n" +" {\n" +" float4 abInA;\n" +" abInA = qtInvRotate( bodyA.m_quat, ab );\n" +"\n" +" float4 abInB;\n" +" abInB = qtInvRotate( bodyB.m_quat, ab );\n" +"\n" +"#ifdef COMBINE_REDUCTION\n" +" extractManifold2( lCPointsA, &lNContactsA, abInA,\n" +" lCPointsB, &lNContactsB, abInB,\n" +" lManifoldBuffer, center );\n" +"#else\n" +" if( lNContactsA != 0 )\n" +" center[0] = extractManifold( lCPointsA, lManifoldBuffer, &lNContactsA, abInA );\n" +" if( lNContactsB != 0 )\n" +" center[1] = extractManifold( lCPointsB, lManifoldBuffer, &lNContactsB, abInB );\n" +"#endif\n" +" }\n" +"\n" +" int firstSet = GET_LOCAL_IDX/32;\n" +"\n" +"/*\n" +" if( GET_LOCAL_IDX == 0 ) // for debug\n" +" {\n" +" ldsContacts[0].m_worldNormal = make_float4(-1,-1,-1,0);\n" +" ldsContacts[0].m_bodyAPtr = 0;\n" +" ldsContacts[0].m_bodyBPtr = 0;\n" +" ldsContacts[0].m_batchIdx = 111;\n" +" ldsContacts[1].m_worldNormal = make_float4(-1,-1,-1,0);\n" +" ldsContacts[1].m_bodyAPtr = 0;\n" +" ldsContacts[1].m_bodyBPtr = 0;\n" +" ldsContacts[1].m_batchIdx = 111;\n" +" }\n" +"*/\n" +" bool doReduction = true;\n" +" if( doReduction )\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" output2LDS( (firstSet)?&bodyA: &bodyB, (firstSet)?&bodyB : &bodyA, \n" +" (firstSet)?iPairAB.x : iPairAB.y, (firstSet)?iPairAB.y : iPairAB.x, \n" +" (firstSet)?lNContactsA : lNContactsB, (firstSet)?lCPointsA:lCPointsB, \n" +" (firstSet)?center[0] : center[1], shapeData, (firstSet)?&ldsContacts[0]: &ldsContacts[1], collisionMargin );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" if( GET_LOCAL_IDX == 0 )\n" +" {\n" +" if( lNContactsA && lNContactsB )\n" +" {\n" +" float nDotn = dot3F4( ldsContacts[0].m_worldNormal, ldsContacts[1].m_worldNormal );\n" +" if( nDotn < -(1.f-0.01f) )\n" +" {\n" +" if( ldsContacts[0].m_bodyAPtr > ldsContacts[1].m_bodyAPtr )\n" +" lNContactsA = 0;\n" +" else\n" +" lNContactsB = 0;\n" +" }\n" +" }\n" +" }\n" +" \n" +" if( GET_LOCAL_IDX == 0 )\n" +" {\n" +" int n = lNContactsA;\n" +" if( n != 0 )\n" +" {\n" +" int dstIdx;\n" +" AppendInc( nContactsOut, dstIdx );\n" +" if( dstIdx < capacity )\n" +" { int idx = 0;\n" +" contactsOut[ dstIdx ] = ldsContacts[idx];\n" +" contactsOut[ dstIdx].m_batchIdx = pairIdx;\n" +" }\n" +" }\n" +"\n" +" n = lNContactsB;\n" +" if( n != 0 )\n" +" {\n" +" int dstIdx;\n" +" AppendInc( nContactsOut, dstIdx );\n" +" if( dstIdx < capacity )\n" +" { int idx = 1;\n" +" contactsOut[ dstIdx ] = ldsContacts[idx];\n" +" contactsOut[ dstIdx].m_batchIdx = pairIdx;\n" +" }\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" else\n" +" {\n" +" //output2( (firstSet)?&bodyA: &bodyB, (firstSet)?&bodyB : &bodyA, \n" +" // (firstSet)?iPairAB.x : iPairAB.y, (firstSet)?iPairAB.y : iPairAB.x, \n" +" // (firstSet)?&lNContactsA : &lNContactsB, (firstSet)?lCPointsA:lCPointsB, \n" +" // (firstSet)?center[0] : center[1], shapeData, contactsOut, nContactsOut, capacity, collisionMargin );\n" +" }\n" +" }\n" +"}\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64, 1, 1)))\n" +"void NarrowphaseWithPlaneKernel( const __global int2* restrict pairs, const __global ShapeData* shapeData, const __global BodyData* restrict bodyDatas, \n" +" __global Contact4* restrict contactsOut,\n" +" counter32_t nContactsOut, ConstBuffer cb ) \n" +"{\n" +" // 2.5K LDS\n" +" __local BodyData bodyA;\n" +" __local BodyData bodyB;\n" +" __local ShapeDeviceData shapeA;\n" +" __local ShapeDeviceData shapeB;\n" +" __local float4 lCPointsA[32*2];\n" +" __local int lNContactsA;\n" +"// __local float4* lCPointsB = lCPointsA+32;\n" +"// __local int lNContactsB;\n" +" __local float4 lManifoldBuffer[64];\n" +" __local int2 iPairAB;\n" +"\n" +" const int capacity = cb.m_capacity;\n" +" const float collisionMargin = cb.m_collisionMargin;\n" +"\n" +" int pairIdx = GET_GROUP_IDX;\n" +" {\n" +" if( GET_LOCAL_IDX == 0 ) // load Bodies\n" +" {\n" +" int2 pair = pairs[pairIdx];\n" +" iPairAB = make_int2(pair.x, pair.y);\n" +" bodyA = bodyDatas[ pair.x ];\n" +" bodyB = bodyDatas[ pair.y ];\n" +" shapeA.m_scale = shapeData[ GET_SHAPE_IDX(bodyA) ].m_scale;\n" +" shapeB.m_scale = shapeData[ GET_SHAPE_IDX(bodyB) ].m_scale;\n" +" lNContactsA = 0;\n" +"// lNContactsB = 0;\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" if (bodyB.m_invMass == 0.f)\n" +" return;\n" +" \n" +" // todo. can check if the shape is the same to previous one. If same, dont read\n" +" { // load shape data\n" +" int idx = GET_LOCAL_IDX%32;\n" +" int bIdx = GET_LOCAL_IDX/32;\n" +" __local ShapeDeviceData* myShape = (bIdx==0)?&shapeA: &shapeB;\n" +" int myShapeIdx = (bIdx==0)?GET_SHAPE_IDX(bodyA): GET_SHAPE_IDX(bodyB);\n" +"\n" +" while( idx < HEIGHT_RES*HEIGHT_RES*6/4 )\n" +" {\n" +" myShape->m_height4[idx] = shapeData[ myShapeIdx ].m_height4[idx];\n" +"\n" +" idx+=32;\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" float4 nA = make_float4(0,1,0,0);\n" +"\n" +"\n" +"// testVtx2( &bodyA, &bodyB, &shapeA, &shapeB, &lNContactsA, lCPointsA, &lNContactsB, lCPointsB );\n" +" testVtxWithPlane( &bodyA, &bodyB, nA, &shapeB, &lNContactsA, lCPointsA, collisionMargin );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +"// float4 ab = bodyB.m_pos - bodyA.m_pos;\n" +" float4 center[2];\n" +"\n" +" if( lNContactsA != 0 )\n" +" {\n" +" float4 abInA;\n" +" abInA = nA;//qtInvRotate( bodyA.m_quat, ab );\n" +"\n" +" if( lNContactsA != 0 )\n" +" center[0] = extractManifold( lCPointsA, lManifoldBuffer, &lNContactsA, abInA );\n" +" }\n" +"\n" +"// int firstSet = GET_LOCAL_IDX/32;\n" +"\n" +" output2_1( &bodyA, &bodyB, \n" +" iPairAB.x, iPairAB.y, \n" +" &lNContactsA, lCPointsA, \n" +" center[0], nA, shapeData, contactsOut, nContactsOut, capacity, collisionMargin );\n" +" }\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.h new file mode 100644 index 000000000..2a2382ac1 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.h @@ -0,0 +1,203 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma once +#ifndef __ADL_SOLVER_H +#define __ADL_SOLVER_H + + +#include +#include +#include +#include +#include +#include + +//#include +#include "AdlRigidBody.h" +#include "AdlContact4.h" + +//#include "AdlPhysics/Batching/Batching.h> + + +#define MYF4 float4 +#define MAKE_MYF4 make_float4 + +//#define MYF4 float4sse +//#define MAKE_MYF4 make_float4sse + +#include "AdlConstraint4.h" + +namespace adl +{ +class SolverBase +{ + public: + + + struct ConstraintData + { + ConstraintData(): m_b(0.f), m_appliedRambdaDt(0.f) {} + + float4 m_linear; // have to be normalized + float4 m_angular0; + float4 m_angular1; + float m_jacCoeffInv; + float m_b; + float m_appliedRambdaDt; + + u32 m_bodyAPtr; + u32 m_bodyBPtr; + + bool isInvalid() const { return ((u32)m_bodyAPtr+(u32)m_bodyBPtr) == 0; } + float getFrictionCoeff() const { return m_linear.w; } + void setFrictionCoeff(float coeff) { m_linear.w = coeff; } + }; + + struct ConstraintCfg + { + ConstraintCfg( float dt = 0.f ): m_positionDrift( 0.005f ), m_positionConstraintCoeff( 0.2f ), m_dt(dt), m_staticIdx(-1) {} + + float m_positionDrift; + float m_positionConstraintCoeff; + float m_dt; + bool m_enableParallelSolve; + float m_averageExtent; + int m_staticIdx; + }; + + static + __inline + Buffer* allocateContact4( const Device* device, int capacity ) + { + return new Buffer( device, capacity ); + } + + static + __inline + void deallocateContact4( Buffer* data ) { delete data; } + + static + __inline + SolverData allocateConstraint4( const Device* device, int capacity ) + { + return new Buffer( device, capacity ); + } + + static + __inline + void deallocateConstraint4( SolverData data ) { delete (Buffer*)data; } + + static + __inline + void* allocateFrictionConstraint( const Device* device, int capacity, u32 type = 0 ) + { + return 0; + } + + static + __inline + void deallocateFrictionConstraint( void* data ) + { + } + + enum + { + N_SPLIT = 16, + N_BATCHES = 4, + N_OBJ_PER_SPLIT = 10, + N_TASKS_PER_BATCH = N_SPLIT*N_SPLIT, + }; +}; + +template +class Solver : public SolverBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct Data + { + Data() : m_nIterations(4){} + + const Device* m_device; + void* m_parallelSolveData; + int m_nIterations; + Kernel* m_batchingKernel; + Kernel* m_batchSolveKernel; + Kernel* m_contactToConstraintKernel; + Kernel* m_setSortDataKernel; + Kernel* m_reorderContactKernel; + Kernel* m_copyConstraintKernel; + //typename RadixSort::Data* m_sort; + typename RadixSort32::Data* m_sort32; + typename BoundSearch::Data* m_search; + typename PrefixScan::Data* m_scan; + Buffer* m_sortDataBuffer; + Buffer* m_contactBuffer; + }; + + enum + { + DYNAMIC_CONTACT_ALLOCATION_THRESHOLD = 2000000, + }; + + static + Data* allocate( const Device* device, int pairCapacity ); + + static + void deallocate( Data* data ); + + static + void reorderConvertToConstraints( Data* data, const Buffer* bodyBuf, + const Buffer* shapeBuf, + Buffer* contactsIn, SolverData contactCOut, void* additionalData, + int nContacts, const ConstraintCfg& cfg ); + + static + void solveContactConstraint( Data* data, const Buffer* bodyBuf, const Buffer* inertiaBuf, + SolverData constraint, void* additionalData, int n ); + +// static +// int createSolveTasks( int batchIdx, Data* data, const Buffer* bodyBuf, const Buffer* shapeBuf, +// SolverData constraint, int n, ThreadPool::Task* tasksOut[], int taskCapacity ); + + + //private: + static + void convertToConstraints( Data* data, const Buffer* bodyBuf, + const Buffer* shapeBuf, + Buffer* contactsIn, SolverData contactCOut, void* additionalData, + int nContacts, const ConstraintCfg& cfg ); + + static + void sortContacts( Data* data, const Buffer* bodyBuf, + Buffer* contactsIn, void* additionalData, + int nContacts, const ConstraintCfg& cfg ); + + static + void batchContacts( Data* data, Buffer* contacts, int nContacts, Buffer* n, Buffer* offsets, int staticIdx ); + +}; + +#include "Solver.inl" +#include "SolverHost.inl" +}; + +#undef MYF4 +#undef MAKE_MYF4 + +#endif //__ADL_SOLVER_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.inl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.inl new file mode 100644 index 000000000..3fc5a2f9f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/Solver.inl @@ -0,0 +1,762 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#define PATH "..\\..\\dynamics\\basic_demo\\Stubs\\SolverKernels" +#define BATCHING_PATH "..\\..\\dynamics\\basic_demo\\Stubs\\batchingKernels" + +#define KERNEL1 "SingleBatchSolveKernel" +#define KERNEL2 "BatchSolveKernel" + +#define KERNEL3 "ContactToConstraintKernel" +#define KERNEL4 "SetSortDataKernel" +#define KERNEL5 "ReorderContactKernel" +#include "SolverKernels.h" + +#include "batchingKernels.h" + + +struct SolverDebugInfo +{ + int m_valInt0; + int m_valInt1; + int m_valInt2; + int m_valInt3; + + int m_valInt4; + int m_valInt5; + int m_valInt6; + int m_valInt7; + + int m_valInt8; + int m_valInt9; + int m_valInt10; + int m_valInt11; + + int m_valInt12; + int m_valInt13; + int m_valInt14; + int m_valInt15; + + + float m_val0; + float m_val1; + float m_val2; + float m_val3; +}; + + + + +class SolverDeviceInl +{ +public: + struct ParallelSolveData + { + Buffer* m_numConstraints; + Buffer* m_offsets; + }; +}; + +template +typename Solver::Data* Solver::allocate( const Device* device, int pairCapacity ) +{ + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {solverKernelsCL, 0}; +#else + {0,0}; +#endif + + const char* src2[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {batchingKernelsCL, 0}; +#else + {0,0}; +#endif + + + + + Data* data = new Data; + data->m_device = device; + bool cacheBatchingKernel = true; + data->m_batchingKernel = device->getKernel( BATCHING_PATH, "CreateBatches", "-I ..\\..\\ ", src2[TYPE],cacheBatchingKernel); + //data->m_batchingKernel = device->getKernel( BATCHING_PATH, "CreateBatches", "-I ..\\..\\ ", 0,cacheBatchingKernel); + bool cacheSolverKernel = true; + + data->m_batchSolveKernel = device->getKernel( PATH, KERNEL2, "-I ..\\..\\ ", src[TYPE],cacheSolverKernel ); + data->m_contactToConstraintKernel = device->getKernel( PATH, KERNEL3, + "-I ..\\..\\ ", src[TYPE] ); + data->m_setSortDataKernel = device->getKernel( PATH, KERNEL4, + "-I ..\\..\\ ", src[TYPE] ); + data->m_reorderContactKernel = device->getKernel( PATH, KERNEL5, + "-I ..\\..\\ ", src[TYPE] ); + + data->m_copyConstraintKernel = device->getKernel( PATH, "CopyConstraintKernel", + "-I ..\\..\\ ", src[TYPE] ); + + data->m_parallelSolveData = new SolverDeviceInl::ParallelSolveData; + { + SolverDeviceInl::ParallelSolveData* solveData = (SolverDeviceInl::ParallelSolveData*)data->m_parallelSolveData; + solveData->m_numConstraints = new Buffer( device, N_SPLIT*N_SPLIT ); + solveData->m_offsets = new Buffer( device, N_SPLIT*N_SPLIT ); + } + const int sortSize = NEXTMULTIPLEOF( pairCapacity, 512 ); + + + //data->m_sort = RadixSort::allocate( data->m_device, sortSize );//todo. remove hardcode this + data->m_sort32 = RadixSort32::allocate( data->m_device, sortSize );//todo. remove hardcode this + + data->m_search = BoundSearch::allocate( data->m_device, N_SPLIT*N_SPLIT ); + data->m_scan = PrefixScan::allocate( data->m_device, N_SPLIT*N_SPLIT ); + + data->m_sortDataBuffer = new Buffer( data->m_device, sortSize ); + + if( pairCapacity < DYNAMIC_CONTACT_ALLOCATION_THRESHOLD ) + data->m_contactBuffer = new Buffer( data->m_device, pairCapacity ); + else + data->m_contactBuffer = 0; + + return data; +} + +template +void Solver::deallocate( Data* data ) +{ + { + SolverDeviceInl::ParallelSolveData* solveData = (SolverDeviceInl::ParallelSolveData*)data->m_parallelSolveData; + delete solveData->m_numConstraints; + delete solveData->m_offsets; + delete solveData; + } + +// RadixSort::deallocate( data->m_sort ); + RadixSort32::deallocate(data->m_sort32); + BoundSearch::deallocate( data->m_search ); + PrefixScan::deallocate( data->m_scan ); + + delete data->m_sortDataBuffer; + if( data->m_contactBuffer ) delete data->m_contactBuffer; + + delete data; +} + +template +void Solver::reorderConvertToConstraints( typename Solver::Data* data, const Buffer* bodyBuf, + const Buffer* shapeBuf, + Buffer* contactsIn, SolverData contactCOut, void* additionalData, + int nContacts, const typename Solver::ConstraintCfg& cfg ) +{ + if( data->m_contactBuffer ) + { + if( data->m_contactBuffer->getSize() < nContacts ) + { + BT_PROFILE("delete data->m_contactBuffer;"); + delete data->m_contactBuffer; + data->m_contactBuffer = 0; + } + } + if( data->m_contactBuffer == 0 ) + { + BT_PROFILE("new data->m_contactBuffer;"); + + data->m_contactBuffer = new Buffer( data->m_device, nContacts ); + } + Stopwatch sw; + + Buffer* contactNative = BufferUtils::map( data->m_device, contactsIn, nContacts ); + + //DeviceUtils::Config dhCfg; + //Device* deviceHost = DeviceUtils::allocate( TYPE_HOST, dhCfg ); + if( cfg.m_enableParallelSolve ) + { + SolverDeviceInl::ParallelSolveData* nativeSolveData = (SolverDeviceInl::ParallelSolveData*)data->m_parallelSolveData; + + DeviceUtils::waitForCompletion( data->m_device ); + sw.start(); + // contactsIn -> data->m_contactBuffer + { + BT_PROFILE("sortContacts"); + Solver::sortContacts( data, bodyBuf, contactNative, additionalData, nContacts, cfg ); + DeviceUtils::waitForCompletion( data->m_device ); + } + sw.split(); + if(0) + { + Contact4* tmp = new Contact4[nContacts]; + data->m_contactBuffer->read( tmp, nContacts ); + DeviceUtils::waitForCompletion( data->m_contactBuffer->m_device ); + contactNative->write( tmp, nContacts ); + DeviceUtils::waitForCompletion( contactNative->m_device ); + delete [] tmp; + } + else + { + BT_PROFILE("m_copyConstraintKernel"); + + Buffer constBuffer( data->m_device, 1, BufferBase::BUFFER_CONST ); + + int4 cdata; cdata.x = nContacts; + BufferInfo bInfo[] = { BufferInfo( data->m_contactBuffer ), BufferInfo( contactNative ) }; +// Launcher launcher( data->m_device, data->m_device->getKernel( PATH, "CopyConstraintKernel", "-I ..\\..\\ -Wf,--c++", 0 ) ); + Launcher launcher( data->m_device, data->m_copyConstraintKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nContacts, 64 ); + DeviceUtils::waitForCompletion( data->m_device ); + } + { + BT_PROFILE("batchContacts"); + Solver::batchContacts( data, contactNative, nContacts, nativeSolveData->m_numConstraints, nativeSolveData->m_offsets, cfg.m_staticIdx ); + + } + } + { + BT_PROFILE("waitForCompletion (batchContacts)"); + DeviceUtils::waitForCompletion( data->m_device ); + } + sw.split(); + //================ + if(0) + { +// Solver::Data* solverHost = Solver::allocate( deviceHost, nContacts ); +// Solver::convertToConstraints( solverHost, bodyBuf, shapeBuf, contactNative, contactCOut, additionalData, nContacts, cfg ); +// Solver::deallocate( solverHost ); + } + else + { + BT_PROFILE("convertToConstraints"); + Solver::convertToConstraints( data, bodyBuf, shapeBuf, contactNative, contactCOut, additionalData, nContacts, cfg ); + } + { + BT_PROFILE("convertToConstraints waitForCompletion"); + DeviceUtils::waitForCompletion( data->m_device ); + } + sw.stop(); + + { + BT_PROFILE("printf"); + + float t[5]; + sw.getMs( t, 3 ); +// printf("%3.2f, %3.2f, %3.2f, ", t[0], t[1], t[2]); + } + + { + BT_PROFILE("deallocate and unmap"); + + //DeviceUtils::deallocate( deviceHost ); + + BufferUtils::unmap( contactNative, contactsIn, nContacts ); + } +} + + +template +void Solver::solveContactConstraint( typename Solver::Data* data, const Buffer* bodyBuf, const Buffer* shapeBuf, + SolverData constraint, void* additionalData, int n ) +{ + if(0) + { + DeviceUtils::Config dhCfg; + Device* deviceHost = DeviceUtils::allocate( TYPE_HOST, dhCfg ); + { + Solver::Data* hostData = Solver::allocate( deviceHost, 0 ); + Solver::solveContactConstraint( hostData, bodyBuf, shapeBuf, constraint, additionalData, n ); + Solver::deallocate( hostData ); + } + DeviceUtils::deallocate( deviceHost ); + return; + } + + ADLASSERT( data ); + + Buffer* cBuffer =0; + + Buffer* gBodyNative=0; + Buffer* gShapeNative =0; + Buffer* gConstraintNative =0; + + + { + BT_PROFILE("map"); + cBuffer = (Buffer*)constraint; + + gBodyNative= BufferUtils::map( data->m_device, bodyBuf ); + gShapeNative= BufferUtils::map( data->m_device, shapeBuf ); + gConstraintNative = BufferUtils::map( data->m_device, cBuffer ); + DeviceUtils::waitForCompletion( data->m_device ); + } + + Buffer constBuffer; + int4 cdata = make_int4( n, 0, 0, 0 ); + { + SolverDeviceInl::ParallelSolveData* solveData = (SolverDeviceInl::ParallelSolveData*)data->m_parallelSolveData; + const int nn = N_SPLIT*N_SPLIT; + + cdata.x = 0; + cdata.y = 250; + +#if 0 +//check how the cells are filled + unsigned int* hostCounts = new unsigned int[N_SPLIT*N_SPLIT]; + solveData->m_numConstraints->read(hostCounts,N_SPLIT*N_SPLIT); + DeviceUtils::waitForCompletion( data->m_device ); + for (int i=0;i gpuDebugInfo(data->m_device,numWorkItems); +#endif + + + + { + + BT_PROFILE("m_batchSolveKernel iterations"); + for(int iter=0; iterm_nIterations; iter++) + { + for(int ib=0; ibm_numConstraints ), + BufferInfo( solveData->m_offsets ) +#ifdef DEBUG_ME + , BufferInfo(&gpuDebugInfo) +#endif + }; + + Launcher launcher( data->m_device, data->m_batchSolveKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + + launcher.launch1D( numWorkItems, 64 ); + +#ifdef DEBUG_ME + DeviceUtils::waitForCompletion( data->m_device ); + gpuDebugInfo.read(debugInfo,numWorkItems); + DeviceUtils::waitForCompletion( data->m_device ); + for (int i=0;i0) + { + printf("debugInfo[i].m_valInt2 = %d\n",i,debugInfo[i].m_valInt2); + } + + if (debugInfo[i].m_valInt3>0) + { + printf("debugInfo[i].m_valInt3 = %d\n",i,debugInfo[i].m_valInt3); + } + } +#endif //DEBUG_ME + + + } + } + + DeviceUtils::waitForCompletion( data->m_device ); + + + } + + cdata.x = 1; + { + BT_PROFILE("m_batchSolveKernel iterations2"); + for(int iter=0; iterm_nIterations; iter++) + { + for(int ib=0; ibm_numConstraints ), + BufferInfo( solveData->m_offsets ) +#ifdef DEBUG_ME + ,BufferInfo(&gpuDebugInfo) +#endif //DEBUG_ME + }; + Launcher launcher( data->m_device, data->m_batchSolveKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( 64*nn/N_BATCHES, 64 ); + } + } + DeviceUtils::waitForCompletion( data->m_device ); + + } +#ifdef DEBUG_ME + delete[] debugInfo; +#endif //DEBUG_ME + } + + { + BT_PROFILE("unmap"); + BufferUtils::unmap( gBodyNative, bodyBuf ); + BufferUtils::unmap( gShapeNative, shapeBuf ); + BufferUtils::unmap( gConstraintNative, cBuffer ); + DeviceUtils::waitForCompletion( data->m_device ); + } +} + +template +void Solver::convertToConstraints( typename Solver::Data* data, const Buffer* bodyBuf, + const Buffer* shapeBuf, + Buffer* contactsIn, SolverData contactCOut, void* additionalData, + int nContacts, const ConstraintCfg& cfg ) +{ + ADLASSERT( data->m_device->m_type == TYPE_CL ); + + Buffer* bodyNative =0; + Buffer* shapeNative =0; + Buffer* contactNative =0; + Buffer* constraintNative =0; + + { + BT_PROFILE("map buffers"); + + bodyNative = BufferUtils::map( data->m_device, bodyBuf ); + shapeNative = BufferUtils::map( data->m_device, shapeBuf ); + contactNative= BufferUtils::map( data->m_device, contactsIn ); + constraintNative = BufferUtils::map( data->m_device, (Buffer*)contactCOut ); + } + struct CB + { + int m_nContacts; + float m_dt; + float m_positionDrift; + float m_positionConstraintCoeff; + }; + + { + BT_PROFILE("m_contactToConstraintKernel"); + CB cdata; + cdata.m_nContacts = nContacts; + cdata.m_dt = cfg.m_dt; + cdata.m_positionDrift = cfg.m_positionDrift; + cdata.m_positionConstraintCoeff = cfg.m_positionConstraintCoeff; + + Buffer constBuffer( data->m_device, 1, BufferBase::BUFFER_CONST ); + BufferInfo bInfo[] = { BufferInfo( contactNative ), BufferInfo( bodyNative ), BufferInfo( shapeNative ), + BufferInfo( constraintNative )}; + Launcher launcher( data->m_device, data->m_contactToConstraintKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nContacts, 64 ); + DeviceUtils::waitForCompletion( data->m_device ); + + } + + { + BT_PROFILE("unmap"); + BufferUtils::unmap( bodyNative, bodyBuf ); + BufferUtils::unmap( shapeNative, shapeBuf ); + BufferUtils::unmap( contactNative, contactsIn ); + BufferUtils::unmap( constraintNative, (Buffer*)contactCOut ); + } +} + +template +void Solver::sortContacts( typename Solver::Data* data, const Buffer* bodyBuf, + Buffer* contactsIn, void* additionalData, + int nContacts, const typename Solver::ConstraintCfg& cfg ) +{ + ADLASSERT( data->m_device->m_type == TYPE_CL ); + Buffer* bodyNative + = BufferUtils::map( data->m_device, bodyBuf ); + Buffer* contactNative + = BufferUtils::map( data->m_device, contactsIn ); + + const int sortAlignment = 512; // todo. get this out of sort + if( cfg.m_enableParallelSolve ) + { + SolverDeviceInl::ParallelSolveData* nativeSolveData = (SolverDeviceInl::ParallelSolveData*)data->m_parallelSolveData; + + int sortSize = NEXTMULTIPLEOF( nContacts, sortAlignment ); + + Buffer* countsNative = nativeSolveData->m_numConstraints;//BufferUtils::map( data->m_device, &countsHost ); + Buffer* offsetsNative = nativeSolveData->m_offsets;//BufferUtils::map( data->m_device, &offsetsHost ); + + { // 2. set cell idx + struct CB + { + int m_nContacts; + int m_staticIdx; + float m_scale; + int m_nSplit; + }; + + ADLASSERT( sortSize%64 == 0 ); + CB cdata; + cdata.m_nContacts = nContacts; + cdata.m_staticIdx = cfg.m_staticIdx; + cdata.m_scale = 1.f/(N_OBJ_PER_SPLIT*cfg.m_averageExtent); + cdata.m_nSplit = N_SPLIT; + + Buffer constBuffer( data->m_device, 1, BufferBase::BUFFER_CONST ); + BufferInfo bInfo[] = { BufferInfo( contactNative ), BufferInfo( bodyNative ), BufferInfo( data->m_sortDataBuffer ) }; + Launcher launcher( data->m_device, data->m_setSortDataKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( sortSize, 64 ); + } + + { // 3. sort by cell idx + int n = N_SPLIT*N_SPLIT; + int sortBit = 32; + //if( n <= 0xffff ) sortBit = 16; + //if( n <= 0xff ) sortBit = 8; + RadixSort32::execute( data->m_sort32, *data->m_sortDataBuffer,sortSize); + } + { // 4. find entries + BoundSearch::execute( data->m_search, *data->m_sortDataBuffer, nContacts, *countsNative, N_SPLIT*N_SPLIT, BoundSearchBase::COUNT ); + + PrefixScan::execute( data->m_scan, *countsNative, *offsetsNative, N_SPLIT*N_SPLIT ); + } + + { // 5. sort constraints by cellIdx + // todo. preallocate this +// ADLASSERT( contactsIn->getType() == TYPE_HOST ); +// Buffer* out = BufferUtils::map( data->m_device, contactsIn ); // copying contacts to this buffer + + { + Buffer constBuffer( data->m_device, 1, BufferBase::BUFFER_CONST ); + + int4 cdata; cdata.x = nContacts; + BufferInfo bInfo[] = { BufferInfo( contactNative ), BufferInfo( data->m_contactBuffer ), BufferInfo( data->m_sortDataBuffer ) }; + Launcher launcher( data->m_device, data->m_reorderContactKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nContacts, 64 ); + } +// BufferUtils::unmap( out, contactsIn, nContacts ); + } + } + + BufferUtils::unmap( bodyNative, bodyBuf ); + BufferUtils::unmap( contactNative, contactsIn ); +} + +template +void Solver::batchContacts( typename Solver::Data* data, Buffer* contacts, int nContacts, Buffer* n, Buffer* offsets, int staticIdx ) +{ + ADLASSERT( data->m_device->m_type == TYPE_CL ); + + if(0) + { + BT_PROFILE("CPU classTestKernel/Kernel (batch generation?)"); + + DeviceUtils::Config dhCfg; + Device* deviceHost = DeviceUtils::allocate( TYPE_HOST, dhCfg ); + { + Solver::Data* hostData = Solver::allocate( deviceHost, 0 ); + Solver::batchContacts( hostData, contacts, nContacts, n, offsets, staticIdx ); + Solver::deallocate( hostData ); + } + DeviceUtils::deallocate( deviceHost ); + return; + } + + Buffer* contactNative + = BufferUtils::map( data->m_device, contacts, nContacts ); + Buffer* nNative + = BufferUtils::map( data->m_device, n ); + Buffer* offsetsNative + = BufferUtils::map( data->m_device, offsets ); + + { + BT_PROFILE("GPU classTestKernel/Kernel (batch generation?)"); + Buffer constBuffer( data->m_device, 1, BufferBase::BUFFER_CONST ); + int4 cdata; + cdata.x = nContacts; + cdata.y = 0; + cdata.z = staticIdx; + + int numWorkItems = 64*N_SPLIT*N_SPLIT; +#ifdef BATCH_DEBUG + SolverDebugInfo* debugInfo = new SolverDebugInfo[numWorkItems]; + adl::Buffer gpuDebugInfo(data->m_device,numWorkItems); + memset(debugInfo,0,sizeof(SolverDebugInfo)*numWorkItems); + gpuDebugInfo.write(debugInfo,numWorkItems); +#endif + + + BufferInfo bInfo[] = { + BufferInfo( contactNative ), + BufferInfo( data->m_contactBuffer ), + BufferInfo( nNative ), + BufferInfo( offsetsNative ) +#ifdef BATCH_DEBUG + , BufferInfo(&gpuDebugInfo) +#endif + }; + + + + Launcher launcher( data->m_device, data->m_batchingKernel); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( numWorkItems, 64 ); + DeviceUtils::waitForCompletion( data->m_device ); + +#ifdef BATCH_DEBUG + aaaa + Contact4* hostContacts = new Contact4[nContacts]; + data->m_contactBuffer->read(hostContacts,nContacts); + DeviceUtils::waitForCompletion( data->m_device ); + + gpuDebugInfo.read(debugInfo,numWorkItems); + DeviceUtils::waitForCompletion( data->m_device ); + + for (int i=0;i0) + { + printf("catch\n"); + } + if (debugInfo[i].m_valInt2>0) + { + printf("catch22\n"); + } + + if (debugInfo[i].m_valInt3>0) + { + printf("catch666\n"); + } + + if (debugInfo[i].m_valInt4>0) + { + printf("catch777\n"); + } + } + delete[] debugInfo; +#endif //BATCH_DEBUG + + } + + if(0) + { + u32* nhost = new u32[N_SPLIT*N_SPLIT]; + + nNative->read( nhost, N_SPLIT*N_SPLIT ); + + Contact4* chost = new Contact4[nContacts]; + data->m_contactBuffer->read( chost, nContacts ); + DeviceUtils::waitForCompletion( data->m_device ); + printf(">>"); + int nonzero = 0; + u32 maxn = 0; + for(int i=0; iwrite( *data->m_contactBuffer, nContacts ); + DeviceUtils::waitForCompletion( data->m_device ); + + if(0) + { + DeviceUtils::Config dhCfg; + Device* deviceHost = DeviceUtils::allocate( TYPE_HOST, dhCfg ); + { + HostBuffer host( deviceHost, nContacts ); + contactNative->read( host.m_ptr, nContacts ); + DeviceUtils::waitForCompletion( data->m_device ); + + for(int i=0; i( contactNative, contacts ); + BufferUtils::unmap( nNative, n ); + BufferUtils::unmap( offsetsNative, offsets ); +} + +#undef PATH +#undef KERNEL1 +#undef KERNEL2 + +#undef KERNEL3 +#undef KERNEL4 +#undef KERNEL5 diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverHost.inl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverHost.inl new file mode 100644 index 000000000..a79205d8c --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverHost.inl @@ -0,0 +1,848 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +class SolverInl +{ +public: + typedef SolverBase::ConstraintData ConstraintData; + + + static + __forceinline + void setLinearAndAngular(const MYF4& n, const MYF4& r0, const MYF4& r1, + MYF4& linear, MYF4& angular0, MYF4& angular1) + { + linear = -n; + angular0 = -cross3(r0, n); + angular1 = cross3(r1, n); + } + + static + __forceinline + float calcJacCoeff(const MYF4& linear0, const MYF4& linear1, const MYF4& angular0, const MYF4& angular1, + float invMass0, const Matrix3x3& invInertia0, float invMass1, const Matrix3x3& invInertia1) + { + // linear0,1 are normlized + float jmj0 = invMass0;//dot3F4(linear0, linear0)*invMass0; + float jmj1 = dot3F4(mtMul3(angular0,invInertia0), angular0); + float jmj2 = invMass1;//dot3F4(linear1, linear1)*invMass1; + float jmj3 = dot3F4(mtMul3(angular1,invInertia1), angular1); + return -1.f/(jmj0+jmj1+jmj2+jmj3); + } + static + __forceinline + float calcRelVel(const MYF4& l0, const MYF4& l1, const MYF4& a0, const MYF4& a1, + const MYF4& linVel0, const MYF4& angVel0, const MYF4& linVel1, const MYF4& angVel1) + { + return dot3F4(l0, linVel0) + dot3F4(a0, angVel0) + dot3F4(l1, linVel1) + dot3F4(a1, angVel1); + } + + static + __forceinline + void setConstraint4( const MYF4& posA, const MYF4& linVelA, const MYF4& angVelA, float invMassA, const Matrix3x3& invInertiaA, + const MYF4& posB, const MYF4& linVelB, const MYF4& angVelB, float invMassB, const Matrix3x3& invInertiaB, + const Contact4& src, const SolverBase::ConstraintCfg& cfg, + Constraint4& dstC ) + { + dstC.m_bodyA = (u32)src.m_bodyAPtr; + dstC.m_bodyB = (u32)src.m_bodyBPtr; + + float dtInv = 1.f/cfg.m_dt; + for(int ic=0; ic<4; ic++) + { + dstC.m_appliedRambdaDt[ic] = 0.f; + } + dstC.m_fJacCoeffInv[0] = dstC.m_fJacCoeffInv[1] = 0.f; + + + const MYF4& n = src.m_worldNormal; + dstC.m_linear = -n; + dstC.setFrictionCoeff( src.getFrictionCoeff() ); + for(int ic=0; ic<4; ic++) + { + MYF4 r0 = src.m_worldPos[ic] - posA; + MYF4 r1 = src.m_worldPos[ic] - posB; + + if( ic >= src.getNPoints() ) + { + dstC.m_jacCoeffInv[ic] = 0.f; + continue; + } + + float relVelN; + { + MYF4 linear, angular0, angular1; + setLinearAndAngular(n, r0, r1, linear, angular0, angular1); + + dstC.m_jacCoeffInv[ic] = calcJacCoeff(linear, -linear, angular0, angular1, + invMassA, invInertiaA, invMassB, invInertiaB ); + + relVelN = calcRelVel(linear, -linear, angular0, angular1, + linVelA, angVelA, linVelB, angVelB); + + float e = src.getRestituitionCoeff(); + if( relVelN*relVelN < 0.004f ) e = 0.f; + + dstC.m_b[ic] = e*relVelN; + dstC.m_b[ic] += (src.getPenetration(ic) + cfg.m_positionDrift)*cfg.m_positionConstraintCoeff*dtInv; + dstC.m_appliedRambdaDt[ic] = 0.f; + } + } + + if( src.getNPoints() > 1 ) + { // prepare friction + MYF4 center = MAKE_MYF4(0.f); + for(int i=0; i 0.95f || (invMassA == 0.f || invMassB == 0.f)) + { + float angNA = dot3F4( n, angVelA ); + float angNB = dot3F4( n, angVelB ); + + angVelA -= (angNA*0.1f)*n; + angVelB -= (angNB*0.1f)*n; + } + } + } + + template + static + __inline + void solveContact(Constraint4& cs, + const MYF4& posA, MYF4& linVelA, MYF4& angVelA, float invMassA, const Matrix3x3& invInertiaA, + const MYF4& posB, MYF4& linVelB, MYF4& angVelB, float invMassB, const Matrix3x3& invInertiaB, + float maxRambdaDt[4], float minRambdaDt[4]) + { + MYF4 dLinVelA = MAKE_MYF4(0.f); + MYF4 dAngVelA = MAKE_MYF4(0.f); + MYF4 dLinVelB = MAKE_MYF4(0.f); + MYF4 dAngVelB = MAKE_MYF4(0.f); + + for(int ic=0; ic<4; ic++) + { + // dont necessary because this makes change to 0 + if( cs.m_jacCoeffInv[ic] == 0.f ) continue; + + { + MYF4 angular0, angular1, linear; + MYF4 r0 = cs.m_worldPos[ic] - posA; + MYF4 r1 = cs.m_worldPos[ic] - posB; + setLinearAndAngular( -cs.m_linear, r0, r1, linear, angular0, angular1 ); + + float rambdaDt = calcRelVel(cs.m_linear, -cs.m_linear, angular0, angular1, + linVelA, angVelA, linVelB, angVelB ) + cs.m_b[ic]; + rambdaDt *= cs.m_jacCoeffInv[ic]; + + { + float prevSum = cs.m_appliedRambdaDt[ic]; + float updated = prevSum; + updated += rambdaDt; + updated = max2( updated, minRambdaDt[ic] ); + updated = min2( updated, maxRambdaDt[ic] ); + rambdaDt = updated - prevSum; + cs.m_appliedRambdaDt[ic] = updated; + } + + MYF4 linImp0 = invMassA*linear*rambdaDt; + MYF4 linImp1 = invMassB*(-linear)*rambdaDt; + MYF4 angImp0 = mtMul1(invInertiaA, angular0)*rambdaDt; + MYF4 angImp1 = mtMul1(invInertiaB, angular1)*rambdaDt; + + if( JACOBI ) + { + dLinVelA += linImp0; + dAngVelA += angImp0; + dLinVelB += linImp1; + dAngVelB += angImp1; + } + else + { + linVelA += linImp0; + angVelA += angImp0; + linVelB += linImp1; + angVelB += angImp1; + } + } + } + + if( JACOBI ) + { + linVelA += dLinVelA; + angVelA += dAngVelA; + linVelB += dLinVelB; + angVelB += dAngVelB; + } + } + + enum + { + N_SPLIT = SolverBase::N_SPLIT, + }; + + // for parallel solve + struct ParallelSolveData + { + u32 m_n[N_SPLIT*N_SPLIT]; + u32 m_offset[N_SPLIT*N_SPLIT]; + }; + + static + __inline + int sortConstraintByBatch(Contact4* cs, int n, int ignoreIdx, int simdWidth = -1) + { + SortData* sortData; + { + BT_PROFILE("new"); + sortData = new SortData[n]; + } + + u32* idxBuffer = new u32[n]; + u32* idxSrc = idxBuffer; + u32* idxDst = idxBuffer; + int nIdxSrc, nIdxDst; + + const int N_FLG = 256; + const int FLG_MASK = N_FLG-1; + u32 flg[N_FLG/32]; +#if defined(_DEBUG) + for(int i=0; i sortBuffer; sortBuffer.setRawPtr( deviceHost, sortData, n ); + RadixSort::Data* sort = RadixSort::allocate( deviceHost, n ); + + RadixSort::execute( sort, sortBuffer, n ); + + RadixSort::deallocate( sort ); + } + DeviceUtils::deallocate( deviceHost ); + } + + { + BT_PROFILE("reorder"); + // reorder + Contact4* old = new Contact4[n]; + memcpy( old, cs, sizeof(Contact4)*n); + for(int i=0; i* bodies, const Buffer* shapes, const Buffer* constraints, + int start, int nConstraints) + : m_bodies( bodies ), m_shapes( shapes ), m_constraints( constraints ), m_start( start ), m_nConstraints( nConstraints ), + m_solveFriction( true ){} + + u16 getType(){ return 0; } + + void run(int tIdx) + { + HostBuffer& hBody = *(HostBuffer*)m_bodies; + HostBuffer& hShape = *(HostBuffer*)m_shapes; + HostBuffer& hc = *(HostBuffer*)m_constraints; + + for(int ic=0; ic( hc[i], bodyA.m_pos, (MYF4&)bodyA.m_linVel, (MYF4&)bodyA.m_angVel, bodyA.m_invMass, hShape[aIdx].m_invInertia, + bodyB.m_pos, (MYF4&)bodyB.m_linVel, (MYF4&)bodyB.m_angVel, bodyB.m_invMass, hShape[bIdx].m_invInertia, + maxRambdaDt, minRambdaDt ); + } + else + { + float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX}; + float minRambdaDt[4] = {0.f,0.f,0.f,0.f}; + + float sum = 0; + for(int j=0; j<4; j++) + { + sum +=hc[i].m_appliedRambdaDt[j]; + } + frictionCoeff = 0.7f; + for(int j=0; j<4; j++) + { + maxRambdaDt[j] = frictionCoeff*sum; + minRambdaDt[j] = -maxRambdaDt[j]; + } + + SolverInl::solveFriction( hc[i], bodyA.m_pos, (MYF4&)bodyA.m_linVel, (MYF4&)bodyA.m_angVel, bodyA.m_invMass, hShape[aIdx].m_invInertia, + bodyB.m_pos, (MYF4&)bodyB.m_linVel, (MYF4&)bodyB.m_angVel, bodyB.m_invMass, hShape[bIdx].m_invInertia, + maxRambdaDt, minRambdaDt ); + } + } + } + + const Buffer* m_bodies; + const Buffer* m_shapes; + const Buffer* m_constraints; + int m_start; + int m_nConstraints; + bool m_solveFriction; +}; + + +template<> +static Solver::Data* Solver::allocate( const Device* device, int pairCapacity ) +{ + Solver::Data* data = new Data; + data->m_device = device; + data->m_parallelSolveData = 0; + + return data; +} + +template<> +static void Solver::deallocate( Solver::Data* data ) +{ + if( data->m_parallelSolveData ) delete (SolverInl::ParallelSolveData*)data->m_parallelSolveData; + delete data; +} + + +void sortContacts2( Solver::Data* data, const Buffer* bodyBuf, + Buffer* contactsIn, void* additionalData, + int nContacts, const Solver::ConstraintCfg& cfg ) +{ + ADLASSERT( data->m_device->m_type == TYPE_HOST ); + HostBuffer* bodyNative + = (HostBuffer*)BufferUtils::map( data->m_device, bodyBuf ); + HostBuffer* contactNative + = (HostBuffer*)BufferUtils::map( data->m_device, contactsIn); + + if( cfg.m_enableParallelSolve ) + { + ADLASSERT( data->m_parallelSolveData == 0 ); + data->m_parallelSolveData = new SolverInl::ParallelSolveData; + SolverInl::ParallelSolveData* solveData = (SolverInl::ParallelSolveData*)data->m_parallelSolveData; + + HostBuffer sortData( data->m_device, nContacts ); + { // 2. set cell idx + float spacing = adl::SolverBase::N_OBJ_PER_SPLIT*cfg.m_averageExtent; + float xScale = 1.f/spacing; + for(int i=0; i= 0 && xIdx < adl::SolverBase::N_SPLIT ); + ADLASSERT( zIdx >= 0 && zIdx < adl::SolverBase::N_SPLIT ); + sortData[i].m_key = (xIdx+zIdx*adl::SolverBase::N_SPLIT); + sortData[i].m_value = i; + } + } + + { // 3. sort by cell idx + RadixSort::Data* sData = RadixSort::allocate( data->m_device, nContacts ); + + RadixSort::execute( sData, sortData, nContacts ); + + RadixSort::deallocate( sData ); + } + + { // 4. find entries + HostBuffer counts; counts.setRawPtr( data->m_device, solveData->m_n, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + HostBuffer offsets; offsets.setRawPtr( data->m_device, solveData->m_offset, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + { + BoundSearch::Data* sData = BoundSearch::allocate( data->m_device ); + PrefixScan::Data* pData = PrefixScan::allocate( data->m_device, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + + BoundSearch::execute( sData, sortData, nContacts, counts, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT, BoundSearchBase::COUNT ); + + PrefixScan::execute( pData, counts, offsets, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + + BoundSearch::deallocate( sData ); + PrefixScan::deallocate( pData ); + } +#if defined(_DEBUG) + { + HostBuffer n0( data->m_device, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + HostBuffer offset0( data->m_device, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + for(int i=0; im_ptr, sizeof(Contact4)*nContacts ); + for(int i=0; i( bodyNative, bodyBuf ); + BufferUtils::unmap( contactNative, contactsIn ); +} + +static void reorderConvertToConstraints2( Solver::Data* data, const Buffer* bodyBuf, + const Buffer* shapeBuf, + adl::Buffer* contactsIn, SolverData contactCOut, void* additionalData, + int nContacts, const Solver::ConstraintCfg& cfg ) +{ + + + sortContacts2( data, bodyBuf, contactsIn, additionalData, nContacts, cfg ); + + { + SolverInl::ParallelSolveData* solveData = (SolverInl::ParallelSolveData*)data->m_parallelSolveData; + Buffer n; n.setRawPtr( data->m_device, solveData->m_n, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + Buffer offsets; offsets.setRawPtr( data->m_device, solveData->m_offset, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + Solver::batchContacts( data, contactsIn, nContacts, &n, &offsets, cfg.m_staticIdx ); + printf("hello\n"); + } + + Solver::convertToConstraints( data, bodyBuf, shapeBuf, contactsIn, contactCOut, additionalData, nContacts, cfg ); +} + +template +static void solveContactConstraint( Solver::Data* data, const Buffer* bodyBuf, const Buffer* shapeBuf, + SolverData constraint, void* additionalData, int n ) +{ + + Buffer* bodyNative + = BufferUtils::map( data->m_device, bodyBuf ); + Buffer* shapeNative + = BufferUtils::map( data->m_device, shapeBuf ); + Buffer* constraintNative + = BufferUtils::map( data->m_device, (const Buffer*)constraint ); + + for(int iter=0; iterm_nIterations; iter++) + { + SolveTask task( bodyNative, shapeNative, constraintNative, 0, n ); + task.m_solveFriction = false; + task.run(0); + } + + for(int iter=0; iterm_nIterations; iter++) + { + SolveTask task( bodyNative, shapeNative, constraintNative, 0, n ); + task.m_solveFriction = true; + task.run(0); + } + + BufferUtils::unmap( bodyNative, bodyBuf ); + BufferUtils::unmap( shapeNative, shapeBuf ); + BufferUtils::unmap( constraintNative, (const Buffer*)constraint ); +} + +#if 0 +static +int createSolveTasks( int batchIdx, Data* data, const Buffer* bodyBuf, const Buffer* shapeBuf, + SolverData constraint, int n, ThreadPool::Task* tasksOut[], int taskCapacity ) +{ +/* + ADLASSERT( (N_SPLIT&1) == 0 ); + ADLASSERT( batchIdx < N_BATCHES ); + ADLASSERT( data->m_device->m_type == TYPE_HOST ); + ADLASSERT( data->m_parallelSolveData ); + + SolverInl::ParallelSolveData* solveData = (SolverInl::ParallelSolveData*)data->m_parallelSolveData; + data->m_batchIdx = 0; + + const int nx = N_SPLIT/2; + + int nTasksCreated = 0; + +// for(int ii=0; ii<2; ii++) + for(batchIdx=0; batchIdx<4; batchIdx++) + { + int2 offset = make_int2( batchIdx&1, batchIdx>>1 ); + for(int ix=0; ixm_n[cellIdx]; + int start = solveData->m_offset[cellIdx]; + + if( n == 0 ) continue; + + SolveTask* task = new SolveTask( bodyBuf, shapeBuf, (const Buffer*)constraint, start, n ); +// task->m_solveFriction = (ii==0)? false:true; + tasksOut[nTasksCreated++] = task; + } + } + + return nTasksCreated; +*/ + ADLASSERT(0); + return 0; +} +#endif + + + +static void convertToConstraints2( Solver::Data* data, const Buffer* bodyBuf, + const Buffer* shapeBuf, + Buffer* contactsIn, SolverData contactCOut, void* additionalData, + int nContacts, const Solver::ConstraintCfg& cfg ) +{ + ADLASSERT( data->m_device->m_type == TYPE_HOST ); + + HostBuffer* bodyNative + = (HostBuffer*)BufferUtils::map( data->m_device, bodyBuf ); + HostBuffer* shapeNative + = (HostBuffer*)BufferUtils::map( data->m_device, shapeBuf ); + HostBuffer* contactNative + = (HostBuffer*)BufferUtils::map( data->m_device, contactsIn ); + HostBuffer* constraintNative + = (HostBuffer*)BufferUtils::map( data->m_device, (Buffer*)contactCOut ); + + { +#if !defined(_DEBUG) +#pragma omp parallel for +#endif + for(int i=0; i( bodyNative, bodyBuf ); + BufferUtils::unmap( shapeNative, shapeBuf ); + BufferUtils::unmap( contactNative, contactsIn ); + BufferUtils::unmap( constraintNative, (Buffer*)contactCOut ); +} + + + + + +static void batchContacts2( Solver::Data* data, Buffer* contacts, int nContacts, Buffer* n, Buffer* offsets, int staticIdx ) +{ + ADLASSERT( data->m_device->m_type == TYPE_HOST ); + + HostBuffer* contactNative =0; + HostBuffer* nNative =0; + HostBuffer* offsetsNative =0; + + int sz = sizeof(Contact4); + int sz2 = sizeof(int2); + { + BT_PROFILE("BufferUtils::map"); + contactNative = (HostBuffer*)BufferUtils::map( data->m_device, contacts, nContacts ); + } + { + BT_PROFILE("BufferUtils::map2"); + nNative = (HostBuffer*)BufferUtils::map( data->m_device, n ); + offsetsNative= (HostBuffer*)BufferUtils::map( data->m_device, offsets ); + } + + + { + BT_PROFILE("sortConstraintByBatch"); + int numNonzeroGrid=0; + int maxNumBatches = 0; + + for(int i=0; im_ptr+offset, n, staticIdx,-1 ); // on GPU + maxNumBatches = max(numBatches,maxNumBatches); + + // SolverInl::sortConstraintByBatch( contactNative->m_ptr+offset, n, staticIdx ); // on CPU + } + } + + printf("maxNumBatches = %d\n", maxNumBatches); + } + + { + BT_PROFILE("BufferUtils::unmap"); + BufferUtils::unmap( contactNative, contacts, nContacts ); + } + { + BT_PROFILE("BufferUtils::unmap2"); + BufferUtils::unmap( nNative, n ); + BufferUtils::unmap( offsetsNative, offsets ); + } + + +} + + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.cl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.cl new file mode 100644 index 000000000..e46194391 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.cl @@ -0,0 +1,1051 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_extended_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable + + +#ifdef cl_ext_atomic_counters_32 +#pragma OPENCL EXTENSION cl_ext_atomic_counters_32 : enable +#else +#define counter32_t volatile global int* +#endif + +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GET_NUM_GROUPS get_num_groups(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) +#define AppendInc(x, out) out = atomic_inc(x) +#define AtomAdd(x, value) atom_add(&(x), value) +#define AtomCmpxhg(x, cmp, value) atom_cmpxchg( &(x), cmp, value ) +#define AtomXhg(x, value) atom_xchg ( &(x), value ) + + +#define SELECT_UINT4( b, a, condition ) select( b,a,condition ) + +#define make_float4 (float4) +#define make_float2 (float2) +#define make_uint4 (uint4) +#define make_int4 (int4) +#define make_uint2 (uint2) +#define make_int2 (int2) + + +#define max2 max +#define min2 min + + +/////////////////////////////////////// +// Vector +/////////////////////////////////////// +__inline +float fastDiv(float numerator, float denominator) +{ + return native_divide(numerator, denominator); +// return numerator/denominator; +} + +__inline +float4 fastDiv4(float4 numerator, float4 denominator) +{ + return native_divide(numerator, denominator); +} + +__inline +float fastSqrtf(float f2) +{ + return native_sqrt(f2); +// return sqrt(f2); +} + +__inline +float fastRSqrt(float f2) +{ + return native_rsqrt(f2); +} + +__inline +float fastLength4(float4 v) +{ + return fast_length(v); +} + +__inline +float4 fastNormalize4(float4 v) +{ + return fast_normalize(v); +} + + +__inline +float sqrtf(float a) +{ +// return sqrt(a); + return native_sqrt(a); +} + +__inline +float4 cross3(float4 a, float4 b) +{ + return cross(a,b); +} + +__inline +float dot3F4(float4 a, float4 b) +{ + float4 a1 = make_float4(a.xyz,0.f); + float4 b1 = make_float4(b.xyz,0.f); + return dot(a1, b1); +} + +__inline +float length3(const float4 a) +{ + return sqrtf(dot3F4(a,a)); +} + +__inline +float dot4(const float4 a, const float4 b) +{ + return dot( a, b ); +} + +// for height +__inline +float dot3w1(const float4 point, const float4 eqn) +{ + return dot3F4(point,eqn) + eqn.w; +} + +__inline +float4 normalize3(const float4 a) +{ + float4 n = make_float4(a.x, a.y, a.z, 0.f); + return fastNormalize4( n ); +// float length = sqrtf(dot3F4(a, a)); +// return 1.f/length * a; +} + +__inline +float4 normalize4(const float4 a) +{ + float length = sqrtf(dot4(a, a)); + return 1.f/length * a; +} + +__inline +float4 createEquation(const float4 a, const float4 b, const float4 c) +{ + float4 eqn; + float4 ab = b-a; + float4 ac = c-a; + eqn = normalize3( cross3(ab, ac) ); + eqn.w = -dot3F4(eqn,a); + return eqn; +} + +/////////////////////////////////////// +// Matrix3x3 +/////////////////////////////////////// + +typedef struct +{ + float4 m_row[3]; +}Matrix3x3; + +__inline +Matrix3x3 mtZero(); + +__inline +Matrix3x3 mtIdentity(); + +__inline +Matrix3x3 mtTranspose(Matrix3x3 m); + +__inline +Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b); + +__inline +float4 mtMul1(Matrix3x3 a, float4 b); + +__inline +float4 mtMul3(float4 a, Matrix3x3 b); + +__inline +Matrix3x3 mtZero() +{ + Matrix3x3 m; + m.m_row[0] = (float4)(0.f); + m.m_row[1] = (float4)(0.f); + m.m_row[2] = (float4)(0.f); + return m; +} + +__inline +Matrix3x3 mtIdentity() +{ + Matrix3x3 m; + m.m_row[0] = (float4)(1,0,0,0); + m.m_row[1] = (float4)(0,1,0,0); + m.m_row[2] = (float4)(0,0,1,0); + return m; +} + +__inline +Matrix3x3 mtTranspose(Matrix3x3 m) +{ + Matrix3x3 out; + out.m_row[0] = (float4)(m.m_row[0].x, m.m_row[1].x, m.m_row[2].x, 0.f); + out.m_row[1] = (float4)(m.m_row[0].y, m.m_row[1].y, m.m_row[2].y, 0.f); + out.m_row[2] = (float4)(m.m_row[0].z, m.m_row[1].z, m.m_row[2].z, 0.f); + return out; +} + +__inline +Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b) +{ + Matrix3x3 transB; + transB = mtTranspose( b ); + Matrix3x3 ans; + // why this doesn't run when 0ing in the for{} + a.m_row[0].w = 0.f; + a.m_row[1].w = 0.f; + a.m_row[2].w = 0.f; + for(int i=0; i<3; i++) + { +// a.m_row[i].w = 0.f; + ans.m_row[i].x = dot3F4(a.m_row[i],transB.m_row[0]); + ans.m_row[i].y = dot3F4(a.m_row[i],transB.m_row[1]); + ans.m_row[i].z = dot3F4(a.m_row[i],transB.m_row[2]); + ans.m_row[i].w = 0.f; + } + return ans; +} + +__inline +float4 mtMul1(Matrix3x3 a, float4 b) +{ + float4 ans; + ans.x = dot3F4( a.m_row[0], b ); + ans.y = dot3F4( a.m_row[1], b ); + ans.z = dot3F4( a.m_row[2], b ); + ans.w = 0.f; + return ans; +} + +__inline +float4 mtMul3(float4 a, Matrix3x3 b) +{ + float4 colx = make_float4(b.m_row[0].x, b.m_row[1].x, b.m_row[2].x, 0); + float4 coly = make_float4(b.m_row[0].y, b.m_row[1].y, b.m_row[2].y, 0); + float4 colz = make_float4(b.m_row[0].z, b.m_row[1].z, b.m_row[2].z, 0); + + float4 ans; + ans.x = dot3F4( a, colx ); + ans.y = dot3F4( a, coly ); + ans.z = dot3F4( a, colz ); + return ans; +} + +/////////////////////////////////////// +// Quaternion +/////////////////////////////////////// + +typedef float4 Quaternion; + +__inline +Quaternion qtMul(Quaternion a, Quaternion b); + +__inline +Quaternion qtNormalize(Quaternion in); + +__inline +float4 qtRotate(Quaternion q, float4 vec); + +__inline +Quaternion qtInvert(Quaternion q); + +__inline +Matrix3x3 qtGetRotationMatrix(Quaternion q); + + + +__inline +Quaternion qtMul(Quaternion a, Quaternion b) +{ + Quaternion ans; + ans = cross3( a, b ); + ans += a.w*b+b.w*a; +// ans.w = a.w*b.w - (a.x*b.x+a.y*b.y+a.z*b.z); + ans.w = a.w*b.w - dot3F4(a, b); + return ans; +} + +__inline +Quaternion qtNormalize(Quaternion in) +{ + return fastNormalize4(in); +// in /= length( in ); +// return in; +} +__inline +float4 qtRotate(Quaternion q, float4 vec) +{ + Quaternion qInv = qtInvert( q ); + float4 vcpy = vec; + vcpy.w = 0.f; + float4 out = qtMul(qtMul(q,vcpy),qInv); + return out; +} + +__inline +Quaternion qtInvert(Quaternion q) +{ + return (Quaternion)(-q.xyz, q.w); +} + +__inline +float4 qtInvRotate(const Quaternion q, float4 vec) +{ + return qtRotate( qtInvert( q ), vec ); +} + +__inline +Matrix3x3 qtGetRotationMatrix(Quaternion quat) +{ + float4 quat2 = (float4)(quat.x*quat.x, quat.y*quat.y, quat.z*quat.z, 0.f); + Matrix3x3 out; + + out.m_row[0].x=1-2*quat2.y-2*quat2.z; + out.m_row[0].y=2*quat.x*quat.y-2*quat.w*quat.z; + out.m_row[0].z=2*quat.x*quat.z+2*quat.w*quat.y; + out.m_row[0].w = 0.f; + + out.m_row[1].x=2*quat.x*quat.y+2*quat.w*quat.z; + out.m_row[1].y=1-2*quat2.x-2*quat2.z; + out.m_row[1].z=2*quat.y*quat.z-2*quat.w*quat.x; + out.m_row[1].w = 0.f; + + out.m_row[2].x=2*quat.x*quat.z-2*quat.w*quat.y; + out.m_row[2].y=2*quat.y*quat.z+2*quat.w*quat.x; + out.m_row[2].z=1-2*quat2.x-2*quat2.y; + out.m_row[2].w = 0.f; + + return out; +} + + + + +#define WG_SIZE 64 + +typedef struct +{ + float4 m_pos; + Quaternion m_quat; + float4 m_linVel; + float4 m_angVel; + + u32 m_shapeIdx; + u32 m_shapeType; + float m_invMass; + float m_restituitionCoeff; + float m_frictionCoeff; +} Body; + +typedef struct +{ + Matrix3x3 m_invInertia; + Matrix3x3 m_initInvInertia; +} Shape; + +typedef struct +{ + float4 m_linear; + float4 m_worldPos[4]; + float4 m_center; + float m_jacCoeffInv[4]; + float m_b[4]; + float m_appliedRambdaDt[4]; + + float m_fJacCoeffInv[2]; + float m_fAppliedRambdaDt[2]; + + u32 m_bodyA; + u32 m_bodyB; + + int m_batchIdx; + u32 m_paddings[1]; +} Constraint4; + +typedef struct +{ + float4 m_worldPos[4]; + float4 m_worldNormal; + u32 m_coeffs; + int m_batchIdx; + + u32 m_bodyAPtr; + u32 m_bodyBPtr; +} Contact4; + +typedef struct +{ + int m_nConstraints; + int m_start; + int m_batchIdx; + int m_nSplit; +// int m_paddings[1]; +} ConstBuffer; + +typedef struct +{ + int m_solveFriction; + int m_maxBatch; // long batch really kills the performance + int m_batchIdx; + int m_nSplit; +// int m_paddings[1]; +} ConstBufferBatchSolve; + + +void setLinearAndAngular( float4 n, float4 r0, float4 r1, float4* linear, float4* angular0, float4* angular1) +{ + *linear = -n; + *angular0 = -cross3(r0, n); + *angular1 = cross3(r1, n); +} + + +float calcRelVel( float4 l0, float4 l1, float4 a0, float4 a1, float4 linVel0, float4 angVel0, float4 linVel1, float4 angVel1 ) +{ + return dot3F4(l0, linVel0) + dot3F4(a0, angVel0) + dot3F4(l1, linVel1) + dot3F4(a1, angVel1); +} + + +float calcJacCoeff(const float4 linear0, const float4 linear1, const float4 angular0, const float4 angular1, + float invMass0, const Matrix3x3* invInertia0, float invMass1, const Matrix3x3* invInertia1) +{ + // linear0,1 are normlized + float jmj0 = invMass0;//dot3F4(linear0, linear0)*invMass0; + float jmj1 = dot3F4(mtMul3(angular0,*invInertia0), angular0); + float jmj2 = invMass1;//dot3F4(linear1, linear1)*invMass1; + float jmj3 = dot3F4(mtMul3(angular1,*invInertia1), angular1); + return -1.f/(jmj0+jmj1+jmj2+jmj3); +} + + + +void solveContact(__global Constraint4* cs, + float4 posA, float4* linVelA, float4* angVelA, float invMassA, Matrix3x3 invInertiaA, + float4 posB, float4* linVelB, float4* angVelB, float invMassB, Matrix3x3 invInertiaB) +{ + float minRambdaDt = 0; + float maxRambdaDt = FLT_MAX; + + for(int ic=0; ic<4; ic++) + { + if( cs->m_jacCoeffInv[ic] == 0.f ) continue; + + float4 angular0, angular1, linear; + float4 r0 = cs->m_worldPos[ic] - posA; + float4 r1 = cs->m_worldPos[ic] - posB; + setLinearAndAngular( -cs->m_linear, r0, r1, &linear, &angular0, &angular1 ); + + float rambdaDt = calcRelVel( cs->m_linear, -cs->m_linear, angular0, angular1, + *linVelA, *angVelA, *linVelB, *angVelB ) + cs->m_b[ic]; + rambdaDt *= cs->m_jacCoeffInv[ic]; + + { + float prevSum = cs->m_appliedRambdaDt[ic]; + float updated = prevSum; + updated += rambdaDt; + updated = max2( updated, minRambdaDt ); + updated = min2( updated, maxRambdaDt ); + rambdaDt = updated - prevSum; + cs->m_appliedRambdaDt[ic] = updated; + } + + float4 linImp0 = invMassA*linear*rambdaDt; + float4 linImp1 = invMassB*(-linear)*rambdaDt; + float4 angImp0 = mtMul1(invInertiaA, angular0)*rambdaDt; + float4 angImp1 = mtMul1(invInertiaB, angular1)*rambdaDt; + + *linVelA += linImp0; + *angVelA += angImp0; + *linVelB += linImp1; + *angVelB += angImp1; + } +} + + +void solveFriction(__global Constraint4* cs, + float4 posA, float4* linVelA, float4* angVelA, float invMassA, Matrix3x3 invInertiaA, + float4 posB, float4* linVelB, float4* angVelB, float invMassB, Matrix3x3 invInertiaB, + float maxRambdaDt[4], float minRambdaDt[4]) +{ + if( cs->m_fJacCoeffInv[0] == 0 && cs->m_fJacCoeffInv[0] == 0 ) return; + const float4 center = cs->m_center; + + float4 n = -cs->m_linear; + + float4 tangent[2]; + tangent[0] = cross3( n, cs->m_worldPos[0]-center ); + tangent[1] = cross3( tangent[0], n ); + tangent[0] = normalize3( tangent[0] ); + tangent[1] = normalize3( tangent[1] ); + + float4 angular0, angular1, linear; + float4 r0 = center - posA; + float4 r1 = center - posB; + for(int i=0; i<2; i++) + { + setLinearAndAngular( tangent[i], r0, r1, &linear, &angular0, &angular1 ); + float rambdaDt = calcRelVel(linear, -linear, angular0, angular1, + *linVelA, *angVelA, *linVelB, *angVelB ); + rambdaDt *= cs->m_fJacCoeffInv[i]; + + { + float prevSum = cs->m_fAppliedRambdaDt[i]; + float updated = prevSum; + updated += rambdaDt; + updated = max2( updated, minRambdaDt[i] ); + updated = min2( updated, maxRambdaDt[i] ); + rambdaDt = updated - prevSum; + cs->m_fAppliedRambdaDt[i] = updated; + } + + float4 linImp0 = invMassA*linear*rambdaDt; + float4 linImp1 = invMassB*(-linear)*rambdaDt; + float4 angImp0 = mtMul1(invInertiaA, angular0)*rambdaDt; + float4 angImp1 = mtMul1(invInertiaB, angular1)*rambdaDt; + + *linVelA += linImp0; + *angVelA += angImp0; + *linVelB += linImp1; + *angVelB += angImp1; + } + { // angular damping for point constraint + float4 ab = normalize3( posB - posA ); + float4 ac = normalize3( center - posA ); + if( dot3F4( ab, ac ) > 0.95f || (invMassA == 0.f || invMassB == 0.f)) + { + float angNA = dot3F4( n, *angVelA ); + float angNB = dot3F4( n, *angVelB ); + + *angVelA -= (angNA*0.1f)*n; + *angVelB -= (angNB*0.1f)*n; + } + } +} + +void solveAConstraint(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* ldsCs) +{ + float frictionCoeff = ldsCs[0].m_linear.w; + int aIdx = ldsCs[0].m_bodyA; + int bIdx = ldsCs[0].m_bodyB; + + float4 posA = gBodies[aIdx].m_pos; + float4 linVelA = gBodies[aIdx].m_linVel; + float4 angVelA = gBodies[aIdx].m_angVel; + float invMassA = gBodies[aIdx].m_invMass; + Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia; + + float4 posB = gBodies[bIdx].m_pos; + float4 linVelB = gBodies[bIdx].m_linVel; + float4 angVelB = gBodies[bIdx].m_angVel; + float invMassB = gBodies[bIdx].m_invMass; + Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia; + + + { + solveContact( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA, + posB, &linVelB, &angVelB, invMassB, invInertiaB ); + } + + { + float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX}; + float minRambdaDt[4] = {0.f,0.f,0.f,0.f}; + + float sum = 0; + for(int j=0; j<4; j++) + { + sum +=ldsCs[0].m_appliedRambdaDt[j]; + } + frictionCoeff = 0.7f; + for(int j=0; j<4; j++) + { + maxRambdaDt[j] = frictionCoeff*sum; + minRambdaDt[j] = -maxRambdaDt[j]; + } + + solveFriction( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA, + posB, &linVelB, &angVelB, invMassB, invInertiaB, maxRambdaDt, minRambdaDt ); + } + + gBodies[aIdx].m_linVel = linVelA; + gBodies[aIdx].m_angVel = angVelA; + gBodies[bIdx].m_linVel = linVelB; + gBodies[bIdx].m_angVel = angVelB; +} + +void solveContactConstraint(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* ldsCs) +{ + float frictionCoeff = ldsCs[0].m_linear.w; + int aIdx = ldsCs[0].m_bodyA; + int bIdx = ldsCs[0].m_bodyB; + + float4 posA = gBodies[aIdx].m_pos; + float4 linVelA = gBodies[aIdx].m_linVel; + float4 angVelA = gBodies[aIdx].m_angVel; + float invMassA = gBodies[aIdx].m_invMass; + Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia; + + float4 posB = gBodies[bIdx].m_pos; + float4 linVelB = gBodies[bIdx].m_linVel; + float4 angVelB = gBodies[bIdx].m_angVel; + float invMassB = gBodies[bIdx].m_invMass; + Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia; + + solveContact( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA, + posB, &linVelB, &angVelB, invMassB, invInertiaB ); + + gBodies[aIdx].m_linVel = linVelA; + gBodies[aIdx].m_angVel = angVelA; + gBodies[bIdx].m_linVel = linVelB; + gBodies[bIdx].m_angVel = angVelB; +} + +void solveFrictionConstraint(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* ldsCs) +{ + float frictionCoeff = ldsCs[0].m_linear.w; + int aIdx = ldsCs[0].m_bodyA; + int bIdx = ldsCs[0].m_bodyB; + + float4 posA = gBodies[aIdx].m_pos; + float4 linVelA = gBodies[aIdx].m_linVel; + float4 angVelA = gBodies[aIdx].m_angVel; + float invMassA = gBodies[aIdx].m_invMass; + Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia; + + float4 posB = gBodies[bIdx].m_pos; + float4 linVelB = gBodies[bIdx].m_linVel; + float4 angVelB = gBodies[bIdx].m_angVel; + float invMassB = gBodies[bIdx].m_invMass; + Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia; + + { + float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX}; + float minRambdaDt[4] = {0.f,0.f,0.f,0.f}; + + float sum = 0; + for(int j=0; j<4; j++) + { + sum +=ldsCs[0].m_appliedRambdaDt[j]; + } + frictionCoeff = 0.7f; + for(int j=0; j<4; j++) + { + maxRambdaDt[j] = frictionCoeff*sum; + minRambdaDt[j] = -maxRambdaDt[j]; + } + + solveFriction( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA, + posB, &linVelB, &angVelB, invMassB, invInertiaB, maxRambdaDt, minRambdaDt ); + } + + gBodies[aIdx].m_linVel = linVelA; + gBodies[aIdx].m_angVel = angVelA; + gBodies[bIdx].m_linVel = linVelB; + gBodies[bIdx].m_angVel = angVelB; +} + +typedef struct +{ + int m_valInt0; + int m_valInt1; + int m_valInt2; + int m_valInt3; + + float m_val0; + float m_val1; + float m_val2; + float m_val3; +} SolverDebugInfo; + + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +//void BatchSolveKernel(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* gConstraints, __global int* gN, __global int* gOffsets, __global SolverDebugInfo* debugInfo, ConstBufferBatchSolve cb) +void BatchSolveKernel(__global Body* gBodies, +__global Shape* gShapes, +__global Constraint4* gConstraints, +__global int* gN, +__global int* gOffsets, +ConstBufferBatchSolve cb) +{ + __local int ldsBatchIdx[WG_SIZE+1]; + + __local int ldsCurBatch; + __local int ldsNextBatch; + __local int ldsStart; + + int lIdx = GET_LOCAL_IDX; + int wgIdx = GET_GROUP_IDX; + + int gIdx = GET_GLOBAL_IDX; +// debugInfo[gIdx].m_valInt0 = gIdx; + //debugInfo[gIdx].m_valInt1 = GET_GROUP_SIZE; + + const int solveFriction = cb.m_solveFriction; + const int maxBatch = cb.m_maxBatch; + const int bIdx = cb.m_batchIdx; + const int nSplit = cb.m_nSplit; + + int xIdx = (wgIdx/(nSplit/2))*2 + (bIdx&1); + int yIdx = (wgIdx%(nSplit/2))*2 + (bIdx>>1); + int cellIdx = xIdx+yIdx*nSplit; + + if( gN[cellIdx] == 0 ) + return; + + const int start = gOffsets[cellIdx]; + const int end = start + gN[cellIdx]; + + + if( lIdx == 0 ) + { + ldsCurBatch = 0; + ldsNextBatch = 0; + ldsStart = start; + } + + + GROUP_LDS_BARRIER; + + int idx=ldsStart+lIdx; + while (ldsCurBatch < maxBatch) + { + for(; idxm_bodyA = src.m_bodyAPtr; + dstC->m_bodyB = src.m_bodyBPtr; + + float dtInv = 1.f/dt; + for(int ic=0; ic<4; ic++) + { + dstC->m_appliedRambdaDt[ic] = 0.f; + } + dstC->m_fJacCoeffInv[0] = dstC->m_fJacCoeffInv[1] = 0.f; + + + dstC->m_linear = -src.m_worldNormal; + dstC->m_linear.w = 0.7f ;//src.getFrictionCoeff() ); + for(int ic=0; ic<4; ic++) + { + float4 r0 = src.m_worldPos[ic] - posA; + float4 r1 = src.m_worldPos[ic] - posB; + + if( ic >= src.m_worldNormal.w )//npoints + { + dstC->m_jacCoeffInv[ic] = 0.f; + continue; + } + + float relVelN; + { + float4 linear, angular0, angular1; + setLinearAndAngular(src.m_worldNormal, r0, r1, &linear, &angular0, &angular1); + + dstC->m_jacCoeffInv[ic] = calcJacCoeff(linear, -linear, angular0, angular1, + invMassA, &invInertiaA, invMassB, &invInertiaB ); + + relVelN = calcRelVel(linear, -linear, angular0, angular1, + linVelA, angVelA, linVelB, angVelB); + + float e = 0.f;//src.getRestituitionCoeff(); + if( relVelN*relVelN < 0.004f ) e = 0.f; + + dstC->m_b[ic] = e*relVelN; + //float penetration = src.m_worldPos[ic].w; + dstC->m_b[ic] += (src.m_worldPos[ic].w + positionDrift)*positionConstraintCoeff*dtInv; + dstC->m_appliedRambdaDt[ic] = 0.f; + } + } + + if( src.m_worldNormal.w > 1 )//npoints + { // prepare friction + float4 center = make_float4(0.f); + for(int i=0; im_fJacCoeffInv[i] = calcJacCoeff(linear, -linear, angular0, angular1, + invMassA, &invInertiaA, invMassB, &invInertiaB ); + dstC->m_fAppliedRambdaDt[i] = 0.f; + } + dstC->m_center = center; + } + else + { + // single point constraint + } + + for(int i=0; i<4; i++) + { + if( im_worldPos[i] = src.m_worldPos[i]; + } + else + { + dstC->m_worldPos[i] = make_float4(0.f); + } + } +} + +typedef struct +{ + int m_nContacts; + float m_dt; + float m_positionDrift; + float m_positionConstraintCoeff; +} ConstBufferCTC; + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void ContactToConstraintKernel(__global Contact4* gContact, __global Body* gBodies, __global Shape* gShapes, __global Constraint4* gConstraintOut, ConstBufferCTC cb) +{ + int gIdx = GET_GLOBAL_IDX; + int nContacts = cb.m_nContacts; + float dt = cb.m_dt; + float positionDrift = cb.m_positionDrift; + float positionConstraintCoeff = cb.m_positionConstraintCoeff; + + if( gIdx < nContacts ) + { + int aIdx = gContact[gIdx].m_bodyAPtr; + int bIdx = gContact[gIdx].m_bodyBPtr; + + float4 posA = gBodies[aIdx].m_pos; + float4 linVelA = gBodies[aIdx].m_linVel; + float4 angVelA = gBodies[aIdx].m_angVel; + float invMassA = gBodies[aIdx].m_invMass; + Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia; + + float4 posB = gBodies[bIdx].m_pos; + float4 linVelB = gBodies[bIdx].m_linVel; + float4 angVelB = gBodies[bIdx].m_angVel; + float invMassB = gBodies[bIdx].m_invMass; + Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia; + + Constraint4 cs; + + setConstraint4( posA, linVelA, angVelA, invMassA, invInertiaA, posB, linVelB, angVelB, invMassB, invInertiaB, + gContact[gIdx], dt, positionDrift, positionConstraintCoeff, + &cs ); + + cs.m_batchIdx = gContact[gIdx].m_batchIdx; + + gConstraintOut[gIdx] = cs; + } +} + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void CopyConstraintKernel(__global Contact4* gIn, __global Contact4* gOut, int4 cb ) +{ + int gIdx = GET_GLOBAL_IDX; + if( gIdx < cb.x ) + { + gOut[gIdx] = gIn[gIdx]; + } +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.h b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.h new file mode 100644 index 000000000..c80a6ae05 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/SolverKernels.h @@ -0,0 +1,1037 @@ +static const char* solverKernelsCL= \ +"#pragma OPENCL EXTENSION cl_amd_printf : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable\n" +"#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_extended_atomics : enable\n" +"#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable\n" +"\n" +"\n" +"#ifdef cl_ext_atomic_counters_32\n" +"#pragma OPENCL EXTENSION cl_ext_atomic_counters_32 : enable\n" +"#else\n" +"#define counter32_t volatile global int*\n" +"#endif\n" +"\n" +"typedef unsigned int u32;\n" +"typedef unsigned short u16;\n" +"typedef unsigned char u8;\n" +"\n" +"#define GET_GROUP_IDX get_group_id(0)\n" +"#define GET_LOCAL_IDX get_local_id(0)\n" +"#define GET_GLOBAL_IDX get_global_id(0)\n" +"#define GET_GROUP_SIZE get_local_size(0)\n" +"#define GET_NUM_GROUPS get_num_groups(0)\n" +"#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE)\n" +"#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE)\n" +"#define AtomInc(x) atom_inc(&(x))\n" +"#define AtomInc1(x, out) out = atom_inc(&(x))\n" +"#define AppendInc(x, out) out = atomic_inc(x)\n" +"#define AtomAdd(x, value) atom_add(&(x), value)\n" +"#define AtomCmpxhg(x, cmp, value) atom_cmpxchg( &(x), cmp, value )\n" +"#define AtomXhg(x, value) atom_xchg ( &(x), value )\n" +"\n" +"\n" +"#define SELECT_UINT4( b, a, condition ) select( b,a,condition )\n" +"\n" +"#define make_float4 (float4)\n" +"#define make_float2 (float2)\n" +"#define make_uint4 (uint4)\n" +"#define make_int4 (int4)\n" +"#define make_uint2 (uint2)\n" +"#define make_int2 (int2)\n" +"\n" +"\n" +"#define max2 max\n" +"#define min2 min\n" +"\n" +"\n" +"///////////////////////////////////////\n" +"// Vector\n" +"///////////////////////////////////////\n" +"__inline\n" +"float fastDiv(float numerator, float denominator)\n" +"{\n" +" return native_divide(numerator, denominator); \n" +"// return numerator/denominator; \n" +"}\n" +"\n" +"__inline\n" +"float4 fastDiv4(float4 numerator, float4 denominator)\n" +"{\n" +" return native_divide(numerator, denominator); \n" +"}\n" +"\n" +"__inline\n" +"float fastSqrtf(float f2)\n" +"{\n" +" return native_sqrt(f2);\n" +"// return sqrt(f2);\n" +"}\n" +"\n" +"__inline\n" +"float fastRSqrt(float f2)\n" +"{\n" +" return native_rsqrt(f2);\n" +"}\n" +"\n" +"__inline\n" +"float fastLength4(float4 v)\n" +"{\n" +" return fast_length(v);\n" +"}\n" +"\n" +"__inline\n" +"float4 fastNormalize4(float4 v)\n" +"{\n" +" return fast_normalize(v);\n" +"}\n" +"\n" +"\n" +"__inline\n" +"float sqrtf(float a)\n" +"{\n" +"// return sqrt(a);\n" +" return native_sqrt(a);\n" +"}\n" +"\n" +"__inline\n" +"float4 cross3(float4 a, float4 b)\n" +"{\n" +" return cross(a,b);\n" +"}\n" +"\n" +"__inline\n" +"float dot3F4(float4 a, float4 b)\n" +"{\n" +" float4 a1 = make_float4(a.xyz,0.f);\n" +" float4 b1 = make_float4(b.xyz,0.f);\n" +" return dot(a1, b1);\n" +"}\n" +"\n" +"__inline\n" +"float length3(const float4 a)\n" +"{\n" +" return sqrtf(dot3F4(a,a));\n" +"}\n" +"\n" +"__inline\n" +"float dot4(const float4 a, const float4 b)\n" +"{\n" +" return dot( a, b );\n" +"}\n" +"\n" +"// for height\n" +"__inline\n" +"float dot3w1(const float4 point, const float4 eqn)\n" +"{\n" +" return dot3F4(point,eqn) + eqn.w;\n" +"}\n" +"\n" +"__inline\n" +"float4 normalize3(const float4 a)\n" +"{\n" +" float4 n = make_float4(a.x, a.y, a.z, 0.f);\n" +" return fastNormalize4( n );\n" +"// float length = sqrtf(dot3F4(a, a));\n" +"// return 1.f/length * a;\n" +"}\n" +"\n" +"__inline\n" +"float4 normalize4(const float4 a)\n" +"{\n" +" float length = sqrtf(dot4(a, a));\n" +" return 1.f/length * a;\n" +"}\n" +"\n" +"__inline\n" +"float4 createEquation(const float4 a, const float4 b, const float4 c)\n" +"{\n" +" float4 eqn;\n" +" float4 ab = b-a;\n" +" float4 ac = c-a;\n" +" eqn = normalize3( cross3(ab, ac) );\n" +" eqn.w = -dot3F4(eqn,a);\n" +" return eqn;\n" +"}\n" +"\n" +"///////////////////////////////////////\n" +"// Matrix3x3\n" +"///////////////////////////////////////\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_row[3];\n" +"}Matrix3x3;\n" +"\n" +"__inline\n" +"Matrix3x3 mtZero();\n" +"\n" +"__inline\n" +"Matrix3x3 mtIdentity();\n" +"\n" +"__inline\n" +"Matrix3x3 mtTranspose(Matrix3x3 m);\n" +"\n" +"__inline\n" +"Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b);\n" +"\n" +"__inline\n" +"float4 mtMul1(Matrix3x3 a, float4 b);\n" +"\n" +"__inline\n" +"float4 mtMul3(float4 a, Matrix3x3 b);\n" +"\n" +"__inline\n" +"Matrix3x3 mtZero()\n" +"{\n" +" Matrix3x3 m;\n" +" m.m_row[0] = (float4)(0.f);\n" +" m.m_row[1] = (float4)(0.f);\n" +" m.m_row[2] = (float4)(0.f);\n" +" return m;\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 mtIdentity()\n" +"{\n" +" Matrix3x3 m;\n" +" m.m_row[0] = (float4)(1,0,0,0);\n" +" m.m_row[1] = (float4)(0,1,0,0);\n" +" m.m_row[2] = (float4)(0,0,1,0);\n" +" return m;\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 mtTranspose(Matrix3x3 m)\n" +"{\n" +" Matrix3x3 out;\n" +" out.m_row[0] = (float4)(m.m_row[0].x, m.m_row[1].x, m.m_row[2].x, 0.f);\n" +" out.m_row[1] = (float4)(m.m_row[0].y, m.m_row[1].y, m.m_row[2].y, 0.f);\n" +" out.m_row[2] = (float4)(m.m_row[0].z, m.m_row[1].z, m.m_row[2].z, 0.f);\n" +" return out;\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b)\n" +"{\n" +" Matrix3x3 transB;\n" +" transB = mtTranspose( b );\n" +" Matrix3x3 ans;\n" +" // why this doesn't run when 0ing in the for{}\n" +" a.m_row[0].w = 0.f;\n" +" a.m_row[1].w = 0.f;\n" +" a.m_row[2].w = 0.f;\n" +" for(int i=0; i<3; i++)\n" +" {\n" +"// a.m_row[i].w = 0.f;\n" +" ans.m_row[i].x = dot3F4(a.m_row[i],transB.m_row[0]);\n" +" ans.m_row[i].y = dot3F4(a.m_row[i],transB.m_row[1]);\n" +" ans.m_row[i].z = dot3F4(a.m_row[i],transB.m_row[2]);\n" +" ans.m_row[i].w = 0.f;\n" +" }\n" +" return ans;\n" +"}\n" +"\n" +"__inline\n" +"float4 mtMul1(Matrix3x3 a, float4 b)\n" +"{\n" +" float4 ans;\n" +" ans.x = dot3F4( a.m_row[0], b );\n" +" ans.y = dot3F4( a.m_row[1], b );\n" +" ans.z = dot3F4( a.m_row[2], b );\n" +" ans.w = 0.f;\n" +" return ans;\n" +"}\n" +"\n" +"__inline\n" +"float4 mtMul3(float4 a, Matrix3x3 b)\n" +"{\n" +" float4 colx = make_float4(b.m_row[0].x, b.m_row[1].x, b.m_row[2].x, 0);\n" +" float4 coly = make_float4(b.m_row[0].y, b.m_row[1].y, b.m_row[2].y, 0);\n" +" float4 colz = make_float4(b.m_row[0].z, b.m_row[1].z, b.m_row[2].z, 0);\n" +"\n" +" float4 ans;\n" +" ans.x = dot3F4( a, colx );\n" +" ans.y = dot3F4( a, coly );\n" +" ans.z = dot3F4( a, colz );\n" +" return ans;\n" +"}\n" +"\n" +"///////////////////////////////////////\n" +"// Quaternion\n" +"///////////////////////////////////////\n" +"\n" +"typedef float4 Quaternion;\n" +"\n" +"__inline\n" +"Quaternion qtMul(Quaternion a, Quaternion b);\n" +"\n" +"__inline\n" +"Quaternion qtNormalize(Quaternion in);\n" +"\n" +"__inline\n" +"float4 qtRotate(Quaternion q, float4 vec);\n" +"\n" +"__inline\n" +"Quaternion qtInvert(Quaternion q);\n" +"\n" +"__inline\n" +"Matrix3x3 qtGetRotationMatrix(Quaternion q);\n" +"\n" +"\n" +"\n" +"__inline\n" +"Quaternion qtMul(Quaternion a, Quaternion b)\n" +"{\n" +" Quaternion ans;\n" +" ans = cross3( a, b );\n" +" ans += a.w*b+b.w*a;\n" +"// ans.w = a.w*b.w - (a.x*b.x+a.y*b.y+a.z*b.z);\n" +" ans.w = a.w*b.w - dot3F4(a, b);\n" +" return ans;\n" +"}\n" +"\n" +"__inline\n" +"Quaternion qtNormalize(Quaternion in)\n" +"{\n" +" return fastNormalize4(in);\n" +"// in /= length( in );\n" +"// return in;\n" +"}\n" +"__inline\n" +"float4 qtRotate(Quaternion q, float4 vec)\n" +"{\n" +" Quaternion qInv = qtInvert( q );\n" +" float4 vcpy = vec;\n" +" vcpy.w = 0.f;\n" +" float4 out = qtMul(qtMul(q,vcpy),qInv);\n" +" return out;\n" +"}\n" +"\n" +"__inline\n" +"Quaternion qtInvert(Quaternion q)\n" +"{\n" +" return (Quaternion)(-q.xyz, q.w);\n" +"}\n" +"\n" +"__inline\n" +"float4 qtInvRotate(const Quaternion q, float4 vec)\n" +"{\n" +" return qtRotate( qtInvert( q ), vec );\n" +"}\n" +"\n" +"__inline\n" +"Matrix3x3 qtGetRotationMatrix(Quaternion quat)\n" +"{\n" +" float4 quat2 = (float4)(quat.x*quat.x, quat.y*quat.y, quat.z*quat.z, 0.f);\n" +" Matrix3x3 out;\n" +"\n" +" out.m_row[0].x=1-2*quat2.y-2*quat2.z;\n" +" out.m_row[0].y=2*quat.x*quat.y-2*quat.w*quat.z;\n" +" out.m_row[0].z=2*quat.x*quat.z+2*quat.w*quat.y;\n" +" out.m_row[0].w = 0.f;\n" +"\n" +" out.m_row[1].x=2*quat.x*quat.y+2*quat.w*quat.z;\n" +" out.m_row[1].y=1-2*quat2.x-2*quat2.z;\n" +" out.m_row[1].z=2*quat.y*quat.z-2*quat.w*quat.x;\n" +" out.m_row[1].w = 0.f;\n" +"\n" +" out.m_row[2].x=2*quat.x*quat.z-2*quat.w*quat.y;\n" +" out.m_row[2].y=2*quat.y*quat.z+2*quat.w*quat.x;\n" +" out.m_row[2].z=1-2*quat2.x-2*quat2.y;\n" +" out.m_row[2].w = 0.f;\n" +"\n" +" return out;\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"#define WG_SIZE 64\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_pos;\n" +" Quaternion m_quat;\n" +" float4 m_linVel;\n" +" float4 m_angVel;\n" +"\n" +" u32 m_shapeIdx;\n" +" u32 m_shapeType;\n" +" float m_invMass;\n" +" float m_restituitionCoeff;\n" +" float m_frictionCoeff;\n" +"} Body;\n" +"\n" +"typedef struct\n" +"{\n" +" Matrix3x3 m_invInertia;\n" +" Matrix3x3 m_initInvInertia;\n" +"} Shape;\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_linear;\n" +" float4 m_worldPos[4];\n" +" float4 m_center; \n" +" float m_jacCoeffInv[4];\n" +" float m_b[4];\n" +" float m_appliedRambdaDt[4];\n" +"\n" +" float m_fJacCoeffInv[2]; \n" +" float m_fAppliedRambdaDt[2]; \n" +"\n" +" u32 m_bodyA;\n" +" u32 m_bodyB;\n" +"\n" +" int m_batchIdx;\n" +" u32 m_paddings[1];\n" +"} Constraint4;\n" +"\n" +"typedef struct\n" +"{\n" +" float4 m_worldPos[4];\n" +" float4 m_worldNormal;\n" +" u32 m_coeffs;\n" +" int m_batchIdx;\n" +"\n" +" u32 m_bodyAPtr;\n" +" u32 m_bodyBPtr;\n" +"} Contact4;\n" +"\n" +"typedef struct\n" +"{\n" +" int m_nConstraints;\n" +" int m_start;\n" +" int m_batchIdx;\n" +" int m_nSplit;\n" +"// int m_paddings[1];\n" +"} ConstBuffer;\n" +"\n" +"typedef struct\n" +"{\n" +" int m_solveFriction;\n" +" int m_maxBatch; // long batch really kills the performance\n" +" int m_batchIdx;\n" +" int m_nSplit;\n" +"// int m_paddings[1];\n" +"} ConstBufferBatchSolve;\n" +"\n" +"\n" +"void setLinearAndAngular( float4 n, float4 r0, float4 r1, float4* linear, float4* angular0, float4* angular1)\n" +"{\n" +" *linear = -n;\n" +" *angular0 = -cross3(r0, n);\n" +" *angular1 = cross3(r1, n);\n" +"}\n" +"\n" +"\n" +"float calcRelVel( float4 l0, float4 l1, float4 a0, float4 a1, float4 linVel0, float4 angVel0, float4 linVel1, float4 angVel1 )\n" +"{\n" +" return dot3F4(l0, linVel0) + dot3F4(a0, angVel0) + dot3F4(l1, linVel1) + dot3F4(a1, angVel1);\n" +"}\n" +"\n" +"\n" +"float calcJacCoeff(const float4 linear0, const float4 linear1, const float4 angular0, const float4 angular1,\n" +" float invMass0, const Matrix3x3* invInertia0, float invMass1, const Matrix3x3* invInertia1)\n" +"{\n" +" // linear0,1 are normlized\n" +" float jmj0 = invMass0;//dot3F4(linear0, linear0)*invMass0;\n" +" float jmj1 = dot3F4(mtMul3(angular0,*invInertia0), angular0);\n" +" float jmj2 = invMass1;//dot3F4(linear1, linear1)*invMass1;\n" +" float jmj3 = dot3F4(mtMul3(angular1,*invInertia1), angular1);\n" +" return -1.f/(jmj0+jmj1+jmj2+jmj3);\n" +"}\n" +"\n" +"\n" +"\n" +"void solveContact(__global Constraint4* cs,\n" +" float4 posA, float4* linVelA, float4* angVelA, float invMassA, Matrix3x3 invInertiaA,\n" +" float4 posB, float4* linVelB, float4* angVelB, float invMassB, Matrix3x3 invInertiaB)\n" +"{\n" +" float minRambdaDt = 0;\n" +" float maxRambdaDt = FLT_MAX;\n" +"\n" +" for(int ic=0; ic<4; ic++)\n" +" {\n" +" if( cs->m_jacCoeffInv[ic] == 0.f ) continue;\n" +"\n" +" float4 angular0, angular1, linear;\n" +" float4 r0 = cs->m_worldPos[ic] - posA;\n" +" float4 r1 = cs->m_worldPos[ic] - posB;\n" +" setLinearAndAngular( -cs->m_linear, r0, r1, &linear, &angular0, &angular1 );\n" +"\n" +" float rambdaDt = calcRelVel( cs->m_linear, -cs->m_linear, angular0, angular1, \n" +" *linVelA, *angVelA, *linVelB, *angVelB ) + cs->m_b[ic];\n" +" rambdaDt *= cs->m_jacCoeffInv[ic];\n" +"\n" +" {\n" +" float prevSum = cs->m_appliedRambdaDt[ic];\n" +" float updated = prevSum;\n" +" updated += rambdaDt;\n" +" updated = max2( updated, minRambdaDt );\n" +" updated = min2( updated, maxRambdaDt );\n" +" rambdaDt = updated - prevSum;\n" +" cs->m_appliedRambdaDt[ic] = updated;\n" +" }\n" +"\n" +" float4 linImp0 = invMassA*linear*rambdaDt;\n" +" float4 linImp1 = invMassB*(-linear)*rambdaDt;\n" +" float4 angImp0 = mtMul1(invInertiaA, angular0)*rambdaDt;\n" +" float4 angImp1 = mtMul1(invInertiaB, angular1)*rambdaDt;\n" +"\n" +" *linVelA += linImp0;\n" +" *angVelA += angImp0;\n" +" *linVelB += linImp1;\n" +" *angVelB += angImp1;\n" +" }\n" +"}\n" +"\n" +"\n" +"void solveFriction(__global Constraint4* cs,\n" +" float4 posA, float4* linVelA, float4* angVelA, float invMassA, Matrix3x3 invInertiaA,\n" +" float4 posB, float4* linVelB, float4* angVelB, float invMassB, Matrix3x3 invInertiaB,\n" +" float maxRambdaDt[4], float minRambdaDt[4])\n" +"{\n" +" if( cs->m_fJacCoeffInv[0] == 0 && cs->m_fJacCoeffInv[0] == 0 ) return;\n" +" const float4 center = cs->m_center;\n" +"\n" +" float4 n = -cs->m_linear;\n" +"\n" +" float4 tangent[2];\n" +" tangent[0] = cross3( n, cs->m_worldPos[0]-center );\n" +" tangent[1] = cross3( tangent[0], n );\n" +" tangent[0] = normalize3( tangent[0] );\n" +" tangent[1] = normalize3( tangent[1] );\n" +"\n" +" float4 angular0, angular1, linear;\n" +" float4 r0 = center - posA;\n" +" float4 r1 = center - posB;\n" +" for(int i=0; i<2; i++)\n" +" {\n" +" setLinearAndAngular( tangent[i], r0, r1, &linear, &angular0, &angular1 );\n" +" float rambdaDt = calcRelVel(linear, -linear, angular0, angular1,\n" +" *linVelA, *angVelA, *linVelB, *angVelB );\n" +" rambdaDt *= cs->m_fJacCoeffInv[i];\n" +"\n" +" {\n" +" float prevSum = cs->m_fAppliedRambdaDt[i];\n" +" float updated = prevSum;\n" +" updated += rambdaDt;\n" +" updated = max2( updated, minRambdaDt[i] );\n" +" updated = min2( updated, maxRambdaDt[i] );\n" +" rambdaDt = updated - prevSum;\n" +" cs->m_fAppliedRambdaDt[i] = updated;\n" +" }\n" +"\n" +" float4 linImp0 = invMassA*linear*rambdaDt;\n" +" float4 linImp1 = invMassB*(-linear)*rambdaDt;\n" +" float4 angImp0 = mtMul1(invInertiaA, angular0)*rambdaDt;\n" +" float4 angImp1 = mtMul1(invInertiaB, angular1)*rambdaDt;\n" +"\n" +" *linVelA += linImp0;\n" +" *angVelA += angImp0;\n" +" *linVelB += linImp1;\n" +" *angVelB += angImp1;\n" +" }\n" +" { // angular damping for point constraint\n" +" float4 ab = normalize3( posB - posA );\n" +" float4 ac = normalize3( center - posA );\n" +" if( dot3F4( ab, ac ) > 0.95f || (invMassA == 0.f || invMassB == 0.f))\n" +" {\n" +" float angNA = dot3F4( n, *angVelA );\n" +" float angNB = dot3F4( n, *angVelB );\n" +"\n" +" *angVelA -= (angNA*0.1f)*n;\n" +" *angVelB -= (angNB*0.1f)*n;\n" +" }\n" +" }\n" +"}\n" +"\n" +"void solveAConstraint(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* ldsCs)\n" +"{\n" +" float frictionCoeff = ldsCs[0].m_linear.w;\n" +" int aIdx = ldsCs[0].m_bodyA;\n" +" int bIdx = ldsCs[0].m_bodyB;\n" +"\n" +" float4 posA = gBodies[aIdx].m_pos;\n" +" float4 linVelA = gBodies[aIdx].m_linVel;\n" +" float4 angVelA = gBodies[aIdx].m_angVel;\n" +" float invMassA = gBodies[aIdx].m_invMass;\n" +" Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia;\n" +"\n" +" float4 posB = gBodies[bIdx].m_pos;\n" +" float4 linVelB = gBodies[bIdx].m_linVel;\n" +" float4 angVelB = gBodies[bIdx].m_angVel;\n" +" float invMassB = gBodies[bIdx].m_invMass;\n" +" Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia;\n" +" \n" +" \n" +" {\n" +" solveContact( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA,\n" +" posB, &linVelB, &angVelB, invMassB, invInertiaB );\n" +" }\n" +"\n" +" {\n" +" float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX};\n" +" float minRambdaDt[4] = {0.f,0.f,0.f,0.f};\n" +"\n" +" float sum = 0;\n" +" for(int j=0; j<4; j++)\n" +" {\n" +" sum +=ldsCs[0].m_appliedRambdaDt[j];\n" +" }\n" +" frictionCoeff = 0.7f;\n" +" for(int j=0; j<4; j++)\n" +" {\n" +" maxRambdaDt[j] = frictionCoeff*sum;\n" +" minRambdaDt[j] = -maxRambdaDt[j];\n" +" }\n" +"\n" +" solveFriction( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA,\n" +" posB, &linVelB, &angVelB, invMassB, invInertiaB, maxRambdaDt, minRambdaDt );\n" +" }\n" +"\n" +" gBodies[aIdx].m_linVel = linVelA;\n" +" gBodies[aIdx].m_angVel = angVelA;\n" +" gBodies[bIdx].m_linVel = linVelB;\n" +" gBodies[bIdx].m_angVel = angVelB;\n" +"}\n" +"\n" +"void solveContactConstraint(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* ldsCs)\n" +"{\n" +" float frictionCoeff = ldsCs[0].m_linear.w;\n" +" int aIdx = ldsCs[0].m_bodyA;\n" +" int bIdx = ldsCs[0].m_bodyB;\n" +"\n" +" float4 posA = gBodies[aIdx].m_pos;\n" +" float4 linVelA = gBodies[aIdx].m_linVel;\n" +" float4 angVelA = gBodies[aIdx].m_angVel;\n" +" float invMassA = gBodies[aIdx].m_invMass;\n" +" Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia;\n" +"\n" +" float4 posB = gBodies[bIdx].m_pos;\n" +" float4 linVelB = gBodies[bIdx].m_linVel;\n" +" float4 angVelB = gBodies[bIdx].m_angVel;\n" +" float invMassB = gBodies[bIdx].m_invMass;\n" +" Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia;\n" +"\n" +" solveContact( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA,\n" +" posB, &linVelB, &angVelB, invMassB, invInertiaB );\n" +"\n" +" gBodies[aIdx].m_linVel = linVelA;\n" +" gBodies[aIdx].m_angVel = angVelA;\n" +" gBodies[bIdx].m_linVel = linVelB;\n" +" gBodies[bIdx].m_angVel = angVelB;\n" +"}\n" +"\n" +"void solveFrictionConstraint(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* ldsCs)\n" +"{\n" +" float frictionCoeff = ldsCs[0].m_linear.w;\n" +" int aIdx = ldsCs[0].m_bodyA;\n" +" int bIdx = ldsCs[0].m_bodyB;\n" +"\n" +" float4 posA = gBodies[aIdx].m_pos;\n" +" float4 linVelA = gBodies[aIdx].m_linVel;\n" +" float4 angVelA = gBodies[aIdx].m_angVel;\n" +" float invMassA = gBodies[aIdx].m_invMass;\n" +" Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia;\n" +"\n" +" float4 posB = gBodies[bIdx].m_pos;\n" +" float4 linVelB = gBodies[bIdx].m_linVel;\n" +" float4 angVelB = gBodies[bIdx].m_angVel;\n" +" float invMassB = gBodies[bIdx].m_invMass;\n" +" Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia;\n" +"\n" +" {\n" +" float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX};\n" +" float minRambdaDt[4] = {0.f,0.f,0.f,0.f};\n" +"\n" +" float sum = 0;\n" +" for(int j=0; j<4; j++)\n" +" {\n" +" sum +=ldsCs[0].m_appliedRambdaDt[j];\n" +" }\n" +" frictionCoeff = 0.7f;\n" +" for(int j=0; j<4; j++)\n" +" {\n" +" maxRambdaDt[j] = frictionCoeff*sum;\n" +" minRambdaDt[j] = -maxRambdaDt[j];\n" +" }\n" +"\n" +" solveFriction( ldsCs, posA, &linVelA, &angVelA, invMassA, invInertiaA,\n" +" posB, &linVelB, &angVelB, invMassB, invInertiaB, maxRambdaDt, minRambdaDt );\n" +" }\n" +"\n" +" gBodies[aIdx].m_linVel = linVelA;\n" +" gBodies[aIdx].m_angVel = angVelA;\n" +" gBodies[bIdx].m_linVel = linVelB;\n" +" gBodies[bIdx].m_angVel = angVelB;\n" +"}\n" +"\n" +"typedef struct \n" +"{\n" +" int m_valInt0;\n" +" int m_valInt1;\n" +" int m_valInt2;\n" +" int m_valInt3;\n" +"\n" +" float m_val0;\n" +" float m_val1;\n" +" float m_val2;\n" +" float m_val3;\n" +"} SolverDebugInfo;\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"//void BatchSolveKernel(__global Body* gBodies, __global Shape* gShapes, __global Constraint4* gConstraints, __global int* gN, __global int* gOffsets, __global SolverDebugInfo* debugInfo, ConstBufferBatchSolve cb)\n" +"void BatchSolveKernel(__global Body* gBodies, \n" +"__global Shape* gShapes, \n" +"__global Constraint4* gConstraints, \n" +"__global int* gN, \n" +"__global int* gOffsets, \n" +"ConstBufferBatchSolve cb)\n" +"{\n" +" __local int ldsBatchIdx[WG_SIZE+1];\n" +"\n" +" __local int ldsCurBatch;\n" +" __local int ldsNextBatch;\n" +" __local int ldsStart;\n" +"\n" +" int lIdx = GET_LOCAL_IDX;\n" +" int wgIdx = GET_GROUP_IDX;\n" +"\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"// debugInfo[gIdx].m_valInt0 = gIdx;\n" +" //debugInfo[gIdx].m_valInt1 = GET_GROUP_SIZE;\n" +"\n" +" const int solveFriction = cb.m_solveFriction;\n" +" const int maxBatch = cb.m_maxBatch;\n" +" const int bIdx = cb.m_batchIdx;\n" +" const int nSplit = cb.m_nSplit;\n" +"\n" +" int xIdx = (wgIdx/(nSplit/2))*2 + (bIdx&1);\n" +" int yIdx = (wgIdx%(nSplit/2))*2 + (bIdx>>1);\n" +" int cellIdx = xIdx+yIdx*nSplit;\n" +" \n" +" if( gN[cellIdx] == 0 ) \n" +" return;\n" +"\n" +" const int start = gOffsets[cellIdx];\n" +" const int end = start + gN[cellIdx];\n" +"\n" +" \n" +" if( lIdx == 0 )\n" +" {\n" +" ldsCurBatch = 0;\n" +" ldsNextBatch = 0;\n" +" ldsStart = start;\n" +" }\n" +"\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" int idx=ldsStart+lIdx;\n" +" while (ldsCurBatch < maxBatch)\n" +" {\n" +" for(; idxm_bodyA = src.m_bodyAPtr;\n" +" dstC->m_bodyB = src.m_bodyBPtr;\n" +"\n" +" float dtInv = 1.f/dt;\n" +" for(int ic=0; ic<4; ic++)\n" +" {\n" +" dstC->m_appliedRambdaDt[ic] = 0.f;\n" +" }\n" +" dstC->m_fJacCoeffInv[0] = dstC->m_fJacCoeffInv[1] = 0.f;\n" +"\n" +"\n" +" dstC->m_linear = -src.m_worldNormal;\n" +" dstC->m_linear.w = 0.7f ;//src.getFrictionCoeff() );\n" +" for(int ic=0; ic<4; ic++)\n" +" {\n" +" float4 r0 = src.m_worldPos[ic] - posA;\n" +" float4 r1 = src.m_worldPos[ic] - posB;\n" +"\n" +" if( ic >= src.m_worldNormal.w )//npoints\n" +" {\n" +" dstC->m_jacCoeffInv[ic] = 0.f;\n" +" continue;\n" +" }\n" +"\n" +" float relVelN;\n" +" {\n" +" float4 linear, angular0, angular1;\n" +" setLinearAndAngular(src.m_worldNormal, r0, r1, &linear, &angular0, &angular1);\n" +"\n" +" dstC->m_jacCoeffInv[ic] = calcJacCoeff(linear, -linear, angular0, angular1,\n" +" invMassA, &invInertiaA, invMassB, &invInertiaB );\n" +"\n" +" relVelN = calcRelVel(linear, -linear, angular0, angular1,\n" +" linVelA, angVelA, linVelB, angVelB);\n" +"\n" +" float e = 0.f;//src.getRestituitionCoeff();\n" +" if( relVelN*relVelN < 0.004f ) e = 0.f;\n" +"\n" +" dstC->m_b[ic] = e*relVelN;\n" +" //float penetration = src.m_worldPos[ic].w;\n" +" dstC->m_b[ic] += (src.m_worldPos[ic].w + positionDrift)*positionConstraintCoeff*dtInv;\n" +" dstC->m_appliedRambdaDt[ic] = 0.f;\n" +" }\n" +" }\n" +"\n" +" if( src.m_worldNormal.w > 1 )//npoints\n" +" { // prepare friction\n" +" float4 center = make_float4(0.f);\n" +" for(int i=0; im_fJacCoeffInv[i] = calcJacCoeff(linear, -linear, angular0, angular1,\n" +" invMassA, &invInertiaA, invMassB, &invInertiaB );\n" +" dstC->m_fAppliedRambdaDt[i] = 0.f;\n" +" }\n" +" dstC->m_center = center;\n" +" }\n" +" else\n" +" {\n" +" // single point constraint\n" +" }\n" +"\n" +" for(int i=0; i<4; i++)\n" +" {\n" +" if( im_worldPos[i] = src.m_worldPos[i];\n" +" }\n" +" else\n" +" {\n" +" dstC->m_worldPos[i] = make_float4(0.f);\n" +" }\n" +" }\n" +"}\n" +"\n" +"typedef struct\n" +"{\n" +" int m_nContacts;\n" +" float m_dt;\n" +" float m_positionDrift;\n" +" float m_positionConstraintCoeff;\n" +"} ConstBufferCTC;\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void ContactToConstraintKernel(__global Contact4* gContact, __global Body* gBodies, __global Shape* gShapes, __global Constraint4* gConstraintOut, ConstBufferCTC cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" int nContacts = cb.m_nContacts;\n" +" float dt = cb.m_dt;\n" +" float positionDrift = cb.m_positionDrift;\n" +" float positionConstraintCoeff = cb.m_positionConstraintCoeff;\n" +"\n" +" if( gIdx < nContacts )\n" +" {\n" +" int aIdx = gContact[gIdx].m_bodyAPtr;\n" +" int bIdx = gContact[gIdx].m_bodyBPtr;\n" +"\n" +" float4 posA = gBodies[aIdx].m_pos;\n" +" float4 linVelA = gBodies[aIdx].m_linVel;\n" +" float4 angVelA = gBodies[aIdx].m_angVel;\n" +" float invMassA = gBodies[aIdx].m_invMass;\n" +" Matrix3x3 invInertiaA = gShapes[aIdx].m_invInertia;\n" +"\n" +" float4 posB = gBodies[bIdx].m_pos;\n" +" float4 linVelB = gBodies[bIdx].m_linVel;\n" +" float4 angVelB = gBodies[bIdx].m_angVel;\n" +" float invMassB = gBodies[bIdx].m_invMass;\n" +" Matrix3x3 invInertiaB = gShapes[bIdx].m_invInertia;\n" +"\n" +" Constraint4 cs;\n" +"\n" +" setConstraint4( posA, linVelA, angVelA, invMassA, invInertiaA, posB, linVelB, angVelB, invMassB, invInertiaB,\n" +" gContact[gIdx], dt, positionDrift, positionConstraintCoeff, \n" +" &cs );\n" +" \n" +" cs.m_batchIdx = gContact[gIdx].m_batchIdx;\n" +"\n" +" gConstraintOut[gIdx] = cs;\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void CopyConstraintKernel(__global Contact4* gIn, __global Contact4* gOut, int4 cb )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" if( gIdx < cb.x )\n" +" {\n" +" gOut[gIdx] = gIn[gIdx];\n" +" }\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/batchingKernels.cl b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/batchingKernels.cl new file mode 100644 index 000000000..eee80c1a3 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/batchingKernels.cl @@ -0,0 +1,338 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_extended_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable + +#ifdef cl_ext_atomic_counters_32 +#pragma OPENCL EXTENSION cl_ext_atomic_counters_32 : enable +#else +#define counter32_t volatile __global int* +#endif + + +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GET_NUM_GROUPS get_num_groups(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) +#define AppendInc(x, out) out = atomic_inc(x) +#define AtomAdd(x, value) atom_add(&(x), value) +#define AtomCmpxhg(x, cmp, value) atom_cmpxchg( &(x), cmp, value ) +#define AtomXhg(x, value) atom_xchg ( &(x), value ) + + +#define SELECT_UINT4( b, a, condition ) select( b,a,condition ) + +#define make_float4 (float4) +#define make_float2 (float2) +#define make_uint4 (uint4) +#define make_int4 (int4) +#define make_uint2 (uint2) +#define make_int2 (int2) + + +#define max2 max +#define min2 min + + +#define WG_SIZE 64 + + + +typedef struct +{ + float4 m_worldPos[4]; + float4 m_worldNormal; + u32 m_coeffs; + int m_batchIdx; + + u32 m_bodyA; + u32 m_bodyB; +}Contact4; + +typedef struct +{ + int m_n; + int m_start; + int m_staticIdx; + int m_paddings[1]; +} ConstBuffer; + +typedef struct +{ + u32 m_a; + u32 m_b; + u32 m_idx; +}Elem; + +#define STACK_SIZE (WG_SIZE*10) +//#define STACK_SIZE (WG_SIZE) +#define RING_SIZE 1024 +#define RING_SIZE_MASK (RING_SIZE-1) +#define CHECK_SIZE (WG_SIZE) + + +#define GET_RING_CAPACITY (RING_SIZE - ldsRingEnd) +#define RING_END ldsTmp + +u32 readBuf(__local u32* buff, int idx) +{ + idx = idx % (32*CHECK_SIZE); + int bitIdx = idx%32; + int bufIdx = idx/32; + return buff[bufIdx] & (1<> bitIdx)&1) == 0; +} + +// batching on the GPU +__kernel void CreateBatches( __global Contact4* gConstraints, __global Contact4* gConstraintsOut, + __global u32* gN, __global u32* gStart, + ConstBuffer cb ) +{ + __local u32 ldsStackIdx[STACK_SIZE]; + __local u32 ldsStackEnd; + __local Elem ldsRingElem[RING_SIZE]; + __local u32 ldsRingEnd; + __local u32 ldsTmp; + __local u32 ldsCheckBuffer[CHECK_SIZE]; + __local u32 ldsFixedBuffer[CHECK_SIZE]; + __local u32 ldsGEnd; + __local u32 ldsDstEnd; + + int wgIdx = GET_GROUP_IDX; + int lIdx = GET_LOCAL_IDX; + + const int m_n = gN[wgIdx]; + const int m_start = gStart[wgIdx]; + const int m_staticIdx = cb.m_staticIdx; + + if( lIdx == 0 ) + { + ldsRingEnd = 0; + ldsGEnd = 0; + ldsStackEnd = 0; + ldsDstEnd = m_start; + } + +// while(1) + for(int ie=0; ie<250; ie++) + { + ldsFixedBuffer[lIdx] = 0; + + for(int giter=0; giter<4; giter++) + { + int ringCap = GET_RING_CAPACITY; + + // 1. fill ring + if( ldsGEnd < m_n ) + { + while( ringCap > WG_SIZE ) + { + if( ldsGEnd >= m_n ) break; + if( lIdx < ringCap - WG_SIZE ) + { + int srcIdx; + AtomInc1( ldsGEnd, srcIdx ); + if( srcIdx < m_n ) + { + int dstIdx; + AtomInc1( ldsRingEnd, dstIdx ); + + int a = gConstraints[m_start+srcIdx].m_bodyA; + int b = gConstraints[m_start+srcIdx].m_bodyB; + ldsRingElem[dstIdx].m_a = (a>b)? b:a; + ldsRingElem[dstIdx].m_b = (a>b)? a:b; + ldsRingElem[dstIdx].m_idx = srcIdx; + } + } + ringCap = GET_RING_CAPACITY; + } + } + + GROUP_LDS_BARRIER; + + // 2. fill stack + __local Elem* dst = ldsRingElem; + if( lIdx == 0 ) RING_END = 0; + + int srcIdx=lIdx; + int end = ldsRingEnd; + + { + for(int ii=0; ii> bitIdx)&1) == 0;\n" +"}\n" +"\n" +"typedef struct \n" +"{\n" +" int m_valInt0;\n" +" int m_valInt1;\n" +" int m_valInt2;\n" +" int m_valInt3;\n" +"\n" +" int m_valInt4;\n" +" int m_valInt5;\n" +" int m_valInt6;\n" +" int m_valInt7;\n" +"\n" +" int m_valInt8;\n" +" int m_valInt9;\n" +" int m_valInt10;\n" +" int m_valInt11;\n" +" \n" +" int m_valInt12;\n" +" int m_valInt13;\n" +" int m_valInt14;\n" +" int m_valInt15;\n" +"\n" +"\n" +" float m_fval0;\n" +" float m_fval1;\n" +" float m_fval2;\n" +" float m_fval3;\n" +"} SolverDebugInfo;\n" +"\n" +"// batching on the GPU\n" +"__kernel void CreateBatches( __global Contact4* gConstraints, __global Contact4* gConstraintsOut, //__global u32* gRes, \n" +" __global u32* gN, __global u32* gStart, \n" +"// __global SolverDebugInfo* debugInfo, \n" +" ConstBuffer cb )\n" +"{\n" +" __local u32 ldsStackIdx[STACK_SIZE];\n" +" __local u32 ldsStackEnd;\n" +" __local Elem ldsRingElem[RING_SIZE];\n" +" __local u32 ldsRingEnd;\n" +" __local u32 ldsTmp;\n" +" __local u32 ldsCheckBuffer[CHECK_SIZE];\n" +" __local u32 ldsFixedBuffer[CHECK_SIZE];\n" +" __local u32 ldsGEnd;\n" +" __local u32 ldsDstEnd;\n" +"\n" +" int wgIdx = GET_GROUP_IDX;\n" +" int lIdx = GET_LOCAL_IDX;\n" +" \n" +" const int m_n = gN[wgIdx];\n" +" const int m_start = gStart[wgIdx];\n" +" const int m_staticIdx = cb.m_staticIdx;\n" +" \n" +" if( lIdx == 0 )\n" +" {\n" +" ldsRingEnd = 0;\n" +" ldsGEnd = 0;\n" +" ldsStackEnd = 0;\n" +" ldsDstEnd = m_start;\n" +" }\n" +" \n" +"// while(1)\n" +" for(int ie=0; ie<250; ie++)\n" +" {\n" +" ldsFixedBuffer[lIdx] = 0;\n" +"\n" +" for(int giter=0; giter<4; giter++)\n" +" {\n" +" int ringCap = GET_RING_CAPACITY;\n" +" \n" +" // 1. fill ring\n" +" if( ldsGEnd < m_n )\n" +" {\n" +" while( ringCap > WG_SIZE )\n" +" {\n" +" if( ldsGEnd >= m_n ) break;\n" +" if( lIdx < ringCap - WG_SIZE )\n" +" {\n" +" int srcIdx;\n" +" AtomInc1( ldsGEnd, srcIdx );\n" +" if( srcIdx < m_n )\n" +" {\n" +" int dstIdx;\n" +" AtomInc1( ldsRingEnd, dstIdx );\n" +" \n" +" int a = gConstraints[m_start+srcIdx].m_bodyA;\n" +" int b = gConstraints[m_start+srcIdx].m_bodyB;\n" +" ldsRingElem[dstIdx].m_a = (a>b)? b:a;\n" +" ldsRingElem[dstIdx].m_b = (a>b)? a:b;\n" +" ldsRingElem[dstIdx].m_idx = srcIdx;\n" +" }\n" +" }\n" +" ringCap = GET_RING_CAPACITY;\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" // 2. fill stack\n" +" __local Elem* dst = ldsRingElem;\n" +" if( lIdx == 0 ) RING_END = 0;\n" +"\n" +" int srcIdx=lIdx;\n" +" int end = ldsRingEnd;\n" +"\n" +" {\n" +" for(int ii=0; iiChNarrowphaseKernels.h + + +@echo Warning: +@echo You might still need to find/replace for \\n (due to macros) and replace #include statements by their content +pause diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsAll.bat b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsAll.bat new file mode 100644 index 000000000..9854d9352 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsAll.bat @@ -0,0 +1,10 @@ +stringify.py ChNarrowphaseKernels.cl narrowphaseKernelsCL >ChNarrowphaseKernels.h +stringify.py SolverKernels.cl solverKernelsCL >SolverKernels.h +stringify.py batchingKernels.cl batchingKernelsCL >batchingKernels.h + + + + +@echo Warning: +@echo You might still need to find/replace for \\n (due to macros) and replace #include statements by their content +pause diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsBatching.bat b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsBatching.bat new file mode 100644 index 000000000..1282f7e28 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsBatching.bat @@ -0,0 +1,8 @@ +stringify.py batchingKernels.cl batchingKernelsCL >batchingKernels.h + + + + +@echo Warning: +@echo You might still need to find/replace for \\n (due to macros) and replace #include statements by their content +pause diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsNarrowphase.bat b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsNarrowphase.bat new file mode 100644 index 000000000..20a0d3ea8 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsNarrowphase.bat @@ -0,0 +1,8 @@ +stringify.py ChNarrowphaseKernels.cl narrowphaseKernelsCL >ChNarrowphaseKernels.h + + + + +@echo Warning: +@echo You might still need to find/replace for \\n (due to macros) and replace #include statements by their content +pause diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsSolver.bat b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsSolver.bat new file mode 100644 index 000000000..ff483deb9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/Stubs/stringifykernelsSolver.bat @@ -0,0 +1,8 @@ +stringify.py SolverKernels.cl solverKernelsCL >SolverKernels.h + + + + +@echo Warning: +@echo You might still need to find/replace for \\n (due to macros) and replace #include statements by their content +pause diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/main.cpp b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/main.cpp new file mode 100644 index 000000000..26846f90c --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/main.cpp @@ -0,0 +1,77 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "BasicDemo.h" +#include "GlutStuff.h" +#include "btBulletDynamicsCommon.h" +#include "LinearMath/btHashMap.h" + +#ifdef CL_PLATFORM_AMD +#include "../../opencl/basic_initialize/btOpenCLUtils.h" +extern cl_context g_cxMainContext; +extern cl_command_queue g_cqCommandQue; +extern cl_device_id g_clDevice; +#endif + + + +int main(int argc,char** argv) +{ + + #ifdef CL_PLATFORM_AMD + int ciErrNum = 0; + const char* vendorSDK = btOpenCLUtils::getSdkVendorName(); + printf("This program was compiled using the %s OpenCL SDK\n",vendorSDK); + + cl_device_type deviceType = CL_DEVICE_TYPE_GPU;//CPU;//GPU; + + + void* glCtx=0; + void* glDC = 0; + g_cxMainContext = btOpenCLUtils::createContextFromType(deviceType, &ciErrNum, glCtx, glDC); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + int numDev = btOpenCLUtils::getNumDevices(g_cxMainContext); + + if (numDev>0) + { + int deviceIndex =0; + g_clDevice = btOpenCLUtils::getDevice(g_cxMainContext,deviceIndex); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(g_clDevice,clInfo); + btOpenCLUtils::printDeviceInfo(g_clDevice); + // create a command-queue + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, g_clDevice, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } +#endif //#ifdef CL_PLATFORM_AMD + + + BasicDemo ccdDemo; + ccdDemo.initPhysics(); + + +#ifdef CHECK_MEMORY_LEAKS + ccdDemo.exitPhysics(); +#else + glutmain(argc, argv,1024,600,"Bullet Physics Demo. http://bulletphysics.org",&ccdDemo); +#endif + + //setupGUI(1024,768); + glutMainLoop(); + //default glut doesn't return from mainloop + return 0; +} + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/premake4.lua b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/premake4.lua new file mode 100644 index 000000000..c779ff987 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/basic_demo/premake4.lua @@ -0,0 +1,34 @@ + +-- include "AMD" + +if os.is("Windows") then + + project "basic_bullet2_demo" + + language "C++" + + kind "ConsoleApp" + targetdir "../../bin" + + includedirs { + ".", + "../../bullet2", + "../testbed", + "../../rendering/Gwen", + } + + + links { "testbed", + "bullet2", + "gwen" + } + + initOpenGL() + initGlut() + + files { + "**.cpp", + "**.h" + } + +end diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/DebugCastResult.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/DebugCastResult.h new file mode 100644 index 000000000..ef3befe44 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/DebugCastResult.h @@ -0,0 +1,88 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef DEBUG_CAST_RESULT_H +#define DEBUG_CAST_RESULT_H + +#include "BulletCollision/NarrowPhaseCollision/btConvexCast.h" +#include "LinearMath/btTransform.h" +#include "GL_ShapeDrawer.h" +#include "GlutStuff.h" +#ifdef WIN32 +#include +#endif +//think different +#if defined(__APPLE__) && !defined (VMDMESA) +#include +#include +#else +#include +#endif +struct btDebugCastResult : public btConvexCast::CastResult +{ + + btTransform m_fromTrans; + const btPolyhedralConvexShape* m_shape; + btVector3 m_linVel; + btVector3 m_angVel; + GL_ShapeDrawer* m_shapeDrawer; + + btDebugCastResult(const btTransform& fromTrans,const btPolyhedralConvexShape* shape, + const btVector3& linVel,const btVector3& angVel,GL_ShapeDrawer* drawer) + :m_fromTrans(fromTrans), + m_shape(shape), + m_linVel(linVel), + m_angVel(angVel), + m_shapeDrawer(drawer) + { + } + + virtual void drawCoordSystem(const btTransform& tr) + { + btScalar m[16]; + tr.getOpenGLMatrix(m); + glPushMatrix(); + btglLoadMatrix(m); + glBegin(GL_LINES); + btglColor3(1, 0, 0); + btglVertex3(0, 0, 0); + btglVertex3(1, 0, 0); + btglColor3(0, 1, 0); + btglVertex3(0, 0, 0); + btglVertex3(0, 1, 0); + btglColor3(0, 0, 1); + btglVertex3(0, 0, 0); + btglVertex3(0, 0, 1); + glEnd(); + glPopMatrix(); + } + + virtual void DebugDraw(btScalar fraction) + { + btVector3 worldBoundsMin(-1000,-1000,-1000); + btVector3 worldBoundsMax(1000,1000,1000); + + + btScalar m[16]; + btTransform hitTrans; + btTransformUtil::integrateTransform(m_fromTrans,m_linVel,m_angVel,fraction,hitTrans); + hitTrans.getOpenGLMatrix(m); + if (m_shapeDrawer) + m_shapeDrawer->drawOpenGL(m,m_shape,btVector3(1,0,0),btIDebugDraw::DBG_NoDebug,worldBoundsMin,worldBoundsMax); + } +}; + + +#endif //DEBUG_CAST_RESULT_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.cpp new file mode 100644 index 000000000..d582570e6 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.cpp @@ -0,0 +1,1375 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#include "DemoApplication.h" +#include "LinearMath/btIDebugDraw.h" +#include "BulletDynamics/Dynamics/btDynamicsWorld.h" + +#include "BulletDynamics/ConstraintSolver/btPoint2PointConstraint.h"//picking +#include "BulletDynamics/ConstraintSolver/btGeneric6DofConstraint.h"//picking + +#include "BulletCollision/CollisionShapes/btCollisionShape.h" +#include "BulletCollision/CollisionShapes/btBoxShape.h" +#include "BulletCollision/CollisionShapes/btSphereShape.h" +#include "BulletCollision/CollisionShapes/btCompoundShape.h" +#include "BulletCollision/CollisionShapes/btUniformScalingShape.h" +#include "BulletDynamics/ConstraintSolver/btConstraintSolver.h" +#include "GL_ShapeDrawer.h" +#include "LinearMath/btQuickprof.h" +#include "LinearMath/btDefaultMotionState.h" +#include "LinearMath/btSerializer.h" +#include "GLDebugFont.h" + +static bool use6Dof = false; +extern bool gDisableDeactivation; +int numObjects = 0; +const int maxNumObjects = 16384; +btTransform startTransforms[maxNumObjects]; +btCollisionShape* gShapePtr[maxNumObjects];//1 rigidbody has 1 shape (no re-use of shapes) +#define SHOW_NUM_DEEP_PENETRATIONS 1 + +extern int gNumClampedCcdMotions; + +#ifdef SHOW_NUM_DEEP_PENETRATIONS +extern int gNumDeepPenetrationChecks; + +extern int gNumSplitImpulseRecoveries; +extern int gNumGjkChecks; +extern int gNumAlignedAllocs; +extern int gNumAlignedFree; +extern int gTotalBytesAlignedAllocs; + +#endif // + + +DemoApplication::DemoApplication() +//see btIDebugDraw.h for modes +: +m_dynamicsWorld(0), +m_pickConstraint(0), +m_shootBoxShape(0), +m_cameraDistance(15.0), +m_debugMode(0), +m_ele(20.f), +m_azi(0.f), +m_cameraPosition(0.f,0.f,0.f), +m_cameraTargetPosition(0.f,0.f,0.f), +m_mouseOldX(0), +m_mouseOldY(0), +m_mouseButtons(0), +m_modifierKeys(0), +m_scaleBottom(0.5f), +m_scaleFactor(2.f), +m_cameraUp(0,1,0), +m_forwardAxis(2), +m_glutScreenWidth(0), +m_glutScreenHeight(0), +m_frustumZNear(1.f), +m_frustumZFar(10000.f), +m_ortho(0), +m_ShootBoxInitialSpeed(40.f), +m_stepping(true), +m_singleStep(false), +m_idle(false), + +m_enableshadows(false), +m_sundirection(btVector3(1,-2,1)*1000), +m_defaultContactProcessingThreshold(BT_LARGE_FLOAT) +{ +#ifndef BT_NO_PROFILE + m_profileIterator = CProfileManager::Get_Iterator(); +#endif //BT_NO_PROFILE + + m_shapeDrawer = new GL_ShapeDrawer (); + m_shapeDrawer->enableTexture(true); + m_enableshadows = false; +} + + + +DemoApplication::~DemoApplication() +{ +#ifndef BT_NO_PROFILE + CProfileManager::Release_Iterator(m_profileIterator); +#endif //BT_NO_PROFILE + + if (m_shootBoxShape) + delete m_shootBoxShape; + + if (m_shapeDrawer) + delete m_shapeDrawer; +} + + +void DemoApplication::overrideGLShapeDrawer (GL_ShapeDrawer* shapeDrawer) +{ + shapeDrawer->enableTexture (m_shapeDrawer->hasTextureEnabled()); + delete m_shapeDrawer; + m_shapeDrawer = shapeDrawer; +} + +void DemoApplication::myinit(void) +{ + + GLfloat light_ambient[] = { btScalar(0.2), btScalar(0.2), btScalar(0.2), btScalar(1.0) }; + GLfloat light_diffuse[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0) }; + GLfloat light_specular[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0 )}; + /* light_position is NOT default value */ + GLfloat light_position0[] = { btScalar(1.0), btScalar(10.0), btScalar(1.0), btScalar(0.0 )}; + GLfloat light_position1[] = { btScalar(-1.0), btScalar(-10.0), btScalar(-1.0), btScalar(0.0) }; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + + glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + + glShadeModel(GL_SMOOTH); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glClearColor(btScalar(0.7),btScalar(0.7),btScalar(0.7),btScalar(0)); + + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); +} + + +void DemoApplication::setCameraDistance(float dist) +{ + m_cameraDistance = dist; +} + +float DemoApplication::getCameraDistance() +{ + return m_cameraDistance; +} + + + +void DemoApplication::toggleIdle() { + if (m_idle) { + m_idle = false; + } + else { + m_idle = true; + } +} + + + + +void DemoApplication::updateCamera() { + + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + btScalar rele = m_ele * btScalar(0.01745329251994329547);// rads per deg + btScalar razi = m_azi * btScalar(0.01745329251994329547);// rads per deg + + + btQuaternion rot(m_cameraUp,razi); + + + btVector3 eyePos(0,0,0); + eyePos[m_forwardAxis] = -m_cameraDistance; + + btVector3 forward(eyePos[0],eyePos[1],eyePos[2]); + if (forward.length2() < SIMD_EPSILON) + { + forward.setValue(1.f,0.f,0.f); + } + btVector3 right = m_cameraUp.cross(forward); + btQuaternion roll(right,-rele); + + eyePos = btMatrix3x3(rot) * btMatrix3x3(roll) * eyePos; + + m_cameraPosition[0] = eyePos.getX(); + m_cameraPosition[1] = eyePos.getY(); + m_cameraPosition[2] = eyePos.getZ(); + m_cameraPosition += m_cameraTargetPosition; + + if (m_glutScreenWidth == 0 && m_glutScreenHeight == 0) + return; + + btScalar aspect; + btVector3 extents; + + aspect = m_glutScreenWidth / (btScalar)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + + + if (m_ortho) + { + // reset matrix + glLoadIdentity(); + + + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + //gluOrtho2D(lower.x, upper.x, lower.y, upper.y); + glOrtho(lower.getX(), upper.getX(), lower.getY(), upper.getY(),-1000,1000); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + //glTranslatef(100,210,0); + } else + { +// glFrustum (-aspect, aspect, -1.0, 1.0, 1.0, 10000.0); + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(m_cameraPosition[0], m_cameraPosition[1], m_cameraPosition[2], + m_cameraTargetPosition[0], m_cameraTargetPosition[1], m_cameraTargetPosition[2], + m_cameraUp.getX(),m_cameraUp.getY(),m_cameraUp.getZ()); + } + +} + + + +const float STEPSIZE = 5; + +void DemoApplication::stepLeft() +{ + m_azi -= STEPSIZE; if (m_azi < 0) m_azi += 360; updateCamera(); +} +void DemoApplication::stepRight() +{ + m_azi += STEPSIZE; if (m_azi >= 360) m_azi -= 360; updateCamera(); +} +void DemoApplication::stepFront() +{ + m_ele += STEPSIZE; if (m_ele >= 360) m_ele -= 360; updateCamera(); +} +void DemoApplication::stepBack() +{ + m_ele -= STEPSIZE; if (m_ele < 0) m_ele += 360; updateCamera(); +} +void DemoApplication::zoomIn() +{ + m_cameraDistance -= btScalar(0.4); updateCamera(); + if (m_cameraDistance < btScalar(0.1)) + m_cameraDistance = btScalar(0.1); + +} +void DemoApplication::zoomOut() +{ + m_cameraDistance += btScalar(0.4); updateCamera(); + +} + + + + + + + + + + +void DemoApplication::reshape(int w, int h) +{ + GLDebugResetFont(w,h); + + m_glutScreenWidth = w; + m_glutScreenHeight = h; + + glViewport(0, 0, w, h); + updateCamera(); +} + + +void DemoApplication::keyboardCallback(unsigned char key, int x, int y) +{ + (void)x; + (void)y; + + m_lastKey = 0; + +#ifndef BT_NO_PROFILE + if (key >= 0x31 && key <= 0x39) + { + int child = key-0x31; + m_profileIterator->Enter_Child(child); + } + if (key==0x30) + { + m_profileIterator->Enter_Parent(); + } +#endif //BT_NO_PROFILE + + switch (key) + { + case 'q' : +#ifdef BT_USE_FREEGLUT + //return from glutMainLoop(), detect memory leaks etc. + glutLeaveMainLoop(); +#else + exit(0); +#endif + break; + + case 'l' : stepLeft(); break; + case 'r' : stepRight(); break; + case 'f' : stepFront(); break; + case 'b' : stepBack(); break; + case 'z' : zoomIn(); break; + case 'x' : zoomOut(); break; + case 'i' : toggleIdle(); break; + case 'g' : m_enableshadows=!m_enableshadows;break; + case 'u' : m_shapeDrawer->enableTexture(!m_shapeDrawer->enableTexture(false));break; + case 'h': + if (m_debugMode & btIDebugDraw::DBG_NoHelpText) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_NoHelpText); + else + m_debugMode |= btIDebugDraw::DBG_NoHelpText; + break; + + case 'w': + if (m_debugMode & btIDebugDraw::DBG_DrawWireframe) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawWireframe); + else + m_debugMode |= btIDebugDraw::DBG_DrawWireframe; + break; + + case 'p': + if (m_debugMode & btIDebugDraw::DBG_ProfileTimings) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_ProfileTimings); + else + m_debugMode |= btIDebugDraw::DBG_ProfileTimings; + break; + + case '=': + { + int maxSerializeBufferSize = 1024*1024*5; + btDefaultSerializer* serializer = new btDefaultSerializer(maxSerializeBufferSize); + //serializer->setSerializationFlags(BT_SERIALIZE_NO_DUPLICATE_ASSERT); + m_dynamicsWorld->serialize(serializer); + FILE* f2 = fopen("testFile.bullet","wb"); + fwrite(serializer->getBufferPointer(),serializer->getCurrentBufferSize(),1,f2); + fclose(f2); + delete serializer; + break; + + } + + case 'm': + if (m_debugMode & btIDebugDraw::DBG_EnableSatComparison) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_EnableSatComparison); + else + m_debugMode |= btIDebugDraw::DBG_EnableSatComparison; + break; + + case 'n': + if (m_debugMode & btIDebugDraw::DBG_DisableBulletLCP) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DisableBulletLCP); + else + m_debugMode |= btIDebugDraw::DBG_DisableBulletLCP; + break; + + case 't' : + if (m_debugMode & btIDebugDraw::DBG_DrawText) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawText); + else + m_debugMode |= btIDebugDraw::DBG_DrawText; + break; + case 'y': + if (m_debugMode & btIDebugDraw::DBG_DrawFeaturesText) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawFeaturesText); + else + m_debugMode |= btIDebugDraw::DBG_DrawFeaturesText; + break; + case 'a': + if (m_debugMode & btIDebugDraw::DBG_DrawAabb) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawAabb); + else + m_debugMode |= btIDebugDraw::DBG_DrawAabb; + break; + case 'c' : + if (m_debugMode & btIDebugDraw::DBG_DrawContactPoints) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawContactPoints); + else + m_debugMode |= btIDebugDraw::DBG_DrawContactPoints; + break; + case 'C' : + if (m_debugMode & btIDebugDraw::DBG_DrawConstraints) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawConstraints); + else + m_debugMode |= btIDebugDraw::DBG_DrawConstraints; + break; + case 'L' : + if (m_debugMode & btIDebugDraw::DBG_DrawConstraintLimits) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_DrawConstraintLimits); + else + m_debugMode |= btIDebugDraw::DBG_DrawConstraintLimits; + break; + + case 'd' : + if (m_debugMode & btIDebugDraw::DBG_NoDeactivation) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_NoDeactivation); + else + m_debugMode |= btIDebugDraw::DBG_NoDeactivation; + if (m_debugMode & btIDebugDraw::DBG_NoDeactivation) + { + gDisableDeactivation = true; + } else + { + gDisableDeactivation = false; + } + break; + + + + + case 'o' : + { + m_ortho = !m_ortho;//m_stepping = !m_stepping; + break; + } + case 's' : clientMoveAndDisplay(); break; + // case ' ' : newRandom(); break; + case ' ': + clientResetScene(); + break; + case '1': + { + if (m_debugMode & btIDebugDraw::DBG_EnableCCD) + m_debugMode = m_debugMode & (~btIDebugDraw::DBG_EnableCCD); + else + m_debugMode |= btIDebugDraw::DBG_EnableCCD; + break; + } + + case '.': + { + shootBox(getRayTo(x,y));//getCameraTargetPosition()); + break; + } + + case '+': + { + m_ShootBoxInitialSpeed += 10.f; + break; + } + case '-': + { + m_ShootBoxInitialSpeed -= 10.f; + break; + } + + default: + // std::cout << "unused key : " << key << std::endl; + break; + } + + if (getDynamicsWorld() && getDynamicsWorld()->getDebugDrawer()) + getDynamicsWorld()->getDebugDrawer()->setDebugMode(m_debugMode); + + + +} + +void DemoApplication::setDebugMode(int mode) +{ + m_debugMode = mode; + if (getDynamicsWorld() && getDynamicsWorld()->getDebugDrawer()) + getDynamicsWorld()->getDebugDrawer()->setDebugMode(mode); +} + + + + + + +void DemoApplication::moveAndDisplay() +{ + if (!m_idle) + clientMoveAndDisplay(); + else + displayCallback(); +} + + + + +void DemoApplication::displayCallback() +{ +} + +#define NUM_SPHERES_ON_DIAGONAL 9 + +void DemoApplication::setShootBoxShape () +{ + if (!m_shootBoxShape) + { + btBoxShape* box = new btBoxShape(btVector3(.5f,.5f,.5f)); + box->initializePolyhedralFeatures(); + m_shootBoxShape = box; + } +} + +void DemoApplication::shootBox(const btVector3& destination) +{ + + if (m_dynamicsWorld) + { + float mass = 1.f; + btTransform startTransform; + startTransform.setIdentity(); + btVector3 camPos = getCameraPosition(); + startTransform.setOrigin(camPos); + + setShootBoxShape (); + + btRigidBody* body = this->localCreateRigidBody(mass, startTransform,m_shootBoxShape); + body->setLinearFactor(btVector3(1,1,1)); + //body->setRestitution(1); + + btVector3 linVel(destination[0]-camPos[0],destination[1]-camPos[1],destination[2]-camPos[2]); + linVel.normalize(); + linVel*=m_ShootBoxInitialSpeed; + + body->getWorldTransform().setOrigin(camPos); + body->getWorldTransform().setRotation(btQuaternion(0,0,0,1)); + body->setLinearVelocity(linVel); + body->setAngularVelocity(btVector3(0,0,0)); + body->setCcdMotionThreshold(0.5); + body->setCcdSweptSphereRadius(0.9f); +// printf("shootBox uid=%d\n", body->getBroadphaseHandle()->getUid()); +// printf("camPos=%f,%f,%f\n",camPos.getX(),camPos.getY(),camPos.getZ()); +// printf("destination=%f,%f,%f\n",destination.getX(),destination.getY(),destination.getZ()); + + } +} + + +int gPickingConstraintId = 0; +btVector3 gOldPickingPos; +btVector3 gHitPos(-1,-1,-1); +float gOldPickingDist = 0.f; +btRigidBody* pickedBody = 0;//for deactivation state + + +btVector3 DemoApplication::getRayTo(int x,int y) +{ + + + + if (m_ortho) + { + + btScalar aspect; + btVector3 extents; + aspect = m_glutScreenWidth / (btScalar)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + + btScalar u = x / btScalar(m_glutScreenWidth); + btScalar v = (m_glutScreenHeight - y) / btScalar(m_glutScreenHeight); + + btVector3 p(0,0,0); + p.setValue((1.0f - u) * lower.getX() + u * upper.getX(),(1.0f - v) * lower.getY() + v * upper.getY(),m_cameraTargetPosition.getZ()); + return p; + } + + float top = 1.f; + float bottom = -1.f; + float nearPlane = 1.f; + float tanFov = (top-bottom)*0.5f / nearPlane; + float fov = btScalar(2.0) * btAtan(tanFov); + + btVector3 rayFrom = getCameraPosition(); + btVector3 rayForward = (getCameraTargetPosition()-getCameraPosition()); + rayForward.normalize(); + float farPlane = 10000.f; + rayForward*= farPlane; + + btVector3 rightOffset; + btVector3 vertical = m_cameraUp; + + btVector3 hor; + hor = rayForward.cross(vertical); + hor.normalize(); + vertical = hor.cross(rayForward); + vertical.normalize(); + + float tanfov = tanf(0.5f*fov); + + + hor *= 2.f * farPlane * tanfov; + vertical *= 2.f * farPlane * tanfov; + + btScalar aspect; + + aspect = m_glutScreenWidth / (btScalar)m_glutScreenHeight; + + hor*=aspect; + + + btVector3 rayToCenter = rayFrom + rayForward; + btVector3 dHor = hor * 1.f/float(m_glutScreenWidth); + btVector3 dVert = vertical * 1.f/float(m_glutScreenHeight); + + + btVector3 rayTo = rayToCenter - 0.5f * hor + 0.5f * vertical; + rayTo += btScalar(x) * dHor; + rayTo -= btScalar(y) * dVert; + return rayTo; +} + +btScalar mousePickClamping = 30.f; + + +void DemoApplication::mouseFunc(int button, int state, int x, int y) +{ + if (state == 0) + { + m_mouseButtons |= 1<rayTest(m_cameraPosition,rayTo,rayCallback); + if (rayCallback.hasHit()) + { + + btRigidBody* body = btRigidBody::upcast(rayCallback.m_collisionObject); + if (body) + { + body->setActivationState(ACTIVE_TAG); + btVector3 impulse = rayTo; + impulse.normalize(); + float impulseStrength = 10.f; + impulse *= impulseStrength; + btVector3 relPos = rayCallback.m_hitPointWorld - body->getCenterOfMassPosition(); + body->applyImpulse(impulse,relPos); + } + } + } +#endif + + + + } else + { + + } + break; + } + case 0: + { + if (state==0) + { + + + //add a point to point constraint for picking + if (m_dynamicsWorld) + { + + btVector3 rayFrom; + if (m_ortho) + { + rayFrom = rayTo; + rayFrom.setZ(-100.f); + } else + { + rayFrom = m_cameraPosition; + } + + btCollisionWorld::ClosestRayResultCallback rayCallback(rayFrom,rayTo); + m_dynamicsWorld->rayTest(rayFrom,rayTo,rayCallback); + if (rayCallback.hasHit()) + { + + + btRigidBody* body = btRigidBody::upcast(rayCallback.m_collisionObject); + if (body) + { + //other exclusions? + if (!(body->isStaticObject() || body->isKinematicObject())) + { + pickedBody = body; + pickedBody->setActivationState(DISABLE_DEACTIVATION); + + + btVector3 pickPos = rayCallback.m_hitPointWorld; + //printf("pickPos=%f,%f,%f\n",pickPos.getX(),pickPos.getY(),pickPos.getZ()); + + + btVector3 localPivot = body->getCenterOfMassTransform().inverse() * pickPos; + + + + + + + if (use6Dof) + { + btTransform tr; + tr.setIdentity(); + tr.setOrigin(localPivot); + btGeneric6DofConstraint* dof6 = new btGeneric6DofConstraint(*body, tr,false); + dof6->setLinearLowerLimit(btVector3(0,0,0)); + dof6->setLinearUpperLimit(btVector3(0,0,0)); + dof6->setAngularLowerLimit(btVector3(0,0,0)); + dof6->setAngularUpperLimit(btVector3(0,0,0)); + + m_dynamicsWorld->addConstraint(dof6); + m_pickConstraint = dof6; + + dof6->setParam(BT_CONSTRAINT_STOP_CFM,0.8,0); + dof6->setParam(BT_CONSTRAINT_STOP_CFM,0.8,1); + dof6->setParam(BT_CONSTRAINT_STOP_CFM,0.8,2); + dof6->setParam(BT_CONSTRAINT_STOP_CFM,0.8,3); + dof6->setParam(BT_CONSTRAINT_STOP_CFM,0.8,4); + dof6->setParam(BT_CONSTRAINT_STOP_CFM,0.8,5); + + dof6->setParam(BT_CONSTRAINT_STOP_ERP,0.1,0); + dof6->setParam(BT_CONSTRAINT_STOP_ERP,0.1,1); + dof6->setParam(BT_CONSTRAINT_STOP_ERP,0.1,2); + dof6->setParam(BT_CONSTRAINT_STOP_ERP,0.1,3); + dof6->setParam(BT_CONSTRAINT_STOP_ERP,0.1,4); + dof6->setParam(BT_CONSTRAINT_STOP_ERP,0.1,5); + } else + { + btPoint2PointConstraint* p2p = new btPoint2PointConstraint(*body,localPivot); + m_dynamicsWorld->addConstraint(p2p); + m_pickConstraint = p2p; + p2p->m_setting.m_impulseClamp = mousePickClamping; + //very weak constraint for picking + p2p->m_setting.m_tau = 0.001f; +/* + p2p->setParam(BT_CONSTRAINT_CFM,0.8,0); + p2p->setParam(BT_CONSTRAINT_CFM,0.8,1); + p2p->setParam(BT_CONSTRAINT_CFM,0.8,2); + p2p->setParam(BT_CONSTRAINT_ERP,0.1,0); + p2p->setParam(BT_CONSTRAINT_ERP,0.1,1); + p2p->setParam(BT_CONSTRAINT_ERP,0.1,2); + */ + + + } + use6Dof = !use6Dof; + + //save mouse position for dragging + gOldPickingPos = rayTo; + gHitPos = pickPos; + + gOldPickingDist = (pickPos-rayFrom).length(); + } + } + } + } + + } else + { + removePickingConstraint(); + } + + break; + + } + default: + { + } + } + +} + +void DemoApplication::removePickingConstraint() +{ + if (m_pickConstraint && m_dynamicsWorld) + { + m_dynamicsWorld->removeConstraint(m_pickConstraint); + delete m_pickConstraint; + //printf("removed constraint %i",gPickingConstraintId); + m_pickConstraint = 0; + pickedBody->forceActivationState(ACTIVE_TAG); + pickedBody->setDeactivationTime( 0.f ); + pickedBody = 0; + } +} + +void DemoApplication::mouseMotionFunc(int x,int y) +{ + + if (m_pickConstraint) + { + //move the constraint pivot + + if (m_pickConstraint->getConstraintType() == D6_CONSTRAINT_TYPE) + { + btGeneric6DofConstraint* pickCon = static_cast(m_pickConstraint); + if (pickCon) + { + //keep it at the same picking distance + + btVector3 newRayTo = getRayTo(x,y); + btVector3 rayFrom; + btVector3 oldPivotInB = pickCon->getFrameOffsetA().getOrigin(); + + btVector3 newPivotB; + if (m_ortho) + { + newPivotB = oldPivotInB; + newPivotB.setX(newRayTo.getX()); + newPivotB.setY(newRayTo.getY()); + } else + { + rayFrom = m_cameraPosition; + btVector3 dir = newRayTo-rayFrom; + dir.normalize(); + dir *= gOldPickingDist; + + newPivotB = rayFrom + dir; + } + pickCon->getFrameOffsetA().setOrigin(newPivotB); + } + + } else + { + btPoint2PointConstraint* pickCon = static_cast(m_pickConstraint); + if (pickCon) + { + //keep it at the same picking distance + + btVector3 newRayTo = getRayTo(x,y); + btVector3 rayFrom; + btVector3 oldPivotInB = pickCon->getPivotInB(); + btVector3 newPivotB; + if (m_ortho) + { + newPivotB = oldPivotInB; + newPivotB.setX(newRayTo.getX()); + newPivotB.setY(newRayTo.getY()); + } else + { + rayFrom = m_cameraPosition; + btVector3 dir = newRayTo-rayFrom; + dir.normalize(); + dir *= gOldPickingDist; + + newPivotB = rayFrom + dir; + } + pickCon->setPivotB(newPivotB); + } + } + } + + float dx, dy; + dx = btScalar(x) - m_mouseOldX; + dy = btScalar(y) - m_mouseOldY; + + + ///only if ALT key is pressed (Maya style) + if (m_modifierKeys& BT_ACTIVE_ALT) + { + if(m_mouseButtons & 2) + { + btVector3 hor = getRayTo(0,0)-getRayTo(1,0); + btVector3 vert = getRayTo(0,0)-getRayTo(0,1); + btScalar multiplierX = btScalar(0.001); + btScalar multiplierY = btScalar(0.001); + if (m_ortho) + { + multiplierX = 1; + multiplierY = 1; + } + + + m_cameraTargetPosition += hor* dx * multiplierX; + m_cameraTargetPosition += vert* dy * multiplierY; + } + + if(m_mouseButtons & (2 << 2) && m_mouseButtons & 1) + { + } + else if(m_mouseButtons & 1) + { + m_azi += dx * btScalar(0.2); + m_azi = fmodf(m_azi, btScalar(360.f)); + m_ele += dy * btScalar(0.2); + m_ele = fmodf(m_ele, btScalar(180.f)); + } + else if(m_mouseButtons & 4) + { + m_cameraDistance -= dy * btScalar(0.02f); + if (m_cameraDistancegetShapeType() != INVALID_SHAPE_PROXYTYPE)); + + //rigidbody is dynamic if and only if mass is non zero, otherwise static + bool isDynamic = (mass != 0.f); + + btVector3 localInertia(0,0,0); + if (isDynamic) + shape->calculateLocalInertia(mass,localInertia); + + //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects + +#define USE_MOTIONSTATE 1 +#ifdef USE_MOTIONSTATE + btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform); + + btRigidBody::btRigidBodyConstructionInfo cInfo(mass,myMotionState,shape,localInertia); + + btRigidBody* body = new btRigidBody(cInfo); + body->setContactProcessingThreshold(m_defaultContactProcessingThreshold); + +#else + btRigidBody* body = new btRigidBody(mass,0,shape,localInertia); + body->setWorldTransform(startTransform); +#endif// + + m_dynamicsWorld->addRigidBody(body); + + return body; +} + +//See http://www.lighthouse3d.com/opengl/glut/index.php?bmpfontortho +void DemoApplication::setOrthographicProjection() +{ + + // switch to projection mode + glMatrixMode(GL_PROJECTION); + + // save previous matrix which contains the + //settings for the perspective projection + glPushMatrix(); + // reset matrix + glLoadIdentity(); + // set a 2D orthographic projection + gluOrtho2D(0, m_glutScreenWidth, 0, m_glutScreenHeight); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // invert the y axis, down is positive + glScalef(1, -1, 1); + // mover the origin from the bottom left corner + // to the upper left corner + glTranslatef(btScalar(0), btScalar(-m_glutScreenHeight), btScalar(0)); + +} + +void DemoApplication::resetPerspectiveProjection() +{ + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + updateCamera(); +} + + + + +extern CProfileIterator * m_profileIterator; + +void DemoApplication::displayProfileString(int xOffset,int yStart,char* message) +{ + glRasterPos3f(btScalar(xOffset),btScalar(yStart),btScalar(0)); + GLDebugDrawString(xOffset,yStart,message); +} + + +float DemoApplication::showProfileInfo(int& xOffset,int& yStart, int yIncr) +{ +#ifndef BT_NO_PROFILE + + static double time_since_reset = 0.f; + if (!m_idle) + { + time_since_reset = CProfileManager::Get_Time_Since_Reset(); + } + + + { + //recompute profiling data, and store profile strings + + char blockTime[128]; + + double totalTime = 0; + + int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); + + m_profileIterator->First(); + + double parent_time = m_profileIterator->Is_Root() ? time_since_reset : m_profileIterator->Get_Current_Parent_Total_Time(); + + { + sprintf(blockTime,"--- Profiling: %s (total running time: %.3f ms) ---", m_profileIterator->Get_Current_Parent_Name(), parent_time ); + displayProfileString(xOffset,yStart,blockTime); + yStart += yIncr; + sprintf(blockTime,"press (1,2...) to display child timings, or 0 for parent" ); + displayProfileString(xOffset,yStart,blockTime); + yStart += yIncr; + + } + + + double accumulated_time = 0.f; + + for (int i = 0; !m_profileIterator->Is_Done(); m_profileIterator->Next()) + { + double current_total_time = m_profileIterator->Get_Current_Total_Time(); + accumulated_time += current_total_time; + double fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; + + sprintf(blockTime,"%d -- %s (%.2f %%) :: %.3f ms / frame (%d calls)", + ++i, m_profileIterator->Get_Current_Name(), fraction, + (current_total_time / (double)frames_since_reset),m_profileIterator->Get_Current_Total_Calls()); + displayProfileString(xOffset,yStart,blockTime); + yStart += yIncr; + totalTime += current_total_time; + } + + sprintf(blockTime,"%s (%.3f %%) :: %.3f ms", "Unaccounted", + // (min(0, time_since_reset - totalTime) / time_since_reset) * 100); + parent_time > SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f, parent_time - accumulated_time); + + displayProfileString(xOffset,yStart,blockTime); + yStart += yIncr; + + + + sprintf(blockTime,"-------------------------------------------------"); + displayProfileString(xOffset,yStart,blockTime); + yStart += yIncr; + + } +#endif//BT_NO_PROFILE + + return time_since_reset; + + +} + + +// +void DemoApplication::renderscene(int pass) +{ + btScalar m[16]; + btMatrix3x3 rot;rot.setIdentity(); + const int numObjects=m_dynamicsWorld->getNumCollisionObjects(); + btVector3 wireColor(1,0,0); + for(int i=0;igetCollisionObjectArray()[i]; + btRigidBody* body=btRigidBody::upcast(colObj); + if(body&&body->getMotionState()) + { + btDefaultMotionState* myMotionState = (btDefaultMotionState*)body->getMotionState(); + myMotionState->m_graphicsWorldTrans.getOpenGLMatrix(m); + rot=myMotionState->m_graphicsWorldTrans.getBasis(); + } + else + { + colObj->getWorldTransform().getOpenGLMatrix(m); + rot=colObj->getWorldTransform().getBasis(); + } + btVector3 wireColor(1.f,1.0f,0.5f); //wants deactivation + if(i&1) wireColor=btVector3(0.f,0.0f,1.f); + ///color differently for active, sleeping, wantsdeactivation states + if (colObj->getActivationState() == 1) //active + { + if (i & 1) + { + wireColor += btVector3 (1.f,0.f,0.f); + } + else + { + wireColor += btVector3 (.5f,0.f,0.f); + } + } + if(colObj->getActivationState()==2) //ISLAND_SLEEPING + { + if(i&1) + { + wireColor += btVector3 (0.f,1.f, 0.f); + } + else + { + wireColor += btVector3 (0.f,0.5f,0.f); + } + } + + btVector3 aabbMin,aabbMax; + m_dynamicsWorld->getBroadphase()->getBroadphaseAabb(aabbMin,aabbMax); + + aabbMin-=btVector3(BT_LARGE_FLOAT,BT_LARGE_FLOAT,BT_LARGE_FLOAT); + aabbMax+=btVector3(BT_LARGE_FLOAT,BT_LARGE_FLOAT,BT_LARGE_FLOAT); +// printf("aabbMin=(%f,%f,%f)\n",aabbMin.getX(),aabbMin.getY(),aabbMin.getZ()); +// printf("aabbMax=(%f,%f,%f)\n",aabbMax.getX(),aabbMax.getY(),aabbMax.getZ()); +// m_dynamicsWorld->getDebugDrawer()->drawAabb(aabbMin,aabbMax,btVector3(1,1,1)); + + + if (!(getDebugMode()& btIDebugDraw::DBG_DrawWireframe)) + { + switch(pass) + { + case 0: m_shapeDrawer->drawOpenGL(m,colObj->getCollisionShape(),wireColor,getDebugMode(),aabbMin,aabbMax);break; + case 1: m_shapeDrawer->drawShadow(m,m_sundirection*rot,colObj->getCollisionShape(),aabbMin,aabbMax);break; + case 2: m_shapeDrawer->drawOpenGL(m,colObj->getCollisionShape(),wireColor*btScalar(0.3),0,aabbMin,aabbMax);break; + } + } + } +} + +// +void DemoApplication::renderme() +{ + myinit(); + + updateCamera(); + + if (m_dynamicsWorld) + { + if(m_enableshadows) + { + glClear(GL_STENCIL_BUFFER_BIT); + glEnable(GL_CULL_FACE); + renderscene(0); + + glDisable(GL_LIGHTING); + glDepthMask(GL_FALSE); + glDepthFunc(GL_LEQUAL); + glEnable(GL_STENCIL_TEST); + glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); + glStencilFunc(GL_ALWAYS,1,0xFFFFFFFFL); + glFrontFace(GL_CCW); + glStencilOp(GL_KEEP,GL_KEEP,GL_INCR); + renderscene(1); + glFrontFace(GL_CW); + glStencilOp(GL_KEEP,GL_KEEP,GL_DECR); + renderscene(1); + glFrontFace(GL_CCW); + + glPolygonMode(GL_FRONT,GL_FILL); + glPolygonMode(GL_BACK,GL_FILL); + glShadeModel(GL_SMOOTH); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glEnable(GL_LIGHTING); + glDepthMask(GL_TRUE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + glEnable(GL_CULL_FACE); + glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); + + glDepthFunc(GL_LEQUAL); + glStencilFunc( GL_NOTEQUAL, 0, 0xFFFFFFFFL ); + glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + glDisable(GL_LIGHTING); + renderscene(2); + glEnable(GL_LIGHTING); + glDepthFunc(GL_LESS); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + } + else + { + glDisable(GL_CULL_FACE); + renderscene(0); + } + + int xOffset = 10; + int yStart = 20; + int yIncr = 20; + + + glDisable(GL_LIGHTING); + glColor3f(0, 0, 0); + + if ((m_debugMode & btIDebugDraw::DBG_NoHelpText)==0) + { + setOrthographicProjection(); + + showProfileInfo(xOffset,yStart,yIncr); + +#ifdef USE_QUICKPROF + + + if ( getDebugMode() & btIDebugDraw::DBG_ProfileTimings) + { + static int counter = 0; + counter++; + std::map::iterator iter; + for (iter = btProfiler::mProfileBlocks.begin(); iter != btProfiler::mProfileBlocks.end(); ++iter) + { + char blockTime[128]; + sprintf(blockTime, "%s: %lf",&((*iter).first[0]),btProfiler::getBlockTime((*iter).first, btProfiler::BLOCK_CYCLE_SECONDS));//BLOCK_TOTAL_PERCENT)); + glRasterPos3f(xOffset,yStart,0); + GLDebugDrawString(BMF_GetFont(BMF_kHelvetica10),blockTime); + yStart += yIncr; + + } + + } +#endif //USE_QUICKPROF + + + + + resetPerspectiveProjection(); + } + + glDisable(GL_LIGHTING); + + + } + + updateCamera(); + +} + +#include "BulletCollision/BroadphaseCollision/btAxisSweep3.h" + + +void DemoApplication::clientResetScene() +{ + removePickingConstraint(); + +#ifdef SHOW_NUM_DEEP_PENETRATIONS + gNumDeepPenetrationChecks = 0; + gNumGjkChecks = 0; +#endif //SHOW_NUM_DEEP_PENETRATIONS + + gNumClampedCcdMotions = 0; + int numObjects = 0; + int i; + + if (m_dynamicsWorld) + { + int numConstraints = m_dynamicsWorld->getNumConstraints(); + for (i=0;igetConstraint(0)->setEnabled(true); + } + numObjects = m_dynamicsWorld->getNumCollisionObjects(); + + ///create a copy of the array, not a reference! + btCollisionObjectArray copyArray = m_dynamicsWorld->getCollisionObjectArray(); + + + + + for (i=0;igetMotionState()) + { + btDefaultMotionState* myMotionState = (btDefaultMotionState*)body->getMotionState(); + myMotionState->m_graphicsWorldTrans = myMotionState->m_startWorldTrans; + body->setCenterOfMassTransform( myMotionState->m_graphicsWorldTrans ); + colObj->setInterpolationWorldTransform( myMotionState->m_startWorldTrans ); + colObj->forceActivationState(ACTIVE_TAG); + colObj->activate(); + colObj->setDeactivationTime(0); + //colObj->setActivationState(WANTS_DEACTIVATION); + } + //removed cached contact points (this is not necessary if all objects have been removed from the dynamics world) + if (m_dynamicsWorld->getBroadphase()->getOverlappingPairCache()) + m_dynamicsWorld->getBroadphase()->getOverlappingPairCache()->cleanProxyFromPairs(colObj->getBroadphaseHandle(),getDynamicsWorld()->getDispatcher()); + + btRigidBody* body = btRigidBody::upcast(colObj); + if (body && !body->isStaticObject()) + { + btRigidBody::upcast(colObj)->setLinearVelocity(btVector3(0,0,0)); + btRigidBody::upcast(colObj)->setAngularVelocity(btVector3(0,0,0)); + } + } + + } + + ///reset some internal cached data in the broadphase + m_dynamicsWorld->getBroadphase()->resetPool(getDynamicsWorld()->getDispatcher()); + m_dynamicsWorld->getConstraintSolver()->reset(); + + } + +} diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.h new file mode 100644 index 000000000..42903e3ec --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/DemoApplication.h @@ -0,0 +1,257 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef DEMO_APPLICATION_H +#define DEMO_APPLICATION_H + + +#include "GlutStuff.h" +#include "GL_ShapeDrawer.h" + +#include +#include +#include + + +#include "LinearMath/btVector3.h" +#include "LinearMath/btMatrix3x3.h" +#include "LinearMath/btTransform.h" +#include "LinearMath/btQuickprof.h" +#include "LinearMath/btAlignedObjectArray.h" + +class btCollisionShape; +class btDynamicsWorld; +class btRigidBody; +class btTypedConstraint; + + + +class DemoApplication +{ +protected: + void displayProfileString(int xOffset,int yStart,char* message); + class CProfileIterator* m_profileIterator; + + protected: +#ifdef USE_BT_CLOCK + btClock m_clock; +#endif //USE_BT_CLOCK + + ///this is the most important class + btDynamicsWorld* m_dynamicsWorld; + + ///constraint for mouse picking + btTypedConstraint* m_pickConstraint; + + virtual void removePickingConstraint(); + + btCollisionShape* m_shootBoxShape; + + float m_cameraDistance; + int m_debugMode; + + float m_ele; + float m_azi; + btVector3 m_cameraPosition; + btVector3 m_cameraTargetPosition;//look at + + int m_mouseOldX; + int m_mouseOldY; + int m_mouseButtons; +public: + int m_modifierKeys; +protected: + + float m_scaleBottom; + float m_scaleFactor; + btVector3 m_cameraUp; + int m_forwardAxis; + + int m_glutScreenWidth; + int m_glutScreenHeight; + + float m_frustumZNear; + float m_frustumZFar; + + int m_ortho; + + float m_ShootBoxInitialSpeed; + + bool m_stepping; + bool m_singleStep; + bool m_idle; + int m_lastKey; + + virtual float showProfileInfo(int& xOffset,int& yStart, int yIncr); + void renderscene(int pass); + + GL_ShapeDrawer* m_shapeDrawer; + bool m_enableshadows; + btVector3 m_sundirection; + btScalar m_defaultContactProcessingThreshold; + +public: + + DemoApplication(); + + virtual ~DemoApplication(); + + btDynamicsWorld* getDynamicsWorld() + { + return m_dynamicsWorld; + } + + virtual void initPhysics() = 0; + + virtual void setDrawClusters(bool drawClusters) + { + + } + + void overrideGLShapeDrawer (GL_ShapeDrawer* shapeDrawer); + + void setOrthographicProjection(); + void resetPerspectiveProjection(); + + bool setTexturing(bool enable) { return(m_shapeDrawer->enableTexture(enable)); } + bool setShadows(bool enable) { bool p=m_enableshadows;m_enableshadows=enable;return(p); } + bool getTexturing() const + { + return m_shapeDrawer->hasTextureEnabled(); + } + bool getShadows() const + { + return m_enableshadows; + } + + + int getDebugMode() + { + return m_debugMode ; + } + + void setDebugMode(int mode); + + void setAzi(float azi) + { + m_azi = azi; + } + + void setCameraUp(const btVector3& camUp) + { + m_cameraUp = camUp; + } + void setCameraForwardAxis(int axis) + { + m_forwardAxis = axis; + } + + virtual void myinit(); + + void toggleIdle(); + + virtual void updateCamera(); + + btVector3 getCameraPosition() + { + return m_cameraPosition; + } + btVector3 getCameraTargetPosition() + { + return m_cameraTargetPosition; + } + + btScalar getDeltaTimeMicroseconds() + { +#ifdef USE_BT_CLOCK + btScalar dt = (btScalar)m_clock.getTimeMicroseconds(); + m_clock.reset(); + return dt; +#else + return btScalar(16666.); +#endif + } + void setFrustumZPlanes(float zNear, float zFar) + { + m_frustumZNear = zNear; + m_frustumZFar = zFar; + } + + ///glut callbacks + + float getCameraDistance(); + void setCameraDistance(float dist); + void moveAndDisplay(); + + virtual void clientMoveAndDisplay() = 0; + + virtual void clientResetScene(); + + ///Demo functions + virtual void setShootBoxShape (); + virtual void shootBox(const btVector3& destination); + + + btVector3 getRayTo(int x,int y); + + btRigidBody* localCreateRigidBody(float mass, const btTransform& startTransform,btCollisionShape* shape); + + ///callback methods by glut + + virtual void keyboardCallback(unsigned char key, int x, int y); + + virtual void keyboardUpCallback(unsigned char key, int x, int y) {} + + virtual void specialKeyboard(int key, int x, int y){} + + virtual void specialKeyboardUp(int key, int x, int y){} + + virtual void reshape(int w, int h); + + virtual void mouseFunc(int button, int state, int x, int y); + + virtual void mouseMotionFunc(int x,int y); + + virtual void displayCallback(); + + virtual void renderme(); + + virtual void swapBuffers() = 0; + + virtual void updateModifierKeys() = 0; + + void stepLeft(); + void stepRight(); + void stepFront(); + void stepBack(); + void zoomIn(); + void zoomOut(); + + bool isIdle() const + { + return m_idle; + } + + void setIdle(bool idle) + { + m_idle = idle; + } + + +}; + +#endif //DEMO_APPLICATION_H + + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.cpp new file mode 100644 index 000000000..79758df6c --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.cpp @@ -0,0 +1,139 @@ + +#include "GLDebugDrawer.h" +#include "GLDebugFont.h" +#include "GlutStuff.h" + + + +#include //printf debugging +GLDebugDrawer::GLDebugDrawer() +:m_debugMode(0) +{ + +} + +void GLDebugDrawer::drawLine(const btVector3& from,const btVector3& to,const btVector3& fromColor, const btVector3& toColor) +{ + glBegin(GL_LINES); + glColor3f(fromColor.getX(), fromColor.getY(), fromColor.getZ()); + glVertex3d(from.getX(), from.getY(), from.getZ()); + glColor3f(toColor.getX(), toColor.getY(), toColor.getZ()); + glVertex3d(to.getX(), to.getY(), to.getZ()); + glEnd(); +} + +void GLDebugDrawer::drawLine(const btVector3& from,const btVector3& to,const btVector3& color) +{ + drawLine(from,to,color,color); +} + +void GLDebugDrawer::drawSphere (const btVector3& p, btScalar radius, const btVector3& color) +{ + glColor4f (color.getX(), color.getY(), color.getZ(), btScalar(1.0f)); + glPushMatrix (); + glTranslatef (p.getX(), p.getY(), p.getZ()); + + int lats = 5; + int longs = 5; + + int i, j; + for(i = 0; i <= lats; i++) { + btScalar lat0 = SIMD_PI * (-btScalar(0.5) + (btScalar) (i - 1) / lats); + btScalar z0 = radius*sin(lat0); + btScalar zr0 = radius*cos(lat0); + + btScalar lat1 = SIMD_PI * (-btScalar(0.5) + (btScalar) i / lats); + btScalar z1 = radius*sin(lat1); + btScalar zr1 = radius*cos(lat1); + + glBegin(GL_QUAD_STRIP); + for(j = 0; j <= longs; j++) { + btScalar lng = 2 * SIMD_PI * (btScalar) (j - 1) / longs; + btScalar x = cos(lng); + btScalar y = sin(lng); + + glNormal3f(x * zr0, y * zr0, z0); + glVertex3f(x * zr0, y * zr0, z0); + glNormal3f(x * zr1, y * zr1, z1); + glVertex3f(x * zr1, y * zr1, z1); + } + glEnd(); + } + + glPopMatrix(); +} + +void GLDebugDrawer::drawBox (const btVector3& boxMin, const btVector3& boxMax, const btVector3& color, btScalar alpha) +{ + btVector3 halfExtent = (boxMax - boxMin) * btScalar(0.5f); + btVector3 center = (boxMax + boxMin) * btScalar(0.5f); + //glEnable(GL_BLEND); // Turn blending On + //glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glColor4f (color.getX(), color.getY(), color.getZ(), alpha); + glPushMatrix (); + glTranslatef (center.getX(), center.getY(), center.getZ()); + glScaled(2*halfExtent[0], 2*halfExtent[1], 2*halfExtent[2]); +// glutSolidCube(1.0); + glPopMatrix (); + //glDisable(GL_BLEND); +} + +void GLDebugDrawer::drawTriangle(const btVector3& a,const btVector3& b,const btVector3& c,const btVector3& color,btScalar alpha) +{ +// if (m_debugMode > 0) + { + const btVector3 n=btCross(b-a,c-a).normalized(); + glBegin(GL_TRIANGLES); + glColor4f(color.getX(), color.getY(), color.getZ(),alpha); + glNormal3d(n.getX(),n.getY(),n.getZ()); + glVertex3d(a.getX(),a.getY(),a.getZ()); + glVertex3d(b.getX(),b.getY(),b.getZ()); + glVertex3d(c.getX(),c.getY(),c.getZ()); + glEnd(); + } +} + +void GLDebugDrawer::setDebugMode(int debugMode) +{ + m_debugMode = debugMode; + +} + +void GLDebugDrawer::draw3dText(const btVector3& location,const char* textString) +{ + glRasterPos3f(location.x(), location.y(), location.z()); + //BMF_DrawString(BMF_GetFont(BMF_kHelvetica10),textString); +} + +void GLDebugDrawer::reportErrorWarning(const char* warningString) +{ + printf("%s\n",warningString); +} + +void GLDebugDrawer::drawContactPoint(const btVector3& pointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) +{ + + { + btVector3 to=pointOnB+normalOnB*1;//distance; + const btVector3&from = pointOnB; + glColor4f(color.getX(), color.getY(), color.getZ(),1.f); + //glColor4f(0,0,0,1.f); + glBegin(GL_LINES); + glVertex3d(from.getX(), from.getY(), from.getZ()); + glVertex3d(to.getX(), to.getY(), to.getZ()); + glEnd(); + + +// glRasterPos3f(from.x(), from.y(), from.z()); +// char buf[12]; +// sprintf(buf," %d",lifeTime); + //BMF_DrawString(BMF_GetFont(BMF_kHelvetica10),buf); + + + } +} + + + + + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.h new file mode 100644 index 000000000..2a05405a7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugDrawer.h @@ -0,0 +1,38 @@ +#ifndef GL_DEBUG_DRAWER_H +#define GL_DEBUG_DRAWER_H + +#include "LinearMath/btIDebugDraw.h" + + + +class GLDebugDrawer : public btIDebugDraw +{ + int m_debugMode; + +public: + + GLDebugDrawer(); + + + virtual void drawLine(const btVector3& from,const btVector3& to,const btVector3& fromColor, const btVector3& toColor); + + virtual void drawLine(const btVector3& from,const btVector3& to,const btVector3& color); + + virtual void drawSphere (const btVector3& p, btScalar radius, const btVector3& color); + virtual void drawBox (const btVector3& boxMin, const btVector3& boxMax, const btVector3& color, btScalar alpha); + + virtual void drawTriangle(const btVector3& a,const btVector3& b,const btVector3& c,const btVector3& color,btScalar alpha); + + virtual void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color); + + virtual void reportErrorWarning(const char* warningString); + + virtual void draw3dText(const btVector3& location,const char* textString); + + virtual void setDebugMode(int debugMode); + + virtual int getDebugMode() const { return m_debugMode;} + +}; + +#endif//GL_DEBUG_DRAWER_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.cpp new file mode 100644 index 000000000..b62973182 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.cpp @@ -0,0 +1,1000 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "GLDebugFont.h" + + +#ifdef _WIN32//for glut.h +#include +#endif + +//think different +#if defined(__APPLE__) && !defined (VMDMESA) +#include +#if (defined (TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || (defined (TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) +#import +#define glOrtho glOrthof +#else +#include +#include +#include +#endif +#else + + + +#ifdef _WINDOWS +#include +#include +#include +#else +#include +#include +#endif +#endif + +#include +#include //for memset + +extern unsigned char sFontData[]; +static bool sTexturesInitialized = false; + +static GLuint sTexture = -1; +static int sScreenWidth = -1; +static int sScreenHeight = -1; + + +void GLDebugResetFont(int screenWidth,int screenHeight) +{ + + if ((sScreenWidth == screenWidth) && (sScreenHeight == screenHeight)) + return; + + sScreenWidth = screenWidth; + sScreenHeight = screenHeight; + + if (!sTexturesInitialized) + { + sTexturesInitialized = true; + glGenTextures(1, &sTexture); + glBindTexture(GL_TEXTURE_2D, sTexture); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, 3, 256 , 256 , 0, GL_RGB, GL_UNSIGNED_BYTE, &sFontData[0]); + } + + printf("generating font at resolution %d,%d\n",screenWidth,screenHeight); + +} + +#define USE_ARRAYS 1 + +void GLDebugDrawStringInternal(int x,int y,const char* string, const btVector3& rgb) +{ + GLDebugDrawStringInternal(x,y,string,rgb,true,10); +} + +void GLDebugDrawStringInternal(int x,int y,const char* string, const btVector3& rgb, bool enableBlend, int spacing) +{ + + if (!sTexturesInitialized) + { + GLDebugResetFont(sScreenWidth,sScreenHeight); + } + if (strlen(string)) + { + + glColor4f(rgb.getX(),rgb.getY(),rgb.getZ(),1.f); + float cx; + float cy; + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glDisable(GL_TEXTURE_GEN_R); + + glEnable(GL_TEXTURE_2D); + glBlendFunc(GL_SRC_ALPHA,GL_ONE); + glDepthFunc (GL_LEQUAL); + + if (enableBlend) + { + glEnable(GL_BLEND); + } else + { + glDisable(GL_BLEND); + } + glEnable (GL_DEPTH_TEST); + glBindTexture(GL_TEXTURE_2D, sTexture); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glOrtho(0,sScreenWidth,0,sScreenHeight,-1,1); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(btScalar(x),btScalar(sScreenHeight - y),btScalar(0)); + +#if USE_ARRAYS + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); +#endif + + GLfloat verts[] ={ + 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 0.f,0.f,0.f + }; + + GLfloat uv_texcoords[] = { + 0,0, + 0,0, + 0,0, + 0,0 + }; + verts[0] = 0; verts[1] = 0; verts[2] = 0; + verts[3] = 16-1; verts[4] = 0; verts[5] = 0; + verts[6] = 16-1; verts[7] = 16-1; verts[8] = 0; + verts[9] = 0; verts[10] = 16-1; verts[11] = 0; + + for (int i=0;i=0) + { + cx=float(ch%16) * btScalar(1./16.f); + cy=float(ch/16) * btScalar(1./16.f); + + uv_texcoords[0] = cx; uv_texcoords[1] = btScalar(1-cy-1./16.f); + uv_texcoords[2] = btScalar(cx+1./16.f); uv_texcoords[3] = btScalar(1-cy-1./16.f); + uv_texcoords[4] = btScalar(cx+1./16.f); uv_texcoords[5] = btScalar(1-cy); + uv_texcoords[6] = cx; uv_texcoords[7] = btScalar(1-cy); +#if USE_ARRAYS + glTexCoordPointer(2,GL_FLOAT,0,uv_texcoords); + glVertexPointer(3, GL_FLOAT, 0, verts); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +#else + glBegin(GL_QUADS); + glTexCoord2f(cx,1-cy-1./16.f); + + glVertex2i(0,0); + glTexCoord2f(cx+1./16.f,1-cy-1./16.f); + + glVertex2i(16 - 1,0); + glTexCoord2f(cx+1./16.f,1-cy); + + glVertex2i(16 - 1,16 -1); + glTexCoord2f(cx,1-cy); + + glVertex2i(0,16 -1); + glEnd(); +#endif + + glTranslatef(spacing,0,0); + } + } + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +#if 1 + glEnable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA,GL_ONE); + glDepthFunc (GL_LEQUAL); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glScalef(btScalar(0.025),btScalar(0.025),btScalar(0.025)); +#endif + glMatrixMode(GL_MODELVIEW); +#if USE_ARRAYS + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState (GL_TEXTURE_COORD_ARRAY); +#endif + //glDisable(GL_TEXTURE_2D); + } +} + +void GLDebugDrawString(int x,int y,const char* string) +{ + + btVector3 rgb(1,1,1); + GLDebugDrawStringInternal(x,y,string,rgb); +} + + +unsigned char sFontData[] = +{ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,145,145,145,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,213,213,213,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,2,2,2,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213, + 213,213,255,255,255,255,255,255,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,213,213,213,255,255,255,255,255,255,255,255,255,213,213,213,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,178,178,178,178,178,178,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,0,0,0,0,0,0,0,0,0,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,178,178,178,255,255,255,255,255,255,255,255,255,145,145,145,103,103,103,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,2,2,2,255,255,255,178,178,178,103,103,103,145,145,145,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,145,145,145,103,103,103,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,178,178,178,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,178,178,178,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,103,103,103,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,145,145,145,178,178,178,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,0,0,0,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,70,70,70,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70, + 70,70,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,70,70, + 70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,213,213,213,255,255,255,255,255,255,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,103,103,103,255,255,255,213,213,213,70,70,70,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,255,255,255,246,246,246,178,178,178,246,246,246,70,70,70,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37, + 37,37,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,178,178,178,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,213,213, + 213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,255,255,255,103,103,103,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,178,178,178,255,255,255,37,37,37,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,255,255,255,255,255,255,145,145,145,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,103,103,103,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,145,145,145,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,70,70,70,255,255,255,70,70,70,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,213,213,213,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,70,70,70,255,255,255,37,37,37,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,103,103,103,246,246,246,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,70,70,70,255,255,255,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,0,0,0,37,37,37,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,0,0,0,37,37,37,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,70,70,70,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,213,213,213,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,37,37,37,255,255,255,178,178,178,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,70,70,70,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,145,145,145,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,70,70,70,255,255,255,37,37,37,0,0,0,145,145,145,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,178,178,178,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,0,0,0,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,0,0,0,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,255,255,255,213,213,213,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,246,246,246,103,103,103,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,37,37,37,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,103,103,103,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,103,103,103,70,70,70,0,0,0,103,103,103,255,255, + 255,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,2,2,2,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,178,178,178,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,255,255,255,246,246, + 246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,103,103,103,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255, + 255,255,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,178,178,178,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,178,178,178,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178, + 178,178,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,145,145,145,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,145,145,145,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,255,255,255,145,145,145,0,0,0,37,37,37,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,145,145,145,0,0,0,37,37,37,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2, + 2,2,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103, + 103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,178,178, + 178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37, + 37,37,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246, + 246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,0,0,0,37,37,37,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,246,246,246,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,103,103, + 103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,0,0,0,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103, + 103,103,246,246,246,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,103,103,103,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,70,70,70,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,37,37,37,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,37,37,37,70,70,70,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,178,178,178,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,246,246,246,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,145,145,145,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,213,213,213,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,213,213,213,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246, + 246,246,255,255,255,255,255,255,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,145,145,145,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,178,178,178,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,246,246,246,103,103,103,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,103,103,103,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255, + 255,255,213,213,213,178,178,178,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,178,178,178,0,0,0,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,178,178,178,255,255,255,255,255,255,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,103,103,103,178,178,178,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,37,37,37,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,2,2,2,37,37,37,145,145,145,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255, + 255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,145,145,145,255,255,255,103,103,103,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,178,178,178,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,246,246,246,0,0,0,0,0,0,246,246,246,70,70, + 70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,2,2,2,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,2,2,2,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,178,178,178,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,70,70,70,2,2,2,2,2,2,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,103,103,103,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,255,255,255,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,178,178,178,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,103,103,103,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,145,145,145,255,255,255,103,103,103,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,178,178,178,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,246,246,246,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,255,255,255,246,246,246,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,145,145,145,103,103,103,178,178,178,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,103,103,103,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255, + 255,255,213,213,213,0,0,0,37,37,37,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,213,213,213,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,70,70,70,103,103,103,246,246,246,0,0,0,213,213,213,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,103,103,103,103,103,103,255,255,255,255,255,255,246,246, + 246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,213,213,213,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,145,145,145,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,70,70,70,178,178,178,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255, + 255,255,255,255,255,0,0,0,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,145,145,145,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,103,103,103,103,103,103,145,145,145,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,178,178,178,103,103,103,178,178,178,255,255,255,145,145,145,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,213,213,213,213,213,213,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213, + 213,213,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,213,213,213,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,213,213,213,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,255,255,255,145,145,145,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,178,178,178,255,255,255,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246, + 246,246,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,178,178,178,255,255,255,178,178,178,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,178,178,178,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,0,0,0,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,37,37,37,178,178,178,255,255,255,37,37,37,178,178,178,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,0,0,0,70,70,70,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,213,213,213,103,103,103,103,103,103,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,37,37,37,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,103,103,103,255,255,255,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246, + 246,246,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,255,255,255,2,2,2,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,103,103,103,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,103,103,103,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,103,103,103,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,213,213,213,255,255,255,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,37,37,37,255,255,255,255,255,255,70,70,70,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,145,145,145,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,178,178,178,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,213,213,213,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,145,145,145,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,145,145,145,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,178,178,178,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,178,178,178,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,37,37, + 37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,246,246,246,255,255,255,145,145,145,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,213,213,213,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246, + 246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,246,246,246,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255, + 255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,0,0,0,103,103,103,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,145,145,145,103,103,103,178,178,178,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,213,213,213,255,255,255,246,246,246,2,2,2,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,103,103,103,178,178,178,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37, + 37,37,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,2,2,2,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,145,145,145,0,0,0,0,0,0,255,255,255,103,103, + 103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,103,103,103,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2, + 2,2,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,37,37,37,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,255,255,255,2,2,2,246,246,246,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,145,145,145,103,103, + 103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,213,213,213,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,246,246,246,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103, + 103,103,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,103,103,103,246,246,246,255,255,255,103,103,103,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,70,70,70,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,2,2,2,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,145,145,145,2,2,2,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,70,70,70,255,255,255,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213, + 213,213,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,255,255,255,255,255,255,2,2,2,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,70,70,70,0,0,0,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,0,0,0,255,255,255,37,37,37,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,37,37,37,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,103,103,103,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,103,103,103,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,255,255,255,246,246,246,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255, + 255,255,103,103,103,103,103,103,0,0,0,70,70,70,103,103,103,255,255,255,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,246,246,246,2,2,2,0,0,0,0,0,0,213,213,213,255,255,255,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,0,0,0,213,213,213,255,255,255,145,145,145,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,145,145,145,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,103,103,103,213,213,213,255,255,255,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,2,2,2,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,178,178,178,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,213,213,213,103,103,103,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,103,103,103,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,145,145,145,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,178,178,178,103,103,103,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,103,103,103,145,145,145,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255, + 255,255,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,103,103,103,103,103,103,213,213,213,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,103,103,103,0,0,0,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,70,70,70,0,0,0,37,37,37,255,255,255,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,103,103,103,103,103,103,103,103,103,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,255,255,255,103,103,103,0,0,0,2,2,2,178,178,178,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,255,255,255,103,103,103,0,0,0,70,70,70,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,103,103,103,255,255,255,255,255,255,178,178,178,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,145,145,145,178,178,178,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,246,246,246,255,255,255,2,2,2,213,213,213,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,103,103,103,103,103,103,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,178,178,178,103,103,103,255,255,255,213,213,213,70,70,70,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,103,103,103,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,103,103,103,103,103,103,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,2,2,2,178,178,178,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,246,246,246,255,255,255,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,255,255,255,213,213,213,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,37,37,37,255,255,255,178,178,178,37,37,37,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,246,246,246,255,255,255,178,178,178,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,70,70,70,255,255,255,70,70,70,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,145,145,145,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246, + 246,246,213,213,213,0,0,0,70,70,70,255,255,255,37,37,37,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,70,70,70,255,255,255,37,37,37,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,246,246,246,103,103,103,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,213,213,213,255,255,255,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145, + 145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37, + 37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,246,246,246,2,2,2,103,103,103,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,246,246,246,103,103,103,103,103,103,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,103,103,103,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213, + 213,213,255,255,255,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,103,103,103,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,103,103,103,0,0,0,103,103,103,255,255,255,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,103,103,103,255,255,255,246,246,246,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,255,255,255,246,246, + 246,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,103,103,103,103,103,103,0,0,0,255,255,255,255,255,255,145,145,145,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,213,213,213,255,255,255,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,2,2,2,0,0,0,255,255,255,255,255,255,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,70,70,70,0,0,0,103,103,103,255,255,255,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,145,145,145,103,103,103,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,70,70,70,246,246,246,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,103,103,103,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255, + 255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,178,178,178,255,255,255,178,178,178,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,103,103,103,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,255,255,255,178,178,178,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213, + 213,213,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,103,103,103,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,246,246,246,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,213,213,213,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,37,37,37,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,213,213,213,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,213,213,213,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,178,178,178,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246, + 246,246,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,246,246,246,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,178,178,178,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145, + 145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,246,246,246,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37, + 37,37,255,255,255,103,103,103,103,103,103,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,103,103,103,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,103,103,103,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,213,213,213,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,213,213,213,103,103,103,103,103,103,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,246,246,246,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,178,178,178,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,246,246,246,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,246,246,246,213,213,213,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,213,213,213,255,255,255,255,255,255,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,178, + 178,178,0,0,0,0,0,0,213,213,213,213,213,213,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,2,2,2,178,178,178,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,178,178,178,255,255,255,178,178,178,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,145,145,145,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103, + 103,103,255,255,255,178,178,178,0,0,0,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,103,103,103,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,246,246,246,255,255,255,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,2,2,178,178,178,255,255,255,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,213,213,213,37,37,37,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,213,213,213,255,255,255,37,37, + 37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,213,213,213,255,255,255,145,145,145,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255, + 255,255,255,255,255,145,145,145,0,0,0,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,37,37,37,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,70,70,70,2,2,2,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255, + 255,255,0,0,0,255,255,255,2,2,2,0,0,0,2,2,2,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255, + 255,255,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,2,2,2,255,255,255,145,145,145,70,70, + 70,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70, + 70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,145,145,145,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,255,255, + 255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,246,246,246,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37, + 37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0, + 0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,213,213,213,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,2,2,2,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,213,213,213,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,37,37,37,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,2,2,2,255,255,255,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,246,246,246,103,103,103,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,103,103,103,246,246,246,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,2,2,2,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,103,103,103,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,213,213,213,246,246,246,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,145,145,145,246,246,246,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,103,103,103,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,103,103,103,255,255,255,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37, + 37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,37,37,37,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,145,145,145,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,37,37,37,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,70,70,70,0,0,0,2,2,2,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,37,37,37,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0, + 0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,145,145,145,0,0,0,70,70,70,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,178,178,178,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0, + 0,145,145,145,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,213,213,213,145,145,145,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,159,159,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,246,246,246,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,37,37,37,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,255,255,255,255,255,255,255,255, + 255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,213,213,213,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,213,213,213,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,246,246,246,0,0,0,0,0,0,213,213,213,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,213,213,213,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0, + 0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103, + 103,103,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,178,178,178,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,178,178,178,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,2,2,178,178,178,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,178,178,178,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,145,145, + 145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,70,70,70,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255, + 255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,145,145,145,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,145,145,145,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,213,213,213,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,178,178, + 178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,70,70,70,0,0,0,0,0,0,2,2,2,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,37,37,37,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,70,70,70,0,0,0,2,2,2,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,37,37,37,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,2,2,2,0,0,0,70,70,70,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,37,37,37,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,145,145,145,0,0,0,70,70,70,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,246,246,246,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,70,70,70,0,0,0,145,145,145,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,145,145,145,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,213,213,213,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,70,70,70,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,37,37,37,246,246,246,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,37,37,37,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,37,37,37,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,213,213,213,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,145,145,145,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,213,213,213,0,0,0,145,145,145,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,213,213,213,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,255,255,255,0,0,0,255,255,255,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,145,145,145,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,246,246,246,0,0,0,255,255,255,0,0,0,213,213,213,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,0,0,0,103,103,103,246,246,246,255,255,255,0,0,0,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178, + 178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,255,255,255,37,37,37,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,2,2,2,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,255,255,255,70,70,70,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,2,2,2,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,70,70,70,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,246,246,246,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,37,37,37,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,213,213,213,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,37,37,37,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,37,37,37,246,246,246,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,70,70,70,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,246,246,246,103,103,103,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,213,213,213,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,37,37,37,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37, + 37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,37,37,37,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,246,246,246,103,103,103,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,246,246,246,103,103,103,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,246,246,246,103,103,103,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,178,178,178,246,246,246,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,246,246,246,246,246,246,246,246,246,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,246,246,246,103,103,103,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213, + 213,213,178,178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,213,213,213,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,37,37,37,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,246,246,246,246,246,246,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,255,255,255,37,37,37,70,70,70,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,70,70,70,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,246,246,246,2,2,2,145,145,145,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,70,70,70,255,255,255,246,246,246,178,178,178,213,213,213,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,246,246,246,2,2,2,145,145,145,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,37,37,37,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,145,145,145,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,2,2,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,178,178,178,145,145,145,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,37,37,37,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,246,246,246,255,255,255,0,0,0,178,178,178,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,37,37,37,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,246,246,246,103,103,103,103,103,103,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,37,37,37,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37,37,0,0,0,145,145,145,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,103,103,103,0,0,0,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,246,246,246,0,0,0,255,255,255,213,213,213,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,37,37,37,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,255,255,255,0,0,0,255,255,255,2,2,2,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,255,255,255,103,103,103,255,255,255,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,246,246,246,255,255,255,178,178,178,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,37,37,37,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,37,37, + 37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,2,2,2,246,246,246,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,103,103,103,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,37,37,37,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,213,213,213,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,178,178,178,255,255,255,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,178,178, + 178,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,213,213,213,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,145,145,145,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,246,246,246,246,246,246,246,246,246,246,246,246,246,246, + 246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,103,103,103,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,246,246,246,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,37,37, + 37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,103,103,103,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,0,0,0,0,0,0,103,103,103,0,0,0,70,70,70,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,255,255,255,178,178,178,178,178,178,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,103,103,103,255,255,255,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,2,2,2,255,255,255,0,0,0,178,178,178,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,246,246,246,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,2,2,2,246,246,246,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,246,246,246,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,213,213,213,178,178,178,255,255,255,70,70,70,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,103,103,103,255,255,255,2,2,2,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,145,145,145,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,255,255,255,2,2,2,103,103,103,213,213,213,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,178,178,178,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,103,103,103,0,0,0,145,145,145,103,103,103,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,255,255,255,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,246,246,246,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,103,103,103,246,246,246,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,37,37,37,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,70,70,70,0,0,0,0,0,0,2,2,2,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,70,70,70,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,178,178,178,255,255,255,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,103,103,103,255,255, + 255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,213,213,213,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,246,246,246,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246, + 246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,213,213,213,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,70,70,70,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,246,246,246,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,246,246,246,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,103,103,103,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,246,246,246,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,70,70,70,103,103,103,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,37,37,37,246,246,246,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,246,246,246,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,213,213,213,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,178,178,178,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,103,103,103,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,178,178,178,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,246,246,246,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,213,213,213,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,2,2,2,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,246,246,246,246,246,246,103,103,103,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,103,103,103,103,103,103,145,145,145,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,2,2,2,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,2,2,2,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,255,255,255,255,255,255,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,145,145,145,246,246,246,246,246,246,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,255,255,255,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,70,70,70,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,255,255,255,255,255,255,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,178, + 178,178,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,255,255,255,255,255,255,103,103,103,145,145,145,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,2,2,2,0,0,0,255,255,255,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246, + 246,246,37,37,37,0,0,0,37,37,37,246,246,246,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,103,103,103,0,0,0,0,0,0,246,246,246,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,103,103,103,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,0,0,0,103,103,103,103,103,103,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,246,246,246,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,255,255,255,246,246,246,255,255,255,255,255,255,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,70,70,70,246,246,246,37,37,37,246,246,246,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,246,246,246,255,255,255,0,0,0,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,246,246,246,255,255,255,255,255,255,246,246,246,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,103,103,103,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,213,213,213,103,103,103,145,145,145,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,70,70,70,255,255,255,2,2,2,0,0,0,246,246,246,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,255,255,255,103,103,103,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,37,37,37,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,2,2,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,255,255,255,246,246,246,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,246,246,246,246,246,246,103,103,103,255,255,255,178,178,178,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255, + 255,255,145,145,145,255,255,255,37,37,37,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,255,255,255,145,145,145,178,178, + 178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,255,255,255,103,103,103,213,213,213,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,255,145,145,145,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,145,145,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,2,2,2,0,0,0,70,70, + 70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,2,2,2,0,0,0,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,255,255,255,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0, + 0,0,0,0,0,103,103,103,103,103,103,0,0,0,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,103,103,103,2,2,2,70,70,70,70,70, + 70,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,70,70,70,0,0,0,178,178,178,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,70,70,70,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,255,255,255,103,103,103,255,255,255,2,2,2,103,103,103,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,213,213,103, + 103,103,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,213,213,213,0,0,0,255,255,255,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,178,178,178,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,103,103,103,0,0,0,246,246,246,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,103,103,103,0,0,0,103,103,103,37,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,213,213,213,255,255,255,246,246,246,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178, + 178,178,246,246,246,37,37,37,0,0,0,0,0,0,0,0,0,103,103,103,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,178,178,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,255,255,255,255,255,255,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,178,178,178,255,255,255,145,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,145,145,145,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,70,70,70,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,2,2,2,0,0,0,70,70,70,70,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,246,246,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,103,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.h new file mode 100644 index 000000000..bf2c2575d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GLDebugFont.h @@ -0,0 +1,29 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef BT_DEBUG_FONT_H +#define BT_DEBUG_FONT_H + +#include "LinearMath/btVector3.h" + + +void GLDebugDrawStringInternal(int x,int y,const char* string,const btVector3& rgb, bool enableBlend, int spacing); +void GLDebugDrawStringInternal(int x,int y,const char* string,const btVector3& rgb); +void GLDebugDrawString(int x,int y,const char* string); +void GLDebugResetFont(int screenWidth,int screenHeight); + +#endif //BT_DEBUG_FONT_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.cpp new file mode 100644 index 000000000..7beb19f05 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.cpp @@ -0,0 +1,1058 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifdef _WIN32 //needed for glut.h +#include +#endif +#include "GLDebugFont.h" + + + +#include "GlutStuff.h" +#include "GL_ShapeDrawer.h" +#include "BulletCollision/CollisionShapes/btPolyhedralConvexShape.h" +#include "BulletCollision/CollisionShapes/btTriangleMeshShape.h" +#include "BulletCollision/CollisionShapes/btBoxShape.h" +#include "BulletCollision/CollisionShapes/btSphereShape.h" +#include "BulletCollision/CollisionShapes/btConeShape.h" +#include "BulletCollision/CollisionShapes/btCylinderShape.h" +#include "BulletCollision/CollisionShapes/btTetrahedronShape.h" +#include "BulletCollision/CollisionShapes/btCompoundShape.h" +#include "BulletCollision/CollisionShapes/btCapsuleShape.h" +#include "BulletCollision/CollisionShapes/btConvexTriangleMeshShape.h" +#include "BulletCollision/CollisionShapes/btUniformScalingShape.h" +#include "BulletCollision/CollisionShapes/btStaticPlaneShape.h" +#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" +#include "BulletCollision/CollisionShapes/btConvexPolyhedron.h" + + +/// +#include "BulletCollision/CollisionShapes/btShapeHull.h" + +#include "LinearMath/btTransformUtil.h" + + +#include "LinearMath/btIDebugDraw.h" +//for debugmodes + +#include //printf debugging + +//#define USE_DISPLAY_LISTS 1 +#ifdef USE_DISPLAY_LISTS + +#include + +using namespace std; + +//Set for storing Display list per trimesh +struct TRIMESH_KEY +{ + btCollisionShape* m_shape; + GLuint m_dlist;//OpenGL display list +}; + +typedef map TRIMESH_KEY_MAP; + +typedef pair TRIMESH_KEY_PAIR; + +TRIMESH_KEY_MAP g_display_lists; + +class GlDisplaylistDrawcallback : public btTriangleCallback +{ +public: + + virtual void processTriangle(btVector3* triangle,int partId, int triangleIndex) + { + + btVector3 diff1 = triangle[1] - triangle[0]; + btVector3 diff2 = triangle[2] - triangle[0]; + btVector3 normal = diff1.cross(diff2); + + normal.normalize(); + + glBegin(GL_TRIANGLES); + glColor3f(1, 1, 1); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + + //glColor3f(0, 1, 0); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + + //glColor3f(0, 1, 0); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glEnd(); + + /*glBegin(GL_LINES); + glColor3f(1, 1, 0); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glColor3f(1, 1, 0); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glColor3f(1, 1, 0); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glNormal3d(normal.getX(),normal.getY(),normal.getZ()); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glEnd();*/ + + + } +}; + +GLuint OGL_get_displaylist_for_shape(btCollisionShape * shape) +{ + TRIMESH_KEY_MAP::iterator map_iter; + + unsigned long key = (unsigned long)shape; + map_iter = g_display_lists.find(key); + if(map_iter!=g_display_lists.end()) + { + return map_iter->second.m_dlist; + } + + return 0; +} + +void OGL_displaylist_clean() +{ + TRIMESH_KEY_MAP::iterator map_iter,map_itend; + + map_iter = g_display_lists.begin(); + + while(map_iter!=map_itend) + { + glDeleteLists(map_iter->second.m_dlist,1); + map_iter++; + } + + g_display_lists.clear(); +} + + +void OGL_displaylist_register_shape(btCollisionShape * shape) +{ + btVector3 aabbMax(btScalar(BT_LARGE_FLOAT),btScalar(BT_LARGE_FLOAT),btScalar(BT_LARGE_FLOAT)); + btVector3 aabbMin(-btScalar(BT_LARGE_FLOAT),-btScalar(BT_LARGE_FLOAT),-btScalar(BT_LARGE_FLOAT)); + GlDisplaylistDrawcallback drawCallback; + TRIMESH_KEY dlist; + + dlist.m_dlist = glGenLists(1); + dlist.m_shape = shape; + + unsigned long key = (unsigned long)shape; + + g_display_lists.insert(TRIMESH_KEY_PAIR(key,dlist)); + + glNewList(dlist.m_dlist,GL_COMPILE); + +// glEnable(GL_CULL_FACE); + + glCullFace(GL_BACK); + + if (shape->isConcave()) + { + btConcaveShape* concaveMesh = (btConcaveShape*) shape; + //todo pass camera, for some culling + concaveMesh->processAllTriangles(&drawCallback,aabbMin,aabbMax); + } + +// glDisable(GL_CULL_FACE); + + glEndList(); +} +#endif //USE_DISPLAY_LISTS + +void GL_ShapeDrawer::drawCoordSystem() { + glBegin(GL_LINES); + glColor3f(1, 0, 0); + glVertex3d(0, 0, 0); + glVertex3d(1, 0, 0); + glColor3f(0, 1, 0); + glVertex3d(0, 0, 0); + glVertex3d(0, 1, 0); + glColor3f(0, 0, 1); + glVertex3d(0, 0, 0); + glVertex3d(0, 0, 1); + glEnd(); + +} + + + + + +class GlDrawcallback : public btTriangleCallback +{ + +public: + + bool m_wireframe; + + GlDrawcallback() + :m_wireframe(false) + { + } + + virtual void processTriangle(btVector3* triangle,int partId, int triangleIndex) + { + + (void)triangleIndex; + (void)partId; + + + if (m_wireframe) + { + glBegin(GL_LINES); + glColor3f(1, 0, 0); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glColor3f(0, 1, 0); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glColor3f(0, 0, 1); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glEnd(); + } else + { + glBegin(GL_TRIANGLES); + //glColor3f(1, 1, 1); + + + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glEnd(); + } + } +}; + +class TriangleGlDrawcallback : public btInternalTriangleIndexCallback +{ +public: + virtual void internalProcessTriangleIndex(btVector3* triangle,int partId,int triangleIndex) + { + (void)triangleIndex; + (void)partId; + + + glBegin(GL_TRIANGLES);//LINES); + glColor3f(1, 0, 0); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glColor3f(0, 1, 0); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glVertex3d(triangle[1].getX(), triangle[1].getY(), triangle[1].getZ()); + glColor3f(0, 0, 1); + glVertex3d(triangle[2].getX(), triangle[2].getY(), triangle[2].getZ()); + glVertex3d(triangle[0].getX(), triangle[0].getY(), triangle[0].getZ()); + glEnd(); + } +}; + + +void GL_ShapeDrawer::drawSphere(btScalar radius, int lats, int longs) +{ + int i, j; + for(i = 0; i <= lats; i++) { + btScalar lat0 = SIMD_PI * (-btScalar(0.5) + (btScalar) (i - 1) / lats); + btScalar z0 = radius*sin(lat0); + btScalar zr0 = radius*cos(lat0); + + btScalar lat1 = SIMD_PI * (-btScalar(0.5) + (btScalar) i / lats); + btScalar z1 = radius*sin(lat1); + btScalar zr1 = radius*cos(lat1); + + glBegin(GL_QUAD_STRIP); + for(j = 0; j <= longs; j++) { + btScalar lng = 2 * SIMD_PI * (btScalar) (j - 1) / longs; + btScalar x = cos(lng); + btScalar y = sin(lng); + glNormal3f(x * zr1, y * zr1, z1); + glVertex3f(x * zr1, y * zr1, z1); + glNormal3f(x * zr0, y * zr0, z0); + glVertex3f(x * zr0, y * zr0, z0); + } + glEnd(); + } +} + +void GL_ShapeDrawer::drawCylinder(float radius,float halfHeight, int upAxis) +{ + + + glPushMatrix(); + switch (upAxis) + { + case 0: + glRotatef(-90.0, 0.0, 1.0, 0.0); + glTranslatef(0.0, 0.0, -halfHeight); + break; + case 1: + glRotatef(-90.0, 1.0, 0.0, 0.0); + glTranslatef(0.0, 0.0, -halfHeight); + break; + case 2: + + glTranslatef(0.0, 0.0, -halfHeight); + break; + default: + { + btAssert(0); + } + + } + + GLUquadricObj *quadObj = gluNewQuadric(); + + //The gluCylinder subroutine draws a cylinder that is oriented along the z axis. + //The base of the cylinder is placed at z = 0; the top of the cylinder is placed at z=height. + //Like a sphere, the cylinder is subdivided around the z axis into slices and along the z axis into stacks. + + gluQuadricDrawStyle(quadObj, (GLenum)GLU_FILL); + gluQuadricNormals(quadObj, (GLenum)GLU_SMOOTH); + + gluDisk(quadObj,0,radius,15, 10); + + gluCylinder(quadObj, radius, radius, 2.f*halfHeight, 15, 10); + glTranslatef(0.0f, 0.0f, 2.f*halfHeight); + glRotatef(-180.0f, 0.0f, 1.0f, 0.0f); + gluDisk(quadObj,0.f,radius,15, 10); + + glPopMatrix(); + gluDeleteQuadric(quadObj); +} + +GL_ShapeDrawer::ShapeCache* GL_ShapeDrawer::cache(btConvexShape* shape) +{ + ShapeCache* sc=(ShapeCache*)shape->getUserPointer(); + if(!sc) + { + sc=new(btAlignedAlloc(sizeof(ShapeCache),16)) ShapeCache(shape); + + m_shapecaches.push_back(sc); + shape->setUserPointer(sc); + + const btConvexPolyhedron* poly = shape->isPolyhedral() ? ((btPolyhedralConvexShape*) shape)->getConvexPolyhedron() : 0; + if (poly) + { + int i; + /* Build edges */ + const int nv= poly->m_vertices.size(); + if (nv) + { + const btVector3* pv=&poly->m_vertices[0]; + btAlignedObjectArray edges; + edges.resize(nv*nv,0); + + int maxIndices = 0; + for (i=0;im_faces.size();i++) + { + maxIndices += poly->m_faces[i].m_indices.size(); + } + sc->m_edges.reserve(maxIndices); + + for (i=0;im_faces.size();i++) + { + int numVerts = poly->m_faces[i].m_indices.size(); + if (numVerts>2) + { + int index0 = poly->m_faces[i].m_indices[0]; + int index1 = poly->m_faces[i].m_indices[1]; + int index2 = poly->m_faces[i].m_indices[2]; + int j = poly->m_faces[i].m_indices.size()-1; + const btVector3 nrm=btCross(pv[index1]-pv[index0],pv[index2]-pv[index0]).normalized(); + + for (int v=0;vm_faces[i].m_indices.size();j=v++) + { + { + const unsigned int a=poly->m_faces[i].m_indices[j]; + const unsigned int b=poly->m_faces[i].m_indices[v]; + int edgeIndex = btMin(a,b)*nv+btMax(a,b); + ShapeCache::Edge*& e=edges[edgeIndex]; + if(!e) + { + sc->m_edges.push_back(ShapeCache::Edge()); + e=&sc->m_edges[sc->m_edges.size()-1]; + e->n[0]=nrm;e->n[1]=-nrm; + e->v[0]=a;e->v[1]=b; + } + else + { + e->n[1]=nrm; + } + } + } + } + } + } + + } else + { + + sc->m_shapehull.buildHull(shape->getMargin()); + + + /* Build edges */ + const int ni=sc->m_shapehull.numIndices(); + const int nv=sc->m_shapehull.numVertices(); + const unsigned int* pi=sc->m_shapehull.getIndexPointer(); + const btVector3* pv=sc->m_shapehull.getVertexPointer(); + btAlignedObjectArray edges; + sc->m_edges.reserve(ni); + edges.resize(nv*nv,0); + for(int i=0;im_edges.push_back(ShapeCache::Edge()); + e=&sc->m_edges[sc->m_edges.size()-1]; + e->n[0]=nrm;e->n[1]=-nrm; + e->v[0]=a;e->v[1]=b; + } + else + { + e->n[1]=nrm; + } + } + } + } + } + return(sc); +} + +void renderSquareA(float x, float y, float z) +{ + glBegin(GL_LINE_LOOP); + glVertex3f(x, y, z); + glVertex3f(x + 10.f, y, z); + glVertex3f(x + 10.f, y + 10.f, z); + glVertex3f(x, y + 10.f, z); + glEnd(); +} + +inline void glDrawVector(const btVector3& v) { glVertex3d(v[0], v[1], v[2]); } + + +void GL_ShapeDrawer::drawOpenGL(btScalar* m, const btCollisionShape* shape, const btVector3& color,int debugMode,const btVector3& worldBoundsMin,const btVector3& worldBoundsMax) +{ + + if (shape->getShapeType() == CUSTOM_CONVEX_SHAPE_TYPE) + { + btVector3 org(m[12], m[13], m[14]); + btVector3 dx(m[0], m[1], m[2]); + btVector3 dy(m[4], m[5], m[6]); +// btVector3 dz(m[8], m[9], m[10]); + const btBoxShape* boxShape = static_cast(shape); + btVector3 halfExtent = boxShape->getHalfExtentsWithMargin(); + dx *= halfExtent[0]; + dy *= halfExtent[1]; +// dz *= halfExtent[2]; + glColor3f(1,1,1); + glDisable(GL_LIGHTING); + glLineWidth(2); + + glBegin(GL_LINE_LOOP); + glDrawVector(org - dx - dy); + glDrawVector(org - dx + dy); + glDrawVector(org + dx + dy); + glDrawVector(org + dx - dy); + glEnd(); + return; + } + else if((shape->getShapeType() == BOX_SHAPE_PROXYTYPE) && (debugMode & btIDebugDraw::DBG_FastWireframe)) + { + btVector3 org(m[12], m[13], m[14]); + btVector3 dx(m[0], m[1], m[2]); + btVector3 dy(m[4], m[5], m[6]); + btVector3 dz(m[8], m[9], m[10]); + const btBoxShape* boxShape = static_cast(shape); + btVector3 halfExtent = boxShape->getHalfExtentsWithMargin(); + dx *= halfExtent[0]; + dy *= halfExtent[1]; + dz *= halfExtent[2]; + glBegin(GL_LINE_LOOP); + glDrawVector(org - dx - dy - dz); + glDrawVector(org + dx - dy - dz); + glDrawVector(org + dx + dy - dz); + glDrawVector(org - dx + dy - dz); + glDrawVector(org - dx + dy + dz); + glDrawVector(org + dx + dy + dz); + glDrawVector(org + dx - dy + dz); + glDrawVector(org - dx - dy + dz); + glEnd(); + glBegin(GL_LINES); + glDrawVector(org + dx - dy - dz); + glDrawVector(org + dx - dy + dz); + glDrawVector(org + dx + dy - dz); + glDrawVector(org + dx + dy + dz); + glDrawVector(org - dx - dy - dz); + glDrawVector(org - dx + dy - dz); + glDrawVector(org - dx - dy + dz); + glDrawVector(org - dx + dy + dz); + glEnd(); + return; + } + + glPushMatrix(); + btglMultMatrix(m); + + + if (shape->getShapeType() == UNIFORM_SCALING_SHAPE_PROXYTYPE) + { + const btUniformScalingShape* scalingShape = static_cast(shape); + const btConvexShape* convexShape = scalingShape->getChildShape(); + float scalingFactor = (float)scalingShape->getUniformScalingFactor(); + { + btScalar tmpScaling[4][4]={{scalingFactor,0,0,0}, + {0,scalingFactor,0,0}, + {0,0,scalingFactor,0}, + {0,0,0,1}}; + + drawOpenGL( (btScalar*)tmpScaling,convexShape,color,debugMode,worldBoundsMin,worldBoundsMax); + } + glPopMatrix(); + return; + } + + if (shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE) + { + const btCompoundShape* compoundShape = static_cast(shape); + for (int i=compoundShape->getNumChildShapes()-1;i>=0;i--) + { + btTransform childTrans = compoundShape->getChildTransform(i); + const btCollisionShape* colShape = compoundShape->getChildShape(i); + btScalar childMat[16]; + childTrans.getOpenGLMatrix(childMat); + drawOpenGL(childMat,colShape,color,debugMode,worldBoundsMin,worldBoundsMax); + } + + } else + { + if(m_textureenabled&&(!m_textureinitialized)) + { + GLubyte* image=new GLubyte[256*256*3]; + for(int y=0;y<256;++y) + { + const int t=y>>4; + GLubyte* pi=image+y*256*3; + for(int x=0;x<256;++x) + { + const int s=x>>4; + const GLubyte b=180; + GLubyte c=b+((s+t&1)&1)*(255-b); + pi[0]=pi[1]=pi[2]=c;pi+=3; + } + } + + glGenTextures(1,(GLuint*)&m_texturehandle); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + gluBuild2DMipmaps(GL_TEXTURE_2D,3,256,256,GL_RGB,GL_UNSIGNED_BYTE,image); + delete[] image; + + + } + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glScalef(0.025f,0.025f,0.025f); + glMatrixMode(GL_MODELVIEW); + + static const GLfloat planex[]={1,0,0,0}; + // static const GLfloat planey[]={0,1,0,0}; + static const GLfloat planez[]={0,0,1,0}; + glTexGenfv(GL_S,GL_OBJECT_PLANE,planex); + glTexGenfv(GL_T,GL_OBJECT_PLANE,planez); + glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR); + glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glEnable(GL_TEXTURE_GEN_R); + m_textureinitialized=true; + + + + + //drawCoordSystem(); + + //glPushMatrix(); + glEnable(GL_COLOR_MATERIAL); + if(m_textureenabled) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + } else + { + glDisable(GL_TEXTURE_2D); + } + + + glColor3f(color.x(),color.y(), color.z()); + + bool useWireframeFallback = true; + + if (!(debugMode & btIDebugDraw::DBG_DrawWireframe)) + { + ///you can comment out any of the specific cases, and use the default + + ///the benefit of 'default' is that it approximates the actual collision shape including collision margin + //int shapetype=m_textureenabled?MAX_BROADPHASE_COLLISION_TYPES:shape->getShapeType(); + int shapetype=shape->getShapeType(); + switch (shapetype) + { + + case SPHERE_SHAPE_PROXYTYPE: + { + const btSphereShape* sphereShape = static_cast(shape); + float radius = sphereShape->getMargin();//radius doesn't include the margin, so draw with margin + drawSphere(radius,10,10); + useWireframeFallback = false; + break; + } + + case BOX_SHAPE_PROXYTYPE: + { + const btBoxShape* boxShape = static_cast(shape); + btVector3 halfExtent = boxShape->getHalfExtentsWithMargin(); + + static int indices[36] = { + 0,1,2, + 3,2,1, + 4,0,6, + 6,0,2, + 5,1,4, + 4,1,0, + 7,3,1, + 7,1,5, + 5,4,7, + 7,4,6, + 7,2,3, + 7,6,2}; + + btVector3 vertices[8]={ + btVector3(halfExtent[0],halfExtent[1],halfExtent[2]), + btVector3(-halfExtent[0],halfExtent[1],halfExtent[2]), + btVector3(halfExtent[0],-halfExtent[1],halfExtent[2]), + btVector3(-halfExtent[0],-halfExtent[1],halfExtent[2]), + btVector3(halfExtent[0],halfExtent[1],-halfExtent[2]), + btVector3(-halfExtent[0],halfExtent[1],-halfExtent[2]), + btVector3(halfExtent[0],-halfExtent[1],-halfExtent[2]), + btVector3(-halfExtent[0],-halfExtent[1],-halfExtent[2])}; +#if 1 + glBegin (GL_TRIANGLES); + int si=36; + for (int i=0;i(shape); + int upIndex = coneShape->getConeUpIndex(); + float radius = coneShape->getRadius();//+coneShape->getMargin(); + float height = coneShape->getHeight();//+coneShape->getMargin(); + switch (upIndex) + { + case 0: + glRotatef(90.0, 0.0, 1.0, 0.0); + break; + case 1: + glRotatef(-90.0, 1.0, 0.0, 0.0); + break; + case 2: + break; + default: + { + } + }; + + glTranslatef(0.0, 0.0, -0.5*height); + glutSolidCone(radius,height,10,10); + useWireframeFallback = false; + break; + + } +#endif + + case STATIC_PLANE_PROXYTYPE: + { + const btStaticPlaneShape* staticPlaneShape = static_cast(shape); + btScalar planeConst = staticPlaneShape->getPlaneConstant(); + const btVector3& planeNormal = staticPlaneShape->getPlaneNormal(); + btVector3 planeOrigin = planeNormal * planeConst; + btVector3 vec0,vec1; + btPlaneSpace1(planeNormal,vec0,vec1); + btScalar vecLen = 100.f; + btVector3 pt0 = planeOrigin + vec0*vecLen; + btVector3 pt1 = planeOrigin - vec0*vecLen; + btVector3 pt2 = planeOrigin + vec1*vecLen; + btVector3 pt3 = planeOrigin - vec1*vecLen; + glBegin(GL_LINES); + glVertex3f(pt0.getX(),pt0.getY(),pt0.getZ()); + glVertex3f(pt1.getX(),pt1.getY(),pt1.getZ()); + glVertex3f(pt2.getX(),pt2.getY(),pt2.getZ()); + glVertex3f(pt3.getX(),pt3.getY(),pt3.getZ()); + glEnd(); + + + break; + + } + +/* + case CYLINDER_SHAPE_PROXYTYPE: + { + const btCylinderShape* cylinder = static_cast(shape); + int upAxis = cylinder->getUpAxis(); + + + float radius = cylinder->getRadius(); + float halfHeight = cylinder->getHalfExtentsWithMargin()[upAxis]; + + drawCylinder(radius,halfHeight,upAxis); + + break; + } +*/ + + case MULTI_SPHERE_SHAPE_PROXYTYPE: + { + const btMultiSphereShape* multiSphereShape = static_cast(shape); + + btTransform childTransform; + childTransform.setIdentity(); + + + for (int i = multiSphereShape->getSphereCount()-1; i>=0;i--) + { + btSphereShape sc(multiSphereShape->getSphereRadius(i)); + childTransform.setOrigin(multiSphereShape->getSpherePosition(i)); + btScalar childMat[16]; + childTransform.getOpenGLMatrix(childMat); + drawOpenGL(childMat,&sc,color,debugMode,worldBoundsMin,worldBoundsMax); + } + + break; + } + + default: + { + if (shape->isConvex()) + { + const btConvexPolyhedron* poly = shape->isPolyhedral() ? ((btPolyhedralConvexShape*) shape)->getConvexPolyhedron() : 0; + if (poly) + { + int i; + glBegin (GL_TRIANGLES); + for (i=0;im_faces.size();i++) + { + btVector3 centroid(0,0,0); + int numVerts = poly->m_faces[i].m_indices.size(); + if (numVerts>2) + { + btVector3 v1 = poly->m_vertices[poly->m_faces[i].m_indices[0]]; + for (int v=0;vm_faces[i].m_indices.size()-2;v++) + { + + btVector3 v2 = poly->m_vertices[poly->m_faces[i].m_indices[v+1]]; + btVector3 v3 = poly->m_vertices[poly->m_faces[i].m_indices[v+2]]; + btVector3 normal = (v3-v1).cross(v2-v1); + normal.normalize (); + glNormal3f(normal.getX(),normal.getY(),normal.getZ()); + glVertex3f (v1.x(), v1.y(), v1.z()); + glVertex3f (v2.x(), v2.y(), v2.z()); + glVertex3f (v3.x(), v3.y(), v3.z()); + } + } + } + glEnd (); + } else + { + ShapeCache* sc=cache((btConvexShape*)shape); + //glutSolidCube(1.0); + btShapeHull* hull = &sc->m_shapehull/*(btShapeHull*)shape->getUserPointer()*/; + + if (hull && hull->numTriangles () > 0) + { + int index = 0; + const unsigned int* idx = hull->getIndexPointer(); + const btVector3* vtx = hull->getVertexPointer(); + + glBegin (GL_TRIANGLES); + + for (int i = 0; i < hull->numTriangles (); i++) + { + int i1 = index++; + int i2 = index++; + int i3 = index++; + btAssert(i1 < hull->numIndices () && + i2 < hull->numIndices () && + i3 < hull->numIndices ()); + + int index1 = idx[i1]; + int index2 = idx[i2]; + int index3 = idx[i3]; + btAssert(index1 < hull->numVertices () && + index2 < hull->numVertices () && + index3 < hull->numVertices ()); + + btVector3 v1 = vtx[index1]; + btVector3 v2 = vtx[index2]; + btVector3 v3 = vtx[index3]; + btVector3 normal = (v3-v1).cross(v2-v1); + normal.normalize (); + glNormal3f(normal.getX(),normal.getY(),normal.getZ()); + glVertex3f (v1.x(), v1.y(), v1.z()); + glVertex3f (v2.x(), v2.y(), v2.z()); + glVertex3f (v3.x(), v3.y(), v3.z()); + + } + glEnd (); + + } + } + } + } + } + + } + + + glNormal3f(0,1,0); + + + /// for polyhedral shapes + if (debugMode==btIDebugDraw::DBG_DrawFeaturesText && (shape->isPolyhedral())) + { + btPolyhedralConvexShape* polyshape = (btPolyhedralConvexShape*) shape; + + { + + glColor3f(1.f, 1.f, 1.f); + int i; + for (i=0;igetNumVertices();i++) + { + btVector3 vtx; + polyshape->getVertex(i,vtx); + char buf[12]; + sprintf(buf," %d",i); + //btDrawString(BMF_GetFont(BMF_kHelvetica10),buf); + } + + for (i=0;igetNumPlanes();i++) + { + btVector3 normal; + btVector3 vtx; + polyshape->getPlane(normal,vtx,i); + //btScalar d = vtx.dot(normal); + + //char buf[12]; + //sprintf(buf," plane %d",i); + //btDrawString(BMF_GetFont(BMF_kHelvetica10),buf); + + } + } + + } + + +#ifdef USE_DISPLAY_LISTS + + if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE||shape->getShapeType() == GIMPACT_SHAPE_PROXYTYPE) + { + GLuint dlist = OGL_get_displaylist_for_shape((btCollisionShape * )shape); + if (dlist) + { + glCallList(dlist); + } + else + { +#else + if (shape->isConcave() && !shape->isInfinite()) + { + btConcaveShape* concaveMesh = (btConcaveShape*) shape; + + GlDrawcallback drawCallback; + drawCallback.m_wireframe = (debugMode & btIDebugDraw::DBG_DrawWireframe)!=0; + + concaveMesh->processAllTriangles(&drawCallback,worldBoundsMin,worldBoundsMax); + + } +#endif + +#ifdef USE_DISPLAY_LISTS + } +} +#endif + + + + + + } + glPopMatrix(); + +} + +// +void GL_ShapeDrawer::drawShadow(btScalar* m,const btVector3& extrusion,const btCollisionShape* shape,const btVector3& worldBoundsMin,const btVector3& worldBoundsMax) +{ + glPushMatrix(); + btglMultMatrix(m); + if(shape->getShapeType() == UNIFORM_SCALING_SHAPE_PROXYTYPE) + { + const btUniformScalingShape* scalingShape = static_cast(shape); + const btConvexShape* convexShape = scalingShape->getChildShape(); + float scalingFactor = (float)scalingShape->getUniformScalingFactor(); + btScalar tmpScaling[4][4]={ {scalingFactor,0,0,0}, + {0,scalingFactor,0,0}, + {0,0,scalingFactor,0}, + {0,0,0,1}}; + drawShadow((btScalar*)tmpScaling,extrusion,convexShape,worldBoundsMin,worldBoundsMax); + glPopMatrix(); + return; + } + else if(shape->getShapeType()==COMPOUND_SHAPE_PROXYTYPE) + { + const btCompoundShape* compoundShape = static_cast(shape); + for (int i=compoundShape->getNumChildShapes()-1;i>=0;i--) + { + btTransform childTrans = compoundShape->getChildTransform(i); + const btCollisionShape* colShape = compoundShape->getChildShape(i); + btScalar childMat[16]; + childTrans.getOpenGLMatrix(childMat); + drawShadow(childMat,extrusion*childTrans.getBasis(),colShape,worldBoundsMin,worldBoundsMax); + } + } + else + { + // bool useWireframeFallback = true; + if (shape->isConvex()) + { + + const btConvexPolyhedron* poly = shape->isPolyhedral() ? ((btPolyhedralConvexShape*) shape)->getConvexPolyhedron() : 0; + ShapeCache* sc=cache((btConvexShape*)shape); + btShapeHull* hull =&sc->m_shapehull; + const btVector3* vertexPointer = 0; + + vertexPointer = (poly && poly->m_vertices.size())? &poly->m_vertices[0] : 0; + if (!vertexPointer) + vertexPointer = hull->numVertices() ? hull->getVertexPointer():0; + + if (vertexPointer) + { + glBegin(GL_QUADS); + for(int i=0;im_edges.size();++i) + { + const btScalar d=btDot(sc->m_edges[i].n[0],extrusion); + if((d*btDot(sc->m_edges[i].n[1],extrusion))<0) + { + const int q= d<0?1:0; + const btVector3& a= vertexPointer[sc->m_edges[i].v[q]]; + const btVector3& b= vertexPointer[sc->m_edges[i].v[1-q]]; + glVertex3f(a[0],a[1],a[2]); + glVertex3f(b[0],b[1],b[2]); + glVertex3f(b[0]+extrusion[0],b[1]+extrusion[1],b[2]+extrusion[2]); + glVertex3f(a[0]+extrusion[0],a[1]+extrusion[1],a[2]+extrusion[2]); + } + } + glEnd(); + } + } + } + + + if (shape->isConcave())//>getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE||shape->getShapeType() == GIMPACT_SHAPE_PROXYTYPE) + // if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + { + btConcaveShape* concaveMesh = (btConcaveShape*) shape; + + GlDrawcallback drawCallback; + drawCallback.m_wireframe = false; + + concaveMesh->processAllTriangles(&drawCallback,worldBoundsMin,worldBoundsMax); + + } + glPopMatrix(); + +} + +// +GL_ShapeDrawer::GL_ShapeDrawer() +{ + m_texturehandle = 0; + m_textureenabled = false; + m_textureinitialized = false; +} + +GL_ShapeDrawer::~GL_ShapeDrawer() +{ + int i; + for (i=0;i~ShapeCache(); + btAlignedFree(m_shapecaches[i]); + } + m_shapecaches.clear(); + if(m_textureinitialized) + { + glDeleteTextures(1,(const GLuint*) &m_texturehandle); + } +} + + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.h new file mode 100644 index 000000000..65bf29de4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_ShapeDrawer.h @@ -0,0 +1,70 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +#ifndef GL_SHAPE_DRAWER_H +#define GL_SHAPE_DRAWER_H + +class btCollisionShape; +class btShapeHull; +#include "LinearMath/btAlignedObjectArray.h" +#include "LinearMath/btVector3.h" + +#include "BulletCollision/CollisionShapes/btShapeHull.h" + +/// OpenGL shape drawing +class GL_ShapeDrawer +{ +protected: + struct ShapeCache + { + struct Edge { btVector3 n[2];int v[2]; }; + ShapeCache(btConvexShape* s) : m_shapehull(s) {} + btShapeHull m_shapehull; + btAlignedObjectArray m_edges; + }; + //clean-up memory of dynamically created shape hulls + btAlignedObjectArray m_shapecaches; + unsigned int m_texturehandle; + bool m_textureenabled; + bool m_textureinitialized; + + + ShapeCache* cache(btConvexShape*); + +public: + GL_ShapeDrawer(); + + virtual ~GL_ShapeDrawer(); + + ///drawOpenGL might allocate temporary memoty, stores pointer in shape userpointer + virtual void drawOpenGL(btScalar* m, const btCollisionShape* shape, const btVector3& color,int debugMode,const btVector3& worldBoundsMin,const btVector3& worldBoundsMax); + virtual void drawShadow(btScalar* m, const btVector3& extrusion,const btCollisionShape* shape,const btVector3& worldBoundsMin,const btVector3& worldBoundsMax); + + bool enableTexture(bool enable) { bool p=m_textureenabled;m_textureenabled=enable;return(p); } + bool hasTextureEnabled() const + { + return m_textureenabled; + } + + static void drawCylinder(float radius,float halfHeight, int upAxis); + void drawSphere(btScalar r, int lats, int longs); + static void drawCoordSystem(); + +}; + +void OGL_displaylist_register_shape(btCollisionShape * shape); +void OGL_displaylist_clean(); + +#endif //GL_SHAPE_DRAWER_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.cpp new file mode 100644 index 000000000..b364a1b98 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.cpp @@ -0,0 +1,76 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +#include "GL_Simplex1to4.h" +#include "BulletCollision/NarrowPhaseCollision/btSimplexSolverInterface.h" +#include "GL_ShapeDrawer.h" +#ifdef _WIN32 +#include +#endif + +//think different +#if defined(__APPLE__) && !defined (VMDMESA) +#include +#include +#else +#include +#endif +#include "GlutStuff.h" +#include "LinearMath/btTransform.h" + +GL_Simplex1to4::GL_Simplex1to4() +:m_simplexSolver(0) +{ +} + +/// +/// Debugging method calcClosest calculates the closest point to the origin, using m_simplexSolver +/// +void GL_Simplex1to4::calcClosest(btScalar* m) +{ + btTransform tr; + tr.setFromOpenGLMatrix(m); + + + + GL_ShapeDrawer::drawCoordSystem(); + + if (m_simplexSolver) + { + m_simplexSolver->reset(); + bool res; + + btVector3 v; + + for (int i=0;iaddVertex(v,v,btVector3(0.f,0.f,0.f)); + res = m_simplexSolver->closest(v); + } + + //draw v? + glDisable(GL_LIGHTING); + glBegin(GL_LINES); + btglColor3(1.f, 0.f, 0.f); + btglVertex3(0.f, 0.f, 0.f); + btglVertex3(v.x(),v.y(),v.z()); + glEnd(); + + glEnable(GL_LIGHTING); + + + } + +} diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.h new file mode 100644 index 000000000..c75e3573b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GL_Simplex1to4.h @@ -0,0 +1,40 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +#ifndef GL_SIMPLEX_1TO4_H +#define GL_SIMPLEX_1TO4_H + +#include "BulletCollision/CollisionShapes/btTetrahedronShape.h" + +#include "BulletCollision/NarrowPhaseCollision/btSimplexSolverInterface.h" + +///GL_Simplex1to4 is a class to debug a Simplex Solver with 1 to 4 points. +///Can be used by GJK. +class GL_Simplex1to4 : public btBU_Simplex1to4 +{ + btSimplexSolverInterface* m_simplexSolver; + + public: + + GL_Simplex1to4(); + + void calcClosest(btScalar* m); + + void setSimplexSolver(btSimplexSolverInterface* simplexSolver) { + m_simplexSolver = simplexSolver; + } + +}; + +#endif //GL_SIMPLEX_1TO4_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.cpp new file mode 100644 index 000000000..0ceaede76 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.cpp @@ -0,0 +1,87 @@ + +#ifndef _WINDOWS + +#include "GlutDemoApplication.h" + +#include "GlutStuff.h" + +#include "BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h" +#include "BulletDynamics/Dynamics/btRigidBody.h" + +void GlutDemoApplication::updateModifierKeys() +{ + m_modifierKeys = 0; + if (glutGetModifiers() & GLUT_ACTIVE_ALT) + m_modifierKeys |= BT_ACTIVE_ALT; + + if (glutGetModifiers() & GLUT_ACTIVE_CTRL) + m_modifierKeys |= BT_ACTIVE_CTRL; + + if (glutGetModifiers() & GLUT_ACTIVE_SHIFT) + m_modifierKeys |= BT_ACTIVE_SHIFT; +} + +void GlutDemoApplication::specialKeyboard(int key, int x, int y) +{ + (void)x; + (void)y; + + switch (key) + { + case GLUT_KEY_F1: + { + + break; + } + + case GLUT_KEY_F2: + { + + break; + } + + + case GLUT_KEY_END: + { + int numObj = getDynamicsWorld()->getNumCollisionObjects(); + if (numObj) + { + btCollisionObject* obj = getDynamicsWorld()->getCollisionObjectArray()[numObj-1]; + + getDynamicsWorld()->removeCollisionObject(obj); + btRigidBody* body = btRigidBody::upcast(obj); + if (body && body->getMotionState()) + { + delete body->getMotionState(); + } + delete obj; + + + } + break; + } + case GLUT_KEY_LEFT : stepLeft(); break; + case GLUT_KEY_RIGHT : stepRight(); break; + case GLUT_KEY_UP : stepFront(); break; + case GLUT_KEY_DOWN : stepBack(); break; + case GLUT_KEY_PAGE_UP : zoomIn(); break; + case GLUT_KEY_PAGE_DOWN : zoomOut(); break; + case GLUT_KEY_HOME : toggleIdle(); break; + default: + // std::cout << "unused (special) key : " << key << std::endl; + break; + } + + glutPostRedisplay(); + +} + +void GlutDemoApplication::swapBuffers() +{ + glutSwapBuffers(); + +} + +#endif //_WINDOWS + + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.h new file mode 100644 index 000000000..e2727a777 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutDemoApplication.h @@ -0,0 +1,34 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef GLUT_DEMO_APPLICATION_H +#define GLUT_DEMO_APPLICATION_H + +#include "DemoApplication.h" + +class GlutDemoApplication : public DemoApplication +{ +public: + + void specialKeyboard(int key, int x, int y); + + virtual void swapBuffers(); + + virtual void updateModifierKeys(); + +}; +#endif //GLUT_DEMO_APPLICATION_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.cpp new file mode 100644 index 000000000..92872a130 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.cpp @@ -0,0 +1,119 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _WINDOWS + +#include "DemoApplication.h" + +//glut is C code, this global gDemoApplication links glut to the C++ demo +static DemoApplication* gDemoApplication = 0; + + +#include "GlutStuff.h" + +static void glutKeyboardCallback(unsigned char key, int x, int y) +{ + gDemoApplication->keyboardCallback(key,x,y); +} + +static void glutKeyboardUpCallback(unsigned char key, int x, int y) +{ + gDemoApplication->keyboardUpCallback(key,x,y); +} + +static void glutSpecialKeyboardCallback(int key, int x, int y) +{ + gDemoApplication->specialKeyboard(key,x,y); +} + +static void glutSpecialKeyboardUpCallback(int key, int x, int y) +{ + gDemoApplication->specialKeyboardUp(key,x,y); +} + + +static void glutReshapeCallback(int w, int h) +{ + gDemoApplication->reshape(w,h); +} + +static void glutMoveAndDisplayCallback() +{ + gDemoApplication->moveAndDisplay(); +} + +static void glutMouseFuncCallback(int button, int state, int x, int y) +{ + gDemoApplication->mouseFunc(button,state,x,y); +} + + +static void glutMotionFuncCallback(int x,int y) +{ + gDemoApplication->mouseMotionFunc(x,y); +} + + +static void glutDisplayCallback(void) +{ + gDemoApplication->displayCallback(); +} + + +int glutmain(int argc, char **argv,int width,int height,const char* title,DemoApplication* demoApp) { + + gDemoApplication = demoApp; + + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL); + glutInitWindowPosition(0, 0); + glutInitWindowSize(width, height); + glutCreateWindow(title); +#ifdef BT_USE_FREEGLUT + glutSetOption (GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS); +#endif + + gDemoApplication->myinit(); + + glutKeyboardFunc(glutKeyboardCallback); + glutKeyboardUpFunc(glutKeyboardUpCallback); + glutSpecialFunc(glutSpecialKeyboardCallback); + glutSpecialUpFunc(glutSpecialKeyboardUpCallback); + + glutReshapeFunc(glutReshapeCallback); + //createMenu(); + glutIdleFunc(glutMoveAndDisplayCallback); + glutMouseFunc(glutMouseFuncCallback); + glutPassiveMotionFunc(glutMotionFuncCallback); + glutMotionFunc(glutMotionFuncCallback); + glutDisplayFunc( glutDisplayCallback ); + + glutMoveAndDisplayCallback(); + +//enable vsync to avoid tearing on Apple (todo: for Windows) + +#if defined(__APPLE__) && !defined (VMDMESA) +int swap_interval = 1; +CGLContextObj cgl_context = CGLGetCurrentContext(); +CGLSetParameter(cgl_context, kCGLCPSwapInterval, &swap_interval); +#endif + + + + return 0; +} + + +#endif //_WINDOWS diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.h new file mode 100644 index 000000000..5891e769d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/GlutStuff.h @@ -0,0 +1,86 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2012 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +#ifndef GLUT_STUFF_H +#define GLUT_STUFF_H + +#ifdef _WIN32//for glut.h +#include +#endif + +//think different +#if defined(__APPLE__) && !defined (VMDMESA) +#include +#include +#include +#include +#else + + +#ifdef _WINDOWS +#include +#include +#include +#else +#include +#include +#endif //_WINDOWS +#endif //APPLE + +#ifdef _WINDOWS +#define BT_ACTIVE_ALT VK_LMENU + +#else +#define BT_KEY_K 'k' +#define BT_KEY_LEFT GLUT_KEY_LEFT +#define BT_KEY_RIGHT GLUT_KEY_RIGHT +#define BT_KEY_UP GLUT_KEY_UP +#define BT_KEY_DOWN GLUT_KEY_DOWN +#define BT_KEY_F1 GLUT_KEY_F1 +#define BT_KEY_F2 GLUT_KEY_F2 +#define BT_KEY_F3 GLUT_KEY_F3 +#define BT_KEY_F4 GLUT_KEY_F4 +#define BT_KEY_F5 GLUT_KEY_F5 +#define BT_KEY_PAGEUP GLUT_KEY_PAGE_UP +#define BT_KEY_PAGEDOWN GLUT_KEY_PAGE_DOWN +#define BT_KEY_END GLUT_KEY_END +#define BT_KEY_HOME GLUT_KEY_HOME +#define BT_ACTIVE_ALT GLUT_ACTIVE_ALT +#define BT_ACTIVE_CTRL GLUT_ACTIVE_ALT +#define BT_ACTIVE_SHIFT GLUT_ACTIVE_SHIFT +#endif + +#if BT_USE_FREEGLUT +#include "GL/freeglut_ext.h" //to be able to return from glutMainLoop() +#endif + + + +class DemoApplication; + +int glutmain(int argc, char **argv,int width,int height,const char* title,DemoApplication* demoApp); + +#if defined(BT_USE_DOUBLE_PRECISION) +#define btglLoadMatrix glLoadMatrixd +#define btglMultMatrix glMultMatrixd +#define btglColor3 glColor3d +#define btglVertex3 glVertex3d +#else +#define btglLoadMatrix glLoadMatrixf +#define btglMultMatrix glMultMatrixf +#define btglColor3 glColor3f +#define btglVertex3 glVertex3d +#endif + +#endif //GLUT_STUFF_H diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.cpp new file mode 100644 index 000000000..2c8b88b82 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.cpp @@ -0,0 +1,86 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "RenderTexture.h" +#include + + +renderTexture::renderTexture(int width,int height) +:m_height(height),m_width(width) +{ + m_buffer = new unsigned char[m_width*m_height*4]; + + //clear screen + memset(m_buffer,0,m_width*m_height*4); + + //clear screen version 2 + for (int x=0;x>=1; + y++; + } + x++; + } + //xx+=16; + xx+=10; + } +} + +renderTexture::~renderTexture() +{ + delete [] m_buffer; +} + + + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.h new file mode 100644 index 000000000..1aee51d79 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/RenderTexture.h @@ -0,0 +1,73 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef RENDER_TEXTURE_H +#define RENDER_TEXTURE_H + +#include "LinearMath/btVector3.h" +#include "GLDebugFont.h" + +/// +///renderTexture provides a software-render context (setpixel/printf) +/// +class renderTexture +{ + int m_height; + int m_width; + unsigned char* m_buffer; + +public: + + renderTexture(int width,int height); + ~renderTexture(); + + ///rgba input is in range [0..1] for each component + inline void setPixel(int x,int y,const btVector4& rgba) + { + unsigned char* pixel = &m_buffer[ (x+y*m_width) * 4]; + + pixel[0] = (unsigned char)(255.*rgba.getX()); + pixel[1] = (unsigned char)(255.*rgba.getY()); + pixel[2] = (unsigned char)(255.*rgba.getZ()); + pixel[3] = (unsigned char)(255.*rgba.getW()); + } + + inline void addPixel(int x,int y,const btVector4& rgba) + { + unsigned char* pixel = &m_buffer[ (x+y*m_width) * 4]; + pixel[0] = (unsigned char)btMin(btScalar(255.f),((btScalar)pixel[0] + btScalar(255.f)*rgba.getX())); + pixel[1] = (unsigned char)btMin(btScalar(255.f),((btScalar)pixel[1] + btScalar(255.f)*rgba.getY())); + pixel[2] = (unsigned char)btMin(btScalar(255.f),((btScalar)pixel[2] + btScalar(255.f)*rgba.getZ())); +// pixel[3] = (unsigned char)btMin(btScalar(255.f),((btScalar)pixel[3] + btScalar(255.f)*rgba.getW())); + } + + inline btVector4 getPixel(int x,int y) + { + unsigned char* pixel = &m_buffer[ (x+y*m_width) * 4]; + return btVector4(pixel[0]*1.f/255.f, + pixel[1]*1.f/255.f, + pixel[2]*1.f/255.f, + pixel[3]*1.f/255.f); + } + + const unsigned char* getBuffer() const { return m_buffer;} + int getWidth() const { return m_width;} + int getHeight() const { return m_height;} + void grapicalPrintf(char* str, void* fontData, int startx = 0,int starty=0); + +}; + +#endif //RENDER_TEXTURE_H + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32AppMain.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32AppMain.cpp new file mode 100644 index 000000000..84f48a316 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32AppMain.cpp @@ -0,0 +1,405 @@ +#ifdef _WINDOWS +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2010 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#include +#include + + +#include "DemoApplication.h" + +#include "GLDebugDrawer.h" +#include "GLDebugFont.h" + +#include "BulletDynamics/Dynamics/btDynamicsWorld.h" + +/// This Win32AppMain is shared code between all demos. +/// The actual demo, derived from DemoApplication is created using 'createDemo', in a separate .cpp file +DemoApplication* gDemoApplication = 0; +DemoApplication* createDemo(); + + +// Function Declarations + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +void EnableOpenGL(HWND hWnd, HDC * hDC, HGLRC * hRC); +void DisableOpenGL(HWND hWnd, HDC hDC, HGLRC hRC); +static bool sOpenGLInitialized = false; +static int sWidth = 0; +static int sHeight =0; +static int quitRequest = 0; + +// WinMain + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int iCmdShow) +{ + WNDCLASS wc; + HWND hWnd; + HDC hDC; + HGLRC hRC; + MSG msg; + BOOL quit = FALSE; + float theta = 0.0f; + + gDemoApplication = createDemo(); + + + // register window class + wc.style = CS_OWNDC; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); + wc.hCursor = LoadCursor( NULL, IDC_ARROW ); + wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH ); + wc.lpszMenuName = NULL; + wc.lpszClassName = "BulletPhysics"; + RegisterClass( &wc ); + + // create main window + hWnd = CreateWindow( + "BulletPhysics", "Bullet Physics Sample. http://bulletphysics.org", + WS_CAPTION | WS_VISIBLE | WS_OVERLAPPEDWINDOW, +// 0, 0, 640, 480, + 0, 0, 1024, 768, + NULL, NULL, hInstance, NULL ); + + // enable OpenGL for the window + EnableOpenGL( hWnd, &hDC, &hRC ); + + + GLDebugDrawer debugDraw; + gDemoApplication->myinit(); + //gDemoApplication->reshape(1024, 768); + gDemoApplication->initPhysics(); + if (gDemoApplication->getDynamicsWorld()) + gDemoApplication->getDynamicsWorld()->setDebugDrawer(&debugDraw); + + gDemoApplication->reshape(sWidth,sHeight); + + // program main loop + while ( !quit ) + { + + // check for messages + if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + + // handle or dispatch messages + if ( msg.message == WM_QUIT ) + { + quit = TRUE; + } + else + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + +// gDemoApplication->displayCallback(); + + + }; + + // OpenGL animation code goes here + + glClearColor( .7f, 0.7f, 0.7f, 1.f ); + + gDemoApplication->moveAndDisplay(); + + + SwapBuffers( hDC ); + + theta += 1.0f; + + + } + + + + // shutdown OpenGL + DisableOpenGL( hWnd, hDC, hRC ); + + // destroy the window explicitly + DestroyWindow( hWnd ); + + delete gDemoApplication; + + return msg.wParam; + +} + +// Window Procedure + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + + + + switch (message) + { + + case WM_SYSKEYDOWN: + { + if (lParam & 1<<29) + { + gDemoApplication->m_modifierKeys = VK_LMENU; + } + break; + } + case WM_SYSKEYUP: + { + if (lParam & 1<<29) + { + gDemoApplication->m_modifierKeys = VK_LMENU; + } else + { + gDemoApplication->m_modifierKeys = 0; + } + + break; + } + + + case WM_SIZE: // Size Action Has Taken Place + + switch (wParam) // Evaluate Size Action + { + case SIZE_MINIMIZED: // Was Window Minimized? + return 0; // Return + + case SIZE_MAXIMIZED: // Was Window Maximized? + sWidth = LOWORD (lParam); + sHeight = HIWORD (lParam); + if (sOpenGLInitialized) + { + gDemoApplication->reshape(sWidth,sHeight); + } + return 0; // Return + + case SIZE_RESTORED: // Was Window Restored? + sWidth = LOWORD (lParam); + sHeight = HIWORD (lParam); + if (sOpenGLInitialized) + { + gDemoApplication->reshape(sWidth,sHeight); + } + return 0; // Return + } + break; + + case WM_CREATE: + return 0; + + case WM_MBUTTONUP: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseFunc(1,1,xPos,yPos); + break; + } + case WM_MBUTTONDOWN: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseFunc(1,0,xPos,yPos); + break; + } + + case WM_LBUTTONUP: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseFunc(0,1,xPos,yPos); + break; + } + case 0x020A://WM_MOUSEWHEEL: + { + + int zDelta = (short)HIWORD(wParam); + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + if (zDelta>0) + gDemoApplication->zoomIn(); + else + gDemoApplication->zoomOut(); + break; + } + + case WM_MOUSEMOVE: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseMotionFunc(xPos,yPos); + break; + } + case WM_RBUTTONUP: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseFunc(2,1,xPos,yPos); + break; + } + case WM_RBUTTONDOWN: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseFunc(2,0,xPos,yPos); + break; + } + case WM_LBUTTONDOWN: + { + int xPos = LOWORD(lParam); + int yPos = HIWORD(lParam); + gDemoApplication->mouseFunc(0,0,xPos,yPos); + break; + } +/*#define WM_LBUTTONUP 0x0202 +#define WM_LBUTTONDBLCLK 0x0203 +#define WM_RBUTTONDOWN 0x0204 +#define WM_RBUTTONUP 0x0205 +#define WM_RBUTTONDBLCLK 0x0206 +#define WM_MBUTTONDOWN 0x0207 +#define WM_MBUTTONUP 0x0208 +#define WM_MBUTTONDBLCLK 0x0209 +*/ + + + + case WM_CLOSE: + PostQuitMessage( 0 ); + return 0; + + case WM_DESTROY: + return 0; + + case WM_KEYUP: + switch ( wParam ) + { + + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + { + if (gDemoApplication) + gDemoApplication->specialKeyboardUp(wParam,0,0); + return 0; + } + default: + { + gDemoApplication->keyboardUpCallback(tolower(wParam),0,0); + } + return DefWindowProc( hWnd, message, wParam, lParam ); + } + + case WM_KEYDOWN: + printf("bla\n"); + switch ( wParam ) + { + case VK_CONTROL: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + { + if (gDemoApplication) + gDemoApplication->specialKeyboard(wParam,0,0); + break; + } + + case ' ': + { + if (gDemoApplication) + gDemoApplication->clientResetScene(); + break; + } + case 'Q': + case VK_ESCAPE: + { + quitRequest = 1; + PostQuitMessage(0); + } + return 0; + + } + return 0; + + case WM_CHAR: + if (!quitRequest) + gDemoApplication->keyboardCallback(wParam,0,0); + break; + + default: + return DefWindowProc( hWnd, message, wParam, lParam ); + + } + return 0; +} + +// Enable OpenGL + +void EnableOpenGL(HWND hWnd, HDC * hDC, HGLRC * hRC) +{ + PIXELFORMATDESCRIPTOR pfd; + int format; + + // get the device context (DC) + *hDC = GetDC( hWnd ); + + // set the pixel format for the DC + ZeroMemory( &pfd, sizeof( pfd ) ); + pfd.nSize = sizeof( pfd ); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + pfd.cDepthBits = 16; + pfd.cStencilBits = 1; + pfd.iLayerType = PFD_MAIN_PLANE; + format = ChoosePixelFormat( *hDC, &pfd ); + SetPixelFormat( *hDC, format, &pfd ); + + // create and enable the render context (RC) + *hRC = wglCreateContext( *hDC ); + wglMakeCurrent( *hDC, *hRC ); + sOpenGLInitialized = true; + + +} + +// Disable OpenGL + +void DisableOpenGL(HWND hWnd, HDC hDC, HGLRC hRC) +{ + sOpenGLInitialized = false; + + wglMakeCurrent( NULL, NULL ); + wglDeleteContext( hRC ); + ReleaseDC( hWnd, hDC ); +} + +#endif //_WINDOWS diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.cpp b/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.cpp new file mode 100644 index 000000000..f959cbf4f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.cpp @@ -0,0 +1,79 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifdef _WINDOWS + +#include "Win32DemoApplication.h" + + + + +#if 0 +void Win32DemoApplication::renderme() +{ +} +void Win32DemoApplication::setTexturing(bool useTexture) +{ +} + +void Win32DemoApplication::setShadows(bool useShadows) +{ +} + +void Win32DemoApplication::setCameraDistance(float camDist) +{ +} +void Win32DemoApplication::clientResetScene() +{ + +} +#endif + +void Win32DemoApplication::updateModifierKeys() +{ + //not yet +} + + + +void Win32DemoApplication::specialKeyboard(int key, int x, int y) +{ + (void)x; + (void)y; + + switch (key) + { + case VK_LEFT : stepLeft(); break; + case VK_RIGHT : stepRight(); break; + case VK_UP : stepFront(); break; + case VK_DOWN : stepBack(); break; + +// case GLUT_KEY_PAGE_UP : zoomIn(); break; +// case GLUT_KEY_PAGE_DOWN : zoomOut(); break; +// case GLUT_KEY_HOME : toggleIdle(); break; + + default: + // std::cout << "unused (special) key : " << key << std::endl; + break; + } + +} + +void Win32DemoApplication::swapBuffers() +{ +} + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.h b/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.h new file mode 100644 index 000000000..0c2a1ee49 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/Win32DemoApplication.h @@ -0,0 +1,40 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef WIN32_DEMO_APPLICATION_H +#define WIN32_DEMO_APPLICATION_H + + +#include "DemoApplication.h" + +class Win32DemoApplication : public DemoApplication +{ +protected: + + +public: + + + virtual void swapBuffers(); + + void specialKeyboard(int key, int x, int y); + + virtual void updateModifierKeys(); + + +}; + +#endif //WIN32_DEMO_APPLICATION_H \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/dynamics/testbed/premake4.lua b/Extras/RigidBodyGpuPipeline/dynamics/testbed/premake4.lua new file mode 100644 index 000000000..576aa0fb7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/dynamics/testbed/premake4.lua @@ -0,0 +1,18 @@ + project "testbed" + + kind "StaticLib" + targetdir "../../build/lib" + includedirs { + ".", + "../../bullet2" + } + configuration {"Windows"} + includedirs { + "../../rendering/GlutGlewWindows" + } + configuration{} + + files { + "**.cpp", + "**.h" + } diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/AMD/premake4.lua new file mode 100644 index 000000000..2ae27f6f4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/AMD/premake4.lua @@ -0,0 +1,29 @@ + + hasCL = findOpenCL_AMD() + + if (hasCL) then + + project "OpenCL_bt3dGridBroadphase_AMD" + + initOpenCL_AMD() + + language "C++" + + kind "StaticLib" + targetdir "../../../bin" + + libdirs {"../../../rendering/GlutGlewWindows"} + + includedirs { +-- "../../../rendering/GlutGlewWindows", + "../../../opencl/3dGridBroadphase/Shared", + "../../../../../src", + "../../primitives" + } + + files { + "../Shared/*.cpp", + "../Shared/*.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/MiniCL/MiniCLTaskWrap.cpp b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/MiniCL/MiniCLTaskWrap.cpp new file mode 100644 index 000000000..1398190f2 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/MiniCL/MiniCLTaskWrap.cpp @@ -0,0 +1,23 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2007 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include + +extern "C" +{ + #define MSTRINGIFY(A) A + #include "bt3dGridBroadphaseOCL.cl" + #undef MSTRINGIFY +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cl b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cl new file mode 100644 index 000000000..f66b6e28b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cl @@ -0,0 +1,349 @@ + +MSTRINGIFY( + +int getPosHash(int4 gridPos, __global float4* pParams) +{ + int4 gridDim = *((__global int4*)(pParams + 1)); + gridPos.x &= gridDim.x - 1; + gridPos.y &= gridDim.y - 1; + gridPos.z &= gridDim.z - 1; + int hash = gridPos.z * gridDim.y * gridDim.x + gridPos.y * gridDim.x + gridPos.x; + return hash; +} + +int4 getGridPos(float4 worldPos, __global float4* pParams) +{ + int4 gridPos; + int4 gridDim = *((__global int4*)(pParams + 1)); + gridPos.x = (int)floor(worldPos.x * pParams[0].x) & (gridDim.x - 1); + gridPos.y = (int)floor(worldPos.y * pParams[0].y) & (gridDim.y - 1); + gridPos.z = (int)floor(worldPos.z * pParams[0].z) & (gridDim.z - 1); + return gridPos; +} + + +// calculate grid hash value for each body using its AABB +__kernel void kCalcHashAABB(int numObjects, __global float4* pAABB, __global int2* pHash, __global float4* pParams GUID_ARG) +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + float4 bbMin = pAABB[index*2]; + float4 bbMax = pAABB[index*2 + 1]; + float4 pos; + pos.x = (bbMin.x + bbMax.x) * 0.5f; + pos.y = (bbMin.y + bbMax.y) * 0.5f; + pos.z = (bbMin.z + bbMax.z) * 0.5f; + pos.w = 0.f; + // get address in grid + int4 gridPos = getGridPos(pos, pParams); + int gridHash = getPosHash(gridPos, pParams); + // store grid hash and body index + int2 hashVal; + hashVal.x = gridHash; + hashVal.y = index; + pHash[index] = hashVal; +} + +__kernel void kClearCellStart( int numCells, + __global int* pCellStart GUID_ARG) +{ + int index = get_global_id(0); + if(index >= numCells) + { + return; + } + pCellStart[index] = -1; +} + +__kernel void kFindCellStart(int numObjects, __global int2* pHash, __global int* cellStart GUID_ARG) +{ + __local int sharedHash[513]; + int index = get_global_id(0); + int2 sortedData; + if(index < numObjects) + { + sortedData = pHash[index]; + // Load hash data into shared memory so that we can look + // at neighboring body's hash value without loading + // two hash values per thread + sharedHash[get_local_id(0) + 1] = sortedData.x; + if((index > 0) && (get_local_id(0) == 0)) + { + // first thread in block must load neighbor body hash + sharedHash[0] = pHash[index-1].x; + } + } + barrier(CLK_LOCAL_MEM_FENCE); + if(index < numObjects) + { + if((index == 0) || (sortedData.x != sharedHash[get_local_id(0)])) + { + cellStart[sortedData.x] = index; + } + } +} + +int testAABBOverlap(float4 min0, float4 max0, float4 min1, float4 max1) +{ + return (min0.x <= max1.x)&& (min1.x <= max0.x) && + (min0.y <= max1.y)&& (min1.y <= max0.y) && + (min0.z <= max1.z)&& (min1.z <= max0.z); +} + + + + + +void findPairsInCell( int numObjects, + int4 gridPos, + int index, + __global int2* pHash, + __global int* pCellStart, + __global float4* pAABB, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + __global float4* pParams) +{ + int4 pGridDim = *((__global int4*)(pParams + 1)); + int maxBodiesPerCell = pGridDim.w; + int gridHash = getPosHash(gridPos, pParams); + // get start of bucket for this cell + int bucketStart = pCellStart[gridHash]; + if (bucketStart == -1) + { + return; // cell empty + } + // iterate over bodies in this cell + int2 sortedData = pHash[index]; + int unsorted_indx = sortedData.y; + float4 min0 = pAABB[unsorted_indx*2 + 0]; + float4 max0 = pAABB[unsorted_indx*2 + 1]; + int handleIndex = as_int(min0.w); + int2 start_curr = pPairBuffStartCurr[handleIndex]; + int start = start_curr.x; + int curr = start_curr.y; + int2 start_curr_next = pPairBuffStartCurr[handleIndex+1]; + int curr_max = start_curr_next.x - start - 1; + int bucketEnd = bucketStart + maxBodiesPerCell; + bucketEnd = (bucketEnd > numObjects) ? numObjects : bucketEnd; + for(int index2 = bucketStart; index2 < bucketEnd; index2++) + { + int2 cellData = pHash[index2]; + if (cellData.x != gridHash) + { + break; // no longer in same bucket + } + int unsorted_indx2 = cellData.y; + if (unsorted_indx2 < unsorted_indx) // check not colliding with self + { + float4 min1 = pAABB[unsorted_indx2*2 + 0]; + float4 max1 = pAABB[unsorted_indx2*2 + 1]; + if(testAABBOverlap(min0, max0, min1, max1)) + { + int handleIndex2 = as_int(min1.w); + int k; + for(k = 0; k < curr; k++) + { + int old_pair = pPairBuff[start+k] & (~0x60000000); + if(old_pair == handleIndex2) + { + pPairBuff[start+k] |= 0x40000000; + break; + } + } + if(k == curr) + { + if(curr >= curr_max) + { // not a good solution, but let's avoid crash + break; + } + pPairBuff[start+curr] = handleIndex2 | 0x20000000; + curr++; + } + } + } + } + int2 newStartCurr; + newStartCurr.x = start; + newStartCurr.y = curr; + pPairBuffStartCurr[handleIndex] = newStartCurr; + return; +} + +__kernel void kFindOverlappingPairs( int numObjects, + __global float4* pAABB, + __global int2* pHash, + __global int* pCellStart, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + __global float4* pParams GUID_ARG) + +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + int2 sortedData = pHash[index]; + int unsorted_indx = sortedData.y; + float4 bbMin = pAABB[unsorted_indx*2 + 0]; + float4 bbMax = pAABB[unsorted_indx*2 + 1]; + float4 pos; + pos.x = (bbMin.x + bbMax.x) * 0.5f; + pos.y = (bbMin.y + bbMax.y) * 0.5f; + pos.z = (bbMin.z + bbMax.z) * 0.5f; + // get address in grid + int4 gridPosA = getGridPos(pos, pParams); + int4 gridPosB; + // examine only neighbouring cells + for(int z=-1; z<=1; z++) + { + gridPosB.z = gridPosA.z + z; + for(int y=-1; y<=1; y++) + { + gridPosB.y = gridPosA.y + y; + for(int x=-1; x<=1; x++) + { + gridPosB.x = gridPosA.x + x; + findPairsInCell(numObjects, gridPosB, index, pHash, pCellStart, pAABB, pPairBuff, pPairBuffStartCurr, pParams); + } + } + } +} + + +__kernel void kFindPairsLarge( int numObjects, + __global float4* pAABB, + __global int2* pHash, + __global int* pCellStart, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + uint numLarge GUID_ARG) +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + int2 sortedData = pHash[index]; + int unsorted_indx = sortedData.y; + float4 min0 = pAABB[unsorted_indx*2 + 0]; + float4 max0 = pAABB[unsorted_indx*2 + 1]; + int handleIndex = as_int(min0.w); + int2 start_curr = pPairBuffStartCurr[handleIndex]; + int start = start_curr.x; + int curr = start_curr.y; + int2 start_curr_next = pPairBuffStartCurr[handleIndex+1]; + int curr_max = start_curr_next.x - start - 1; + for(uint i = 0; i < numLarge; i++) + { + int indx2 = numObjects + i; + float4 min1 = pAABB[indx2*2 + 0]; + float4 max1 = pAABB[indx2*2 + 1]; + if(testAABBOverlap(min0, max0, min1, max1)) + { + int k; + int handleIndex2 = as_int(min1.w); + for(k = 0; k < curr; k++) + { + int old_pair = pPairBuff[start+k] & (~0x60000000); + if(old_pair == handleIndex2) + { + pPairBuff[start+k] |= 0x40000000; + break; + } + } + if(k == curr) + { + pPairBuff[start+curr] = handleIndex2 | 0x20000000; + if(curr >= curr_max) + { // not a good solution, but let's avoid crash + break; + } + curr++; + } + } + } + int2 newStartCurr; + newStartCurr.x = start; + newStartCurr.y = curr; + pPairBuffStartCurr[handleIndex] = newStartCurr; + return; +} + +__kernel void kComputePairCacheChanges( int numObjects, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + __global int* pPairScan, + __global float4* pAABB GUID_ARG) +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + float4 bbMin = pAABB[index * 2]; + int handleIndex = as_int(bbMin.w); + int2 start_curr = pPairBuffStartCurr[handleIndex]; + int start = start_curr.x; + int curr = start_curr.y; + __global int *pInp = pPairBuff + start; + int num_changes = 0; + for(int k = 0; k < curr; k++, pInp++) + { + if(!((*pInp) & 0x40000000)) + { + num_changes++; + } + } + pPairScan[index+1] = num_changes; +} + +__kernel void kSqueezeOverlappingPairBuff( int numObjects, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + __global int* pPairScan, + __global int* pPairOut, + __global float4* pAABB GUID_ARG) +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + float4 bbMin = pAABB[index * 2]; + int handleIndex = as_int(bbMin.w); + int2 start_curr = pPairBuffStartCurr[handleIndex]; + int start = start_curr.x; + int curr = start_curr.y; + __global int* pInp = pPairBuff + start; + __global int* pOut = pPairOut + pPairScan[index+1]; + __global int* pOut2 = pInp; + int num = 0; + for(int k = 0; k < curr; k++, pInp++) + { + if(!((*pInp) & 0x40000000)) + { + *pOut = *pInp; + pOut++; + } + if((*pInp) & 0x60000000) + { + *pOut2 = (*pInp) & (~0x60000000); + pOut2++; + num++; + } + } + int2 newStartCurr; + newStartCurr.x = start; + newStartCurr.y = num; + pPairBuffStartCurr[handleIndex] = newStartCurr; +} + + + + +); \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp new file mode 100644 index 000000000..7229b8e95 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp @@ -0,0 +1,697 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006 - 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#include "LinearMath/btAlignedAllocator.h" +#include "LinearMath/btQuickprof.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" +#include "../basic_initialize/btOpenCLUtils.h" + +#include "bt3dGridBroadphaseOCL.h" + +#include +#include +#include "Adl/Adl.h" +#include +#include +#include + +#define ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + +#define GRID_OCL_PATH "..\\..\\opencl\\3dGridBroadphase\\Shared\\bt3dGridBroadphaseOCL.cl" + + +#define MSTRINGIFY(A) #A + +static const char* spProgramSource = +#include "bt3dGridBroadphaseOCL.cl" + +adl::PrefixScan::Data* gData1=0; +adl::Buffer* m_srcClBuffer=0; + +struct MySortData +{ + int key; + int value; +}; + +adl::RadixSort32::Data* dataC = 0; +adl::RadixSort::Data* dataHost = 0; + + +static unsigned int infElem = 0x2fffffff; + +static unsigned int zeroEl = 0; +static unsigned int minusOne= -1; + + +bt3dGridBroadphaseOCL::bt3dGridBroadphaseOCL( btOverlappingPairCache* overlappingPairCache, + const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerSmallProxy, + btScalar maxSmallProxySize, + int maxSmallProxiesPerCell, + cl_context context, cl_device_id device, cl_command_queue queue, + adl::DeviceCL* deviceCL + ) : + btGpu3DGridBroadphase(overlappingPairCache, cellSize, gridSizeX, gridSizeY, gridSizeZ, maxSmallProxies, maxLargeProxies, maxPairsPerSmallProxy, maxSmallProxySize, maxSmallProxiesPerCell) +{ + + + initCL(context, device, queue); + allocateBuffers(); + + prefillBuffers(); + + initKernels(); + + //create an Adl device host and OpenCL device + + adl::DeviceUtils::Config cfg; + m_deviceHost = adl::DeviceUtils::allocate( adl::TYPE_HOST, cfg ); + m_ownsDevice = false; + if (!deviceCL) + { + m_ownsDevice = true; + deviceCL = new adl::DeviceCL; + deviceCL->m_context = context; + deviceCL->m_deviceIdx = device; + deviceCL->m_commandQueue = queue; + deviceCL->m_kernelManager = new adl::KernelManager; + } + + m_deviceCL = deviceCL; + + int minSize = 256*1024; + int maxSortBuffer = maxSmallProxies < minSize ? minSize :maxSmallProxies; + + m_srcClBuffer = new adl::Buffer (m_deviceCL,maxSmallProxies+2); + m_srcClBuffer->write(&zeroEl,1,0); + + //m_srcClBuffer->write(&infElem,maxSmallProxies,0); + m_srcClBuffer->write(&infElem,1,maxSmallProxies); + m_srcClBuffer->write(&zeroEl,1,maxSmallProxies+1); + m_deviceCL->waitForCompletion(); + + gData1 = adl::PrefixScan::allocate( m_deviceCL, maxSortBuffer+2,adl::PrefixScanBase::EXCLUSIVE ); + dataHost = adl::RadixSort::allocate( m_deviceHost, maxSmallProxies+2 ); + dataC = adl::RadixSort32::allocate( m_deviceCL, maxSortBuffer+2 ); + +} + + + +bt3dGridBroadphaseOCL::~bt3dGridBroadphaseOCL() +{ + //btSimpleBroadphase will free memory of btSortedOverlappingPairCache, because m_ownsPairCache + assert(m_bInitialized); + adl::RadixSort::deallocate(dataHost); + adl::PrefixScan::deallocate(gData1); + adl::RadixSort32::deallocate(dataC); + adl::DeviceUtils::deallocate(m_deviceHost); + delete m_srcClBuffer; + if (m_ownsDevice) + { + delete m_deviceCL->m_kernelManager; + delete m_deviceCL; + } +} + +#ifdef CL_PLATFORM_MINI_CL +// there is a problem with MSVC9 : static constructors are not called if variables defined in library and are not used +// looks like it is because of optimization +// probably this will happen with other compilers as well +// so to make it robust, register kernels again (it is safe) +#define MINICL_DECLARE(a) extern "C" void a(); +MINICL_DECLARE(kCalcHashAABB) +MINICL_DECLARE(kClearCellStart) +MINICL_DECLARE(kFindCellStart) +MINICL_DECLARE(kFindOverlappingPairs) +MINICL_DECLARE(kFindPairsLarge) +MINICL_DECLARE(kComputePairCacheChanges) +MINICL_DECLARE(kSqueezeOverlappingPairBuff) +#undef MINICL_DECLARE +#endif + +void bt3dGridBroadphaseOCL::initCL(cl_context context, cl_device_id device, cl_command_queue queue) +{ + + #ifdef CL_PLATFORM_MINI_CL + // call constructors here + MINICL_REGISTER(kCalcHashAABB) + MINICL_REGISTER(kClearCellStart) + MINICL_REGISTER(kFindCellStart) + MINICL_REGISTER(kFindOverlappingPairs) + MINICL_REGISTER(kFindPairsLarge) + MINICL_REGISTER(kComputePairCacheChanges) + MINICL_REGISTER(kSqueezeOverlappingPairBuff) + #endif + + cl_int ciErrNum; + + btAssert(context); + m_cxMainContext = context; + btAssert(device); + m_cdDevice = device; + btAssert(queue); + m_cqCommandQue = queue; + + //adl::Kernel kern = m_deviceCL->getKernel(fileName,funcName,options,src); + + m_cpProgram = btOpenCLUtils::compileCLProgramFromString(m_cxMainContext,m_cdDevice,spProgramSource, &ciErrNum,"-DGUID_ARG=""""",GRID_OCL_PATH); + + printf("OK\n"); +} + + +void bt3dGridBroadphaseOCL::initKernels() +{ + initKernel(GRID3DOCL_KERNEL_CALC_HASH_AABB, "kCalcHashAABB"); + setKernelArg(GRID3DOCL_KERNEL_CALC_HASH_AABB, 1, sizeof(cl_mem),(void*)&m_dAABB); + setKernelArg(GRID3DOCL_KERNEL_CALC_HASH_AABB, 2, sizeof(cl_mem),(void*)&m_dBodiesHash); + setKernelArg(GRID3DOCL_KERNEL_CALC_HASH_AABB, 3, sizeof(cl_mem),(void*)&m_dBpParams); + + initKernel(GRID3DOCL_KERNEL_CLEAR_CELL_START, "kClearCellStart"); + setKernelArg(GRID3DOCL_KERNEL_CLEAR_CELL_START, 1, sizeof(cl_mem),(void*)&m_dCellStart); + + initKernel(GRID3DOCL_KERNEL_FIND_CELL_START, "kFindCellStart"); + setKernelArg(GRID3DOCL_KERNEL_FIND_CELL_START, 1, sizeof(cl_mem),(void*)&m_dBodiesHash); + setKernelArg(GRID3DOCL_KERNEL_FIND_CELL_START, 2, sizeof(cl_mem),(void*)&m_dCellStart); + + initKernel(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, "kFindOverlappingPairs"); + setKernelArg(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, 1, sizeof(cl_mem),(void*)&m_dAABB); + setKernelArg(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, 2, sizeof(cl_mem),(void*)&m_dBodiesHash); + setKernelArg(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, 3, sizeof(cl_mem),(void*)&m_dCellStart); + setKernelArg(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, 4, sizeof(cl_mem),(void*)&m_dPairBuff); + setKernelArg(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, 5, sizeof(cl_mem),(void*)&m_dPairBuffStartCurr); + setKernelArg(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, 6, sizeof(cl_mem),(void*)&m_dBpParams); + + initKernel(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, "kFindPairsLarge"); + setKernelArg(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, 1, sizeof(cl_mem),(void*)&m_dAABB); + setKernelArg(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, 2, sizeof(cl_mem),(void*)&m_dBodiesHash); + setKernelArg(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, 3, sizeof(cl_mem),(void*)&m_dCellStart); + setKernelArg(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, 4, sizeof(cl_mem),(void*)&m_dPairBuff); + setKernelArg(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, 5, sizeof(cl_mem),(void*)&m_dPairBuffStartCurr); + + initKernel(GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, "kComputePairCacheChanges"); + setKernelArg(GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, 1, sizeof(cl_mem),(void*)&m_dPairBuff); + setKernelArg(GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, 2, sizeof(cl_mem),(void*)&m_dPairBuffStartCurr); + setKernelArg(GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, 3, sizeof(cl_mem),(void*)&m_dPairScanChanged); + setKernelArg(GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, 4, sizeof(cl_mem),(void*)&m_dAABB); + + initKernel(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, "kSqueezeOverlappingPairBuff"); + setKernelArg(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, 1, sizeof(cl_mem),(void*)&m_dPairBuff); + setKernelArg(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, 2, sizeof(cl_mem),(void*)&m_dPairBuffStartCurr); + setKernelArg(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, 3, sizeof(cl_mem),(void*)&m_dPairScanChanged); + setKernelArg(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, 4, sizeof(cl_mem),(void*)&m_dPairsChanged); + setKernelArg(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, 5, sizeof(cl_mem),(void*)&m_dAABB); + +} + + +void bt3dGridBroadphaseOCL::allocateBuffers() +{ + cl_int ciErrNum; + unsigned int memSize; + // current version of bitonic sort works for power of 2 arrays only, so ... + m_hashSize = 1; + for(int bit = 1; bit < 32; bit++) + { + if(m_hashSize >= m_maxHandles) + { + break; + } + m_hashSize <<= 1; + } + memSize = m_hashSize * 2 * sizeof(unsigned int); + if (memSize < 1024*1024) + memSize = 1024*1024; + + m_dBodiesHash = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + memSize = m_numCells * sizeof(unsigned int); + m_dCellStart = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + memSize = m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int); + m_dPairBuff = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + memSize = (m_maxHandles * 2 + 1) * sizeof(unsigned int); + m_dPairBuffStartCurr = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + unsigned int numAABB = m_maxHandles + m_maxLargeHandles; + memSize = numAABB * sizeof(float) * 4 * 2; + m_dAABB = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + memSize = (m_maxHandles + 2) * sizeof(unsigned int); + m_dPairScanChanged = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + memSize = m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int); + m_dPairsChanged = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + m_dPairsContiguous = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + + memSize = 3 * 4 * sizeof(float); + m_dBpParams = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); +} + +void bt3dGridBroadphaseOCL::prefillBuffers() +{ + memset(m_hBodiesHash, 0xFF, m_maxHandles*2*sizeof(unsigned int)); + copyArrayToDevice(m_dBodiesHash, m_hBodiesHash, m_maxHandles * 2 * sizeof(unsigned int)); + // now fill the rest (bitonic sorting works with size == pow of 2) + int remainder = m_hashSize - m_maxHandles; + if(remainder) + { + copyArrayToDevice(m_dBodiesHash, m_hBodiesHash, remainder * 2 * sizeof(unsigned int), m_maxHandles * 2 * sizeof(unsigned int), 0); + } + copyArrayToDevice(m_dPairBuffStartCurr, m_hPairBuffStartCurr, (m_maxHandles * 2 + 1) * sizeof(unsigned int)); + memset(m_hPairBuff, 0x00, m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int)); + copyArrayToDevice(m_dPairBuff, m_hPairBuff, m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int)); +} + + +void bt3dGridBroadphaseOCL::initKernel(int kernelId, char* pName) +{ + + cl_int ciErrNum; + cl_kernel kernel = clCreateKernel(m_cpProgram, pName, &ciErrNum); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + size_t wgSize; + ciErrNum = clGetKernelWorkGroupInfo(kernel, m_cdDevice, CL_KERNEL_WORK_GROUP_SIZE, sizeof(size_t), &wgSize, NULL); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + m_kernels[kernelId].m_Id = kernelId; + m_kernels[kernelId].m_kernel = kernel; + m_kernels[kernelId].m_name = pName; + m_kernels[kernelId].m_workgroupSize = (int)wgSize; + return; +} + +void bt3dGridBroadphaseOCL::runKernelWithWorkgroupSize(int kernelId, int globalSize) +{ + if(globalSize <= 0) + { + return; + } + cl_kernel kernelFunc = m_kernels[kernelId].m_kernel; + cl_int ciErrNum = clSetKernelArg(kernelFunc, 0, sizeof(int), (void*)&globalSize); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + int workgroupSize = btMin(64,m_kernels[kernelId].m_workgroupSize); + + if(workgroupSize <= 0) + { // let OpenCL library calculate workgroup size + size_t globalWorkSize[2]; + globalWorkSize[0] = globalSize; + globalWorkSize[1] = 1; + ciErrNum = clEnqueueNDRangeKernel(m_cqCommandQue, kernelFunc, 1, NULL, globalWorkSize, NULL, 0,0,0 ); + } + else + { + size_t localWorkSize[2], globalWorkSize[2]; + //workgroupSize = btMin(workgroupSize, globalSize); + int num_t = globalSize / workgroupSize; + int num_g = num_t * workgroupSize; + if(num_g < globalSize) + { + num_t++; + } + localWorkSize[0] = workgroupSize; + globalWorkSize[0] = num_t * workgroupSize; + localWorkSize[1] = 1; + globalWorkSize[1] = 1; + ciErrNum = clEnqueueNDRangeKernel(m_cqCommandQue, kernelFunc, 1, NULL, globalWorkSize, localWorkSize, 0,0,0 ); + } + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + ciErrNum = clFlush(m_cqCommandQue); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); +} + + +void bt3dGridBroadphaseOCL::setKernelArg(int kernelId, int argNum, int argSize, void* argPtr) +{ + cl_int ciErrNum; + ciErrNum = clSetKernelArg(m_kernels[kernelId].m_kernel, argNum, argSize, argPtr); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); +} + + +void bt3dGridBroadphaseOCL::copyArrayToDevice(cl_mem device, const void* host, unsigned int size, int devOffs, int hostOffs) +{ + if (size) + { + cl_int ciErrNum; + char* pHost = (char*)host + hostOffs; + ciErrNum = clEnqueueWriteBuffer(m_cqCommandQue, device, CL_TRUE, devOffs, size, pHost, 0, NULL, NULL); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + } +} + +void bt3dGridBroadphaseOCL::copyArrayFromDevice(void* host, const cl_mem device, unsigned int size, int hostOffs, int devOffs) +{ + if (size) + { + cl_int ciErrNum; + char* pHost = (char*)host + hostOffs; + ciErrNum = clEnqueueReadBuffer(m_cqCommandQue, device, CL_TRUE, devOffs, size, pHost, 0, NULL, NULL); + GRID3DOCL_CHECKERROR(ciErrNum, CL_SUCCESS); + } +} + + + +// +// overrides +// + + +void bt3dGridBroadphaseOCL::prepareAABB() +{ + btGpu3DGridBroadphase::prepareAABB(); + copyArrayToDevice(m_dAABB, m_hAABB, sizeof(bt3DGrid3F1U) * 2 * (m_numHandles + m_numLargeHandles)); + return; +} + +void bt3dGridBroadphaseOCL::setParameters(bt3DGridBroadphaseParams* hostParams) +{ + btGpu3DGridBroadphase::setParameters(hostParams); + struct btParamsBpOCL + { + float m_invCellSize[4]; + int m_gridSize[4]; + }; + btParamsBpOCL hParams; + hParams.m_invCellSize[0] = m_params.m_invCellSizeX; + hParams.m_invCellSize[1] = m_params.m_invCellSizeY; + hParams.m_invCellSize[2] = m_params.m_invCellSizeZ; + hParams.m_invCellSize[3] = 0.f; + hParams.m_gridSize[0] = m_params.m_gridSizeX; + hParams.m_gridSize[1] = m_params.m_gridSizeY; + hParams.m_gridSize[2] = m_params.m_gridSizeZ; + hParams.m_gridSize[3] = m_params.m_maxBodiesPerCell; + copyArrayToDevice(m_dBpParams, &hParams, sizeof(btParamsBpOCL)); + return; +} + + +void bt3dGridBroadphaseOCL::calcHashAABB() +{ + BT_PROFILE("calcHashAABB"); +#if 1 + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_CALC_HASH_AABB, m_numHandles); +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif //ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + +#else + btGpu3DGridBroadphase::calcHashAABB(); +#endif + + return; +} + + +void bt3dGridBroadphaseOCL::sortHash() +{ + BT_PROFILE("sortHash"); +#ifdef CL_PLATFORM_MINI_CL + //copyArrayFromDevice(m_hBodiesHash, m_dBodiesHash, m_numHandles * 2 * sizeof(unsigned int)); + btGpu3DGridBroadphase::sortHash(); + copyArrayToDevice(m_dBodiesHash, m_hBodiesHash, m_numHandles * 2 * sizeof(unsigned int)); +#else + +//#define USE_HOST +#ifdef USE_HOST + copyArrayFromDevice(m_hBodiesHash, m_dBodiesHash, m_numHandles * 2 * sizeof(unsigned int)); + //adl::Buffer keysIn,keysOut,valuesIn,valuesOut; + ///adl::RadixSort32::execute(dataC,keysIn,keysOut,valuesIn,valuesOut,m_numHandles); + adl::HostBuffer inoutHost; + inoutHost.m_device = m_deviceHost; + inoutHost.m_ptr = (adl::SortData*)m_hBodiesHash; + inoutHost.m_size = m_numHandles; + adl::RadixSort::execute(dataHost, inoutHost,m_numHandles); + copyArrayToDevice(m_dBodiesHash, m_hBodiesHash, m_numHandles * 2 * sizeof(unsigned int)); +#else + { + clFinish(m_cqCommandQue); + BT_PROFILE("RadixSort32::execute"); + adl::Buffer inout; + inout.m_device = this->m_deviceCL; + inout.m_size = m_numHandles; + inout.m_ptr = (adl::SortData*)m_dBodiesHash; + int actualHandles = m_numHandles; + int dataAlignment = adl::RadixSort32::DATA_ALIGNMENT; + + if (actualHandles%dataAlignment) + { + actualHandles += dataAlignment-(actualHandles%dataAlignment); + } + + adl::RadixSort32::execute(dataC,inout, actualHandles); +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif //ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + } + { + //BT_PROFILE("copyArrayFromDevice"); + //copyArrayFromDevice(m_hBodiesHash, m_dBodiesHash, m_numHandles * 2 * sizeof(unsigned int)); +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif //ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + } + + +#endif //USE_HOST +#endif + + return; +} + + + +void bt3dGridBroadphaseOCL::findCellStart() +{ +#if 1 + BT_PROFILE("findCellStart"); + + #if defined(CL_PLATFORM_MINI_CL) + btGpu3DGridBroadphase::findCellStart(); + copyArrayToDevice(m_dCellStart, m_hCellStart, m_numCells * sizeof(unsigned int)); + #else + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_CLEAR_CELL_START, m_numCells); + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_FIND_CELL_START, m_numHandles); + #endif +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + +#else + btGpu3DGridBroadphase::findCellStart(); +#endif + + return; +} + + + +void bt3dGridBroadphaseOCL::findOverlappingPairs() +{ +#if 1 + BT_PROFILE("findOverlappingPairs"); + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, m_numHandles); +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + +#else + btGpu3DGridBroadphase::findOverlappingPairs(); + copyArrayToDevice(m_dPairBuffStartCurr, m_hPairBuffStartCurr, (m_maxHandles * 2 + 1) * sizeof(unsigned int)); + copyArrayToDevice(m_dPairBuff, m_hPairBuff, m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int)); +#endif + return; +} + + +void bt3dGridBroadphaseOCL::findPairsLarge() +{ + BT_PROFILE("findPairsLarge"); +#if 1 + if(m_numLargeHandles) + { + setKernelArg(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, 6, sizeof(int),(void*)&m_numLargeHandles); + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, m_numHandles); + } +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + +#else + btGpu3DGridBroadphase::findPairsLarge(); +#endif + return; +} + + + +void bt3dGridBroadphaseOCL::computePairCacheChanges() +{ + BT_PROFILE("computePairCacheChanges"); +#if 1 + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, m_numHandles); +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + copyArrayFromDevice( m_hPairScanChanged,m_dPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); + +#else + btGpu3DGridBroadphase::computePairCacheChanges(); + copyArrayToDevice(m_dPairScanChanged, m_hPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); + + +#endif + return; +} + + + + +extern cl_device_type deviceType; + +void bt3dGridBroadphaseOCL::scanOverlappingPairBuff(bool copyToCpu) +{ + + //Intel/CPU version doesn't handlel Adl scan well +#if 0 + { + copyArrayFromDevice(m_hPairScanChanged, m_dPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); + btGpu3DGridBroadphase::scanOverlappingPairBuff(); + copyArrayToDevice(m_dPairScanChanged, m_hPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); + m_numPrefixSum = m_hPairScanChanged[m_numHandles+1]; + clFinish(m_cqCommandQue); + //memset(m_hPairScanChanged,0,sizeof(int)*m_maxHandles + 2); + } +#else + { + + // copyArrayFromDevice(m_hPairScanChanged, m_dPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); + // btGpu3DGridBroadphase::scanOverlappingPairBuff(); + + adl::Buffer destBuffer; + + { + BT_PROFILE("copy GPU->GPU"); + + destBuffer.m_ptr = (unsigned int*)m_dPairScanChanged; + destBuffer.m_device = m_deviceCL; + destBuffer.m_size = sizeof(unsigned int)*(m_numHandles+2); + m_deviceCL->copy(m_srcClBuffer, &destBuffer,m_numHandles,1,1); + +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + + } + + { + BT_PROFILE("PrefixScan"); + + adl::PrefixScan::execute(gData1,*m_srcClBuffer,destBuffer, m_numHandles+2,&m_numPrefixSum); + +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + //if (m_numPrefixSum>0x1000) + // { + // printf("error m_numPrefixSum==%d\n",m_numPrefixSum); + // } + + } + +#if 0 + unsigned int* verifyhPairScanChanged = new unsigned int[m_maxHandles + 2]; + memset(verifyhPairScanChanged,0,sizeof(int)*m_maxHandles + 2); + + copyArrayFromDevice(verifyhPairScanChanged, m_dPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); + clFinish(m_cqCommandQue); + + /*for (int i=0;i CPU"); + copyArrayFromDevice(m_hPairScanChanged, m_dPairScanChanged, sizeof(unsigned int)*(m_numHandles + 2)); +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + } + + } + + } +#endif + + +} + + + +void bt3dGridBroadphaseOCL::squeezeOverlappingPairBuff() +{ + BT_PROFILE("btCuda_squeezeOverlappingPairBuff"); +#if 1 + runKernelWithWorkgroupSize(GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, m_numHandles); +// btCuda_squeezeOverlappingPairBuff(m_dPairBuff, m_dPairBuffStartCurr, m_dPairScanChanged, m_dPairsChanged, m_dAABB, m_numHandles); + + //copyArrayFromDevice(m_hPairsChanged, m_dPairsChanged, sizeof(unsigned int) * m_numPrefixSum);//m_hPairScanChanged[m_numHandles+1]); //gSum +#ifdef ADD_BLOCKING_CL_FINISH_FOR_BENCHMARK + clFinish(m_cqCommandQue); +#endif + +#else + btGpu3DGridBroadphase::squeezeOverlappingPairBuff(); +#endif + return; +} + + + +void bt3dGridBroadphaseOCL::resetPool(btDispatcher* dispatcher) +{ + btGpu3DGridBroadphase::resetPool(dispatcher); + prefillBuffers(); +} + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h new file mode 100644 index 000000000..dee297c29 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h @@ -0,0 +1,146 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006 - 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + + +#ifndef BT3DGRIDBROADPHASEOCL_H +#define BT3DGRIDBROADPHASEOCL_H + +#ifdef __APPLE__ +#ifdef USE_MINICL + #include +#else + #include +#endif +//CL_PLATFORM_MINI_CL could be defined in build system +#else +//#include +// standard utility and system includes +#ifdef USE_MINICL + #include +#else + #include +#endif +// Extra CL/GL include +//#include +#endif //__APPLE__ + +namespace adl +{ + struct Device; + struct DeviceCL; +}; + +#include "BulletCollision/BroadphaseCollision/btSimpleBroadphase.h" +#include "btGpu3DGridBroadphaseSharedTypes.h" +#include "btGpu3DGridBroadphase.h" + + +#define GRID3DOCL_CHECKERROR(a, b) if((a)!=(b)) { printf("3D GRID OCL Error : %d\n", (a)); btAssert((a) == (b)); } + +enum +{ + GRID3DOCL_KERNEL_CALC_HASH_AABB = 0, + GRID3DOCL_KERNEL_CLEAR_CELL_START, + GRID3DOCL_KERNEL_FIND_CELL_START, + GRID3DOCL_KERNEL_FIND_OVERLAPPING_PAIRS, + GRID3DOCL_KERNEL_FIND_PAIRS_LARGE, + GRID3DOCL_KERNEL_COMPUTE_CACHE_CHANGES, + GRID3DOCL_KERNEL_SQUEEZE_PAIR_BUFF, + GRID3DOCL_KERNEL_TOTAL +}; + +struct bt3dGridOCLKernelInfo +{ + int m_Id; + cl_kernel m_kernel; + char* m_name; + int m_workgroupSize; +}; + + +///The bt3dGridBroadphaseOCL uses OpenCL-capable GPU to compute overlapping pairs + +class bt3dGridBroadphaseOCL : public btGpu3DGridBroadphase +{ +protected: + int m_hashSize; + cl_context m_cxMainContext; + cl_device_id m_cdDevice; + cl_command_queue m_cqCommandQue; + cl_program m_cpProgram; + bt3dGridOCLKernelInfo m_kernels[GRID3DOCL_KERNEL_TOTAL]; + // data buffers + cl_mem m_dBodiesHash; + cl_mem m_dCellStart; + cl_mem m_dPairBuff; + cl_mem m_dPairBuffStartCurr; +public: + cl_mem m_dAABB; +protected: + cl_mem m_dPairScanChanged; + cl_mem m_dPairsChanged; + cl_mem m_dPairsContiguous; + cl_mem m_dBpParams; + + adl::Device* m_deviceHost; + adl::DeviceCL* m_deviceCL; + bool m_ownsDevice; + + +public: + unsigned int m_numPrefixSum; + + bt3dGridBroadphaseOCL( btOverlappingPairCache* overlappingPairCache, + const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerSmallProxy, + btScalar maxSmallProxySize, + int maxSmallProxiesPerCell = 8, + cl_context context = NULL, + cl_device_id device = NULL, + cl_command_queue queue = NULL, + adl::DeviceCL* deviceCL = 0 + ); + virtual ~bt3dGridBroadphaseOCL(); + +protected: + void initCL(cl_context context, cl_device_id device, cl_command_queue queue); + void initKernels(); + void allocateBuffers(); + void prefillBuffers(); + void initKernel(int kernelId, char* pName); + void allocateArray(void** devPtr, unsigned int size); + void freeArray(void* devPtr); + void runKernelWithWorkgroupSize(int kernelId, int globalSize); + void setKernelArg(int kernelId, int argNum, int argSize, void* argPtr); + void copyArrayToDevice(cl_mem device, const void* host, unsigned int size, int devOffs = 0, int hostOffs = 0); + void copyArrayFromDevice(void* host, const cl_mem device, unsigned int size, int hostOffs = 0, int devOffs = 0); + +// overrides + virtual void setParameters(bt3DGridBroadphaseParams* hostParams); + virtual void prepareAABB(); + virtual void calcHashAABB(); + virtual void sortHash(); + virtual void findCellStart(); + virtual void findOverlappingPairs(); + virtual void findPairsLarge(); + virtual void computePairCacheChanges(); + virtual void scanOverlappingPairBuff(bool copyToCpu=true); + virtual void squeezeOverlappingPairBuff(); + virtual void resetPool(btDispatcher* dispatcher); +}; + +#endif //BT3DGRIDBROADPHASEOCL_H \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp new file mode 100644 index 000000000..3ecb6a2fb --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp @@ -0,0 +1,626 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006, 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +///The 3 following lines include the CPU implementation of the kernels, keep them in this order. +#include "btGpuDefines.h" +#include "btGpuUtilsSharedDefs.h" +#include "btGpuUtilsSharedCode.h" + + + +#include "LinearMath/btAlignedAllocator.h" +#include "LinearMath/btQuickprof.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" + + + +#include "btGpuDefines.h" +#include "btGpuUtilsSharedDefs.h" + +#include "btGpu3DGridBroadphaseSharedDefs.h" + +#include "btGpu3DGridBroadphase.h" +#include //for memset + + +#include + + + +static bt3DGridBroadphaseParams s3DGridBroadphaseParams; + + + +btGpu3DGridBroadphase::btGpu3DGridBroadphase( const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerBody, + btScalar maxSmallProxySize, + int maxBodiesPerCell) : + btSimpleBroadphase(maxSmallProxies, +// new (btAlignedAlloc(sizeof(btSortedOverlappingPairCache),16)) btSortedOverlappingPairCache), + new (btAlignedAlloc(sizeof(btHashedOverlappingPairCache),16)) btHashedOverlappingPairCache), + m_bInitialized(false), + m_numBodies(0) +{ + _initialize(cellSize, gridSizeX, gridSizeY, gridSizeZ, + maxSmallProxies, maxLargeProxies, maxPairsPerBody, + maxSmallProxySize, maxBodiesPerCell); +} + + + +btGpu3DGridBroadphase::btGpu3DGridBroadphase( btOverlappingPairCache* overlappingPairCache, + const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerBody, + btScalar maxSmallProxySize, + int maxBodiesPerCell) : + btSimpleBroadphase(maxSmallProxies, overlappingPairCache), + m_bInitialized(false), + m_numBodies(0) +{ + _initialize(cellSize, gridSizeX, gridSizeY, gridSizeZ, + maxSmallProxies, maxLargeProxies, maxPairsPerBody, + maxSmallProxySize, maxBodiesPerCell); +} + + + +btGpu3DGridBroadphase::~btGpu3DGridBroadphase() +{ + //btSimpleBroadphase will free memory of btSortedOverlappingPairCache, because m_ownsPairCache + assert(m_bInitialized); + _finalize(); + + +} + +// returns 2^n : 2^(n+1) > val >= 2^n +int btGpu3DGridBroadphase::getFloorPowOfTwo(int val) +{ + int mask = 0x40000000; + for(int k = 0; k < 30; k++, mask >>= 1) + { + if(mask & val) + { + break; + } + } + return mask; +} + + + +void btGpu3DGridBroadphase::_initialize( const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerBody, + btScalar maxSmallProxySize, + int maxBodiesPerCell) +{ + // set various paramerers + m_ownsPairCache = true; + m_params.m_gridSizeX = getFloorPowOfTwo(gridSizeX); + m_params.m_gridSizeY = getFloorPowOfTwo(gridSizeY); + m_params.m_gridSizeZ = getFloorPowOfTwo(gridSizeZ); + m_params.m_numCells = m_params.m_gridSizeX * m_params.m_gridSizeY * m_params.m_gridSizeZ; + m_numCells = m_params.m_numCells; + m_params.m_invCellSizeX = btScalar(1.f) / cellSize[0]; + m_params.m_invCellSizeY = btScalar(1.f) / cellSize[1]; + m_params.m_invCellSizeZ = btScalar(1.f) / cellSize[2]; + m_maxRadius = maxSmallProxySize * btScalar(0.5f); + m_params.m_numBodies = m_numBodies; + m_params.m_maxBodiesPerCell = maxBodiesPerCell; + + m_numLargeHandles = 0; + m_maxLargeHandles = maxLargeProxies; + + m_maxPairsPerBody = maxPairsPerBody; + + m_LastLargeHandleIndex = -1; + + assert(!m_bInitialized); + + // allocate host storage + m_hBodiesHash = new unsigned int[m_maxHandles * 2]; + memset(m_hBodiesHash, 0x00, m_maxHandles*2*sizeof(unsigned int)); + + m_hCellStart = new unsigned int[m_params.m_numCells]; + memset(m_hCellStart, 0x00, m_params.m_numCells * sizeof(unsigned int)); + + m_hPairBuffStartCurr = new unsigned int[m_maxHandles * 2 + 2]; + // --------------- for now, init with m_maxPairsPerBody for each body + m_hPairBuffStartCurr[0] = 0; + m_hPairBuffStartCurr[1] = 0; + for(int i = 1; i <= m_maxHandles; i++) + { + m_hPairBuffStartCurr[i * 2] = m_hPairBuffStartCurr[(i-1) * 2] + m_maxPairsPerBody; + m_hPairBuffStartCurr[i * 2 + 1] = 0; + } + //---------------- + unsigned int numAABB = m_maxHandles + m_maxLargeHandles; + m_hAABB = new bt3DGrid3F1U[numAABB * 2]; // AABB Min & Max + + m_hPairBuff = new unsigned int[m_maxHandles * m_maxPairsPerBody]; + memset(m_hPairBuff, 0x00, m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int)); // needed? + + m_hPairScanChanged = new unsigned int[m_maxHandles + 2]; + memset(m_hPairScanChanged,0,sizeof(int)*m_maxHandles + 2); + + m_hPairsChanged = new unsigned int[m_maxHandles * m_maxPairsPerBody]; + memset(m_hPairsChanged,0,sizeof(int)*(m_maxHandles * m_maxPairsPerBody)); + + m_hAllOverlappingPairs= new MyUint2[m_maxHandles * m_maxPairsPerBody]; + memset(m_hAllOverlappingPairs,0,sizeof(MyUint2)*(m_maxHandles * m_maxPairsPerBody)); + + +// large proxies + + // allocate handles buffer and put all handles on free list + m_pLargeHandlesRawPtr = btAlignedAlloc(sizeof(btSimpleBroadphaseProxy) * m_maxLargeHandles, 16); + m_pLargeHandles = new(m_pLargeHandlesRawPtr) btSimpleBroadphaseProxy[m_maxLargeHandles]; + m_firstFreeLargeHandle = 0; + { + for (int i = m_firstFreeLargeHandle; i < m_maxLargeHandles; i++) + { + m_pLargeHandles[i].SetNextFree(i + 1); + m_pLargeHandles[i].m_uniqueId = m_maxHandles+2+i; + } + m_pLargeHandles[m_maxLargeHandles - 1].SetNextFree(0); + } + +// debug data + m_numPairsAdded = 0; + m_numOverflows = 0; + + + m_bInitialized = true; +} + + + +void btGpu3DGridBroadphase::_finalize() +{ + assert(m_bInitialized); + delete [] m_hBodiesHash; + delete [] m_hCellStart; + delete [] m_hPairBuffStartCurr; + delete [] m_hAABB; + delete [] m_hPairBuff; + delete [] m_hPairScanChanged; + delete [] m_hPairsChanged; + delete [] m_hAllOverlappingPairs; + btAlignedFree(m_pLargeHandlesRawPtr); + m_bInitialized = false; +} + + + +void btGpu3DGridBroadphase::calculateOverlappingPairs(btDispatcher* dispatcher) +{ + btSimpleBroadphase::calculateOverlappingPairs(dispatcher); + + if(m_numHandles <= 0) + { + BT_PROFILE("addLarge2LargePairsToCache"); + addLarge2LargePairsToCache(dispatcher); + return; + } + // update constants + { + BT_PROFILE("setParameters"); + setParameters(&m_params); + } + + // prepare AABB array + { + BT_PROFILE("prepareAABB"); + prepareAABB(); + } + // calculate hash + { + BT_PROFILE("calcHashAABB"); + calcHashAABB(); + } + { + BT_PROFILE("sortHash"); + // sort bodies based on hash + sortHash(); + } + // find start of each cell + { + BT_PROFILE("findCellStart"); + findCellStart(); + } + { + BT_PROFILE("findOverlappingPairs"); + // findOverlappingPairs (small/small) + findOverlappingPairs(); + } + // findOverlappingPairs (small/large) + { + BT_PROFILE("findPairsLarge"); + findPairsLarge(); + } + // add pairs to CPU cache + { + BT_PROFILE("computePairCacheChanges"); + computePairCacheChanges(); + } + { + BT_PROFILE("scanOverlappingPairBuff"); + scanOverlappingPairBuff(); + } + { + BT_PROFILE("squeezeOverlappingPairBuff"); + squeezeOverlappingPairBuff(); + } + { + BT_PROFILE("addPairsToCache"); + addPairsToCache(dispatcher); + } + // find and add large/large pairs to CPU cache + { + BT_PROFILE("addLarge2LargePairsToCache"); + addLarge2LargePairsToCache(dispatcher); + } + return; +} + + + +void btGpu3DGridBroadphase::addPairsToCache(btDispatcher* dispatcher) +{ + m_numPairsAdded = 0; + m_numPairsRemoved = 0; + for(int i = 0; i < m_numHandles; i++) + { + unsigned int num = m_hPairScanChanged[i+2] - m_hPairScanChanged[i+1]; + if(!num) + { + continue; + } + unsigned int* pInp = m_hPairsChanged + m_hPairScanChanged[i+1]; + unsigned int index0 = m_hAABB[i * 2].uw; + btSimpleBroadphaseProxy* proxy0 = &m_pHandles[index0]; + for(unsigned int j = 0; j < num; j++) + { + unsigned int indx1_s = pInp[j]; + unsigned int index1 = indx1_s & (~BT_3DGRID_PAIR_ANY_FLG); + btSimpleBroadphaseProxy* proxy1; + if(index1 < (unsigned int)m_maxHandles) + { + proxy1 = &m_pHandles[index1]; + } + else + { + index1 -= m_maxHandles; + btAssert((index1 >= 0) && (index1 < (unsigned int)m_maxLargeHandles)); + proxy1 = &m_pLargeHandles[index1]; + } + if(indx1_s & BT_3DGRID_PAIR_NEW_FLG) + { + m_pairCache->addOverlappingPair(proxy0,proxy1); + m_numPairsAdded++; + } + else + { + m_pairCache->removeOverlappingPair(proxy0,proxy1,dispatcher); + m_numPairsRemoved++; + } + } + } +} + + + +btBroadphaseProxy* btGpu3DGridBroadphase::createProxy( const btVector3& aabbMin, const btVector3& aabbMax,int shapeType,void* userPtr ,short int collisionFilterGroup,short int collisionFilterMask, btDispatcher* dispatcher,void* multiSapProxy) +{ + btBroadphaseProxy* proxy; + bool bIsLarge = isLargeProxy(aabbMin, aabbMax); + if(bIsLarge) + { + if (m_numLargeHandles >= m_maxLargeHandles) + { + ///you have to increase the cell size, so 'large' proxies become 'small' proxies (fitting a cell) + btAssert(0); + return 0; //should never happen, but don't let the game crash ;-) + } + btAssert((aabbMin[0]<= aabbMax[0]) && (aabbMin[1]<= aabbMax[1]) && (aabbMin[2]<= aabbMax[2])); + int newHandleIndex = allocLargeHandle(); + proxy = new (&m_pLargeHandles[newHandleIndex])btSimpleBroadphaseProxy(aabbMin,aabbMax,shapeType,userPtr,collisionFilterGroup,collisionFilterMask,multiSapProxy); + } + else + { + proxy = btSimpleBroadphase::createProxy(aabbMin, aabbMax, shapeType, userPtr, collisionFilterGroup, collisionFilterMask, dispatcher, multiSapProxy); + } + return proxy; +} + + + +void btGpu3DGridBroadphase::destroyProxy(btBroadphaseProxy* proxy, btDispatcher* dispatcher) +{ + bool bIsLarge = isLargeProxy(proxy); + if(bIsLarge) + { + + btSimpleBroadphaseProxy* proxy0 = static_cast(proxy); + freeLargeHandle(proxy0); + m_pairCache->removeOverlappingPairsContainingProxy(proxy,dispatcher); + } + else + { + btSimpleBroadphase::destroyProxy(proxy, dispatcher); + } + return; +} + + + +void btGpu3DGridBroadphase::resetPool(btDispatcher* dispatcher) +{ + m_hPairBuffStartCurr[0] = 0; + m_hPairBuffStartCurr[1] = 0; + for(int i = 1; i <= m_maxHandles; i++) + { + m_hPairBuffStartCurr[i * 2] = m_hPairBuffStartCurr[(i-1) * 2] + m_maxPairsPerBody; + m_hPairBuffStartCurr[i * 2 + 1] = 0; + } +} + + + +bool btGpu3DGridBroadphase::isLargeProxy(const btVector3& aabbMin, const btVector3& aabbMax) +{ + btVector3 diag = aabbMax - aabbMin; + ///use the bounding sphere radius of this bounding box, to include rotation + btScalar radius = diag.length() * btScalar(0.5f); + return (radius > m_maxRadius); +} + + + +bool btGpu3DGridBroadphase::isLargeProxy(btBroadphaseProxy* proxy) +{ + return (proxy->getUid() >= (m_maxHandles+2)); +} + + + +void btGpu3DGridBroadphase::addLarge2LargePairsToCache(btDispatcher* dispatcher) +{ + int i,j; + if (m_numLargeHandles <= 0) + { + return; + } + int new_largest_index = -1; + for(i = 0; i <= m_LastLargeHandleIndex; i++) + { + btSimpleBroadphaseProxy* proxy0 = &m_pLargeHandles[i]; + new_largest_index = i; + for(j = i + 1; j <= m_LastLargeHandleIndex; j++) + { + btSimpleBroadphaseProxy* proxy1 = &m_pLargeHandles[j]; + btAssert(proxy0 != proxy1); + btSimpleBroadphaseProxy* p0 = getSimpleProxyFromProxy(proxy0); + btSimpleBroadphaseProxy* p1 = getSimpleProxyFromProxy(proxy1); + if(aabbOverlap(p0,p1)) + { + if (!m_pairCache->findPair(proxy0,proxy1)) + { + m_pairCache->addOverlappingPair(proxy0,proxy1); + } + } + else + { + if(m_pairCache->findPair(proxy0,proxy1)) + { + m_pairCache->removeOverlappingPair(proxy0,proxy1,dispatcher); + } + } + } + } + m_LastLargeHandleIndex = new_largest_index; + return; +} + + + +void btGpu3DGridBroadphase::rayTest(const btVector3& rayFrom,const btVector3& rayTo, btBroadphaseRayCallback& rayCallback) +{ + btSimpleBroadphase::rayTest(rayFrom, rayTo, rayCallback); + for (int i=0; i <= m_LastLargeHandleIndex; i++) + { + btSimpleBroadphaseProxy* proxy = &m_pLargeHandles[i]; + rayCallback.process(proxy); + } +} + + + +// +// overrides for CPU version +// + + + +void btGpu3DGridBroadphase::prepareAABB() +{ + BT_PROFILE("prepareAABB"); + bt3DGrid3F1U* pBB = m_hAABB; + int i; + int new_largest_index = -1; + unsigned int num_small = 0; + for(i = 0; i <= m_LastHandleIndex; i++) + { + btSimpleBroadphaseProxy* proxy0 = &m_pHandles[i]; + new_largest_index = i; + pBB->fx = proxy0->m_aabbMin.getX(); + pBB->fy = proxy0->m_aabbMin.getY(); + pBB->fz = proxy0->m_aabbMin.getZ(); + pBB->uw = i; + pBB++; + pBB->fx = proxy0->m_aabbMax.getX(); + pBB->fy = proxy0->m_aabbMax.getY(); + pBB->fz = proxy0->m_aabbMax.getZ(); + pBB->uw = num_small; + pBB++; + num_small++; + } + m_LastHandleIndex = new_largest_index; + new_largest_index = -1; + unsigned int num_large = 0; + for(i = 0; i <= m_LastLargeHandleIndex; i++) + { + btSimpleBroadphaseProxy* proxy0 = &m_pLargeHandles[i]; + new_largest_index = i; + pBB->fx = proxy0->m_aabbMin.getX(); + pBB->fy = proxy0->m_aabbMin.getY(); + pBB->fz = proxy0->m_aabbMin.getZ(); + pBB->uw = i + m_maxHandles; + pBB++; + pBB->fx = proxy0->m_aabbMax.getX(); + pBB->fy = proxy0->m_aabbMax.getY(); + pBB->fz = proxy0->m_aabbMax.getZ(); + pBB->uw = num_large + m_maxHandles; + pBB++; + num_large++; + } + m_LastLargeHandleIndex = new_largest_index; + // paranoid checks + btAssert(num_small == m_numHandles); + btAssert(num_large == m_numLargeHandles); + return; +} + + + +void btGpu3DGridBroadphase::setParameters(bt3DGridBroadphaseParams* hostParams) +{ + s3DGridBroadphaseParams = *hostParams; + return; +} + + + +void btGpu3DGridBroadphase::calcHashAABB() +{ + BT_PROFILE("bt3DGrid_calcHashAABB"); + btGpu_calcHashAABB(m_hAABB, m_hBodiesHash, m_numHandles); + return; +} + + + +void btGpu3DGridBroadphase::sortHash() +{ + class bt3DGridHashKey + { + public: + unsigned int hash; + unsigned int index; + void quickSort(bt3DGridHashKey* pData, int lo, int hi) + { + int i=lo, j=hi; + bt3DGridHashKey x = pData[(lo+hi)/2]; + do + { + while(pData[i].hash > x.hash) i++; + while(x.hash > pData[j].hash) j--; + if(i <= j) + { + bt3DGridHashKey t = pData[i]; + pData[i] = pData[j]; + pData[j] = t; + i++; j--; + } + } while(i <= j); + if(lo < j) pData->quickSort(pData, lo, j); + if(i < hi) pData->quickSort(pData, i, hi); + } + }; + BT_PROFILE("bt3DGrid_sortHash"); + bt3DGridHashKey* pHash = (bt3DGridHashKey*)m_hBodiesHash; + pHash->quickSort(pHash, 0, m_numHandles - 1); + return; +} + + + +void btGpu3DGridBroadphase::findCellStart() +{ + BT_PROFILE("bt3DGrid_findCellStart"); + btGpu_findCellStart(m_hBodiesHash, m_hCellStart, m_numHandles, m_params.m_numCells); + return; +} + + + +void btGpu3DGridBroadphase::findOverlappingPairs() +{ + BT_PROFILE("bt3DGrid_findOverlappingPairs"); + btGpu_findOverlappingPairs(m_hAABB, m_hBodiesHash, m_hCellStart, m_hPairBuff, m_hPairBuffStartCurr, m_numHandles); + return; +} + + + +void btGpu3DGridBroadphase::findPairsLarge() +{ + BT_PROFILE("bt3DGrid_findPairsLarge"); + btGpu_findPairsLarge(m_hAABB, m_hBodiesHash, m_hCellStart, m_hPairBuff, m_hPairBuffStartCurr, m_numHandles, m_numLargeHandles); + return; +} + + + +void btGpu3DGridBroadphase::computePairCacheChanges() +{ + BT_PROFILE("bt3DGrid_computePairCacheChanges"); + btGpu_computePairCacheChanges(m_hPairBuff, m_hPairBuffStartCurr, m_hPairScanChanged, m_hAABB, m_numHandles); + return; +} + + +void btGpu3DGridBroadphase::scanOverlappingPairBuff(bool copyToCpu) +{ + BT_PROFILE("bt3DGrid_scanOverlappingPairBuff"); + unsigned int sum = 0; + m_hPairScanChanged[0]=0; + for(int i = 0; i <= m_numHandles+1; i++) + { + unsigned int delta = m_hPairScanChanged[i]; + m_hPairScanChanged[i] = sum; + sum += delta; + } + return; +} + + + +void btGpu3DGridBroadphase::squeezeOverlappingPairBuff() +{ + BT_PROFILE("bt3DGrid_squeezeOverlappingPairBuff"); + //btGpu_squeezeOverlappingPairBuff(m_hPairBuff, m_hPairBuffStartCurr, m_hPairScanChanged, m_hPairsChanged, m_hAABB, m_numHandles); + btGpu_squeezeOverlappingPairBuff(m_hPairBuff, m_hPairBuffStartCurr, m_hPairScanChanged, (unsigned int*)m_hAllOverlappingPairs, m_hAABB, m_numHandles); + + return; +} + + + +#include "btGpu3DGridBroadphaseSharedCode.h" + diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.h b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.h new file mode 100644 index 000000000..8441151f7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphase.h @@ -0,0 +1,154 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006, 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//---------------------------------------------------------------------------------------- + +#ifndef BTGPU3DGRIDBROADPHASE_H +#define BTGPU3DGRIDBROADPHASE_H + +//---------------------------------------------------------------------------------------- + +#include "BulletCollision/BroadphaseCollision/btSimpleBroadphase.h" + +#include "btGpu3DGridBroadphaseSharedTypes.h" +struct MyUint2 +{ + int x; + int y; +}; + +//---------------------------------------------------------------------------------------- + +///The btGpu3DGridBroadphase uses GPU-style code compiled for CPU to compute overlapping pairs + +class btGpu3DGridBroadphase : public btSimpleBroadphase +{ +protected: + bool m_bInitialized; + unsigned int m_numBodies; + unsigned int m_numCells; + unsigned int m_maxPairsPerBody; + unsigned int m_maxBodiesPerCell; + bt3DGridBroadphaseParams m_params; + btScalar m_maxRadius; + // CPU data + unsigned int* m_hBodiesHash; + unsigned int* m_hCellStart; + unsigned int* m_hPairBuffStartCurr; + bt3DGrid3F1U* m_hAABB; + unsigned int* m_hPairBuff; + unsigned int* m_hPairScanChanged; + unsigned int* m_hPairsChanged; + MyUint2* m_hAllOverlappingPairs; +// large proxies + int m_numLargeHandles; + int m_maxLargeHandles; + int m_LastLargeHandleIndex; + btSimpleBroadphaseProxy* m_pLargeHandles; + void* m_pLargeHandlesRawPtr; + int m_firstFreeLargeHandle; + int allocLargeHandle() + { + btAssert(m_numLargeHandles < m_maxLargeHandles); + int freeLargeHandle = m_firstFreeLargeHandle; + m_firstFreeLargeHandle = m_pLargeHandles[freeLargeHandle].GetNextFree(); + m_numLargeHandles++; + if(freeLargeHandle > m_LastLargeHandleIndex) + { + m_LastLargeHandleIndex = freeLargeHandle; + } + return freeLargeHandle; + } + void freeLargeHandle(btSimpleBroadphaseProxy* proxy) + { + int handle = int(proxy - m_pLargeHandles); + btAssert((handle >= 0) && (handle < m_maxHandles)); + if(handle == m_LastLargeHandleIndex) + { + m_LastLargeHandleIndex--; + } + proxy->SetNextFree(m_firstFreeLargeHandle); + m_firstFreeLargeHandle = handle; + proxy->m_clientObject = 0; + m_numLargeHandles--; + } + bool isLargeProxy(const btVector3& aabbMin, const btVector3& aabbMax); + bool isLargeProxy(btBroadphaseProxy* proxy); +// debug + unsigned int m_numPairsAdded; + unsigned int m_numPairsRemoved; + unsigned int m_numOverflows; +// +public: + virtual int getNumOverlap() + { + return m_hPairScanChanged[m_numHandles+1]; + } + virtual MyUint2* getOverlap() + { + return m_hAllOverlappingPairs; + } + // NOTE : for better results gridSizeX, gridSizeY and gridSizeZ should be powers of 2 + btGpu3DGridBroadphase(const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerBody, + btScalar maxSmallProxySize, + int maxBodiesPerCell = 8); + btGpu3DGridBroadphase( btOverlappingPairCache* overlappingPairCache, + const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerBody, + btScalar maxSmallProxySize, + int maxBodiesPerCell = 8); + virtual ~btGpu3DGridBroadphase(); + virtual void calculateOverlappingPairs(btDispatcher* dispatcher); + + virtual btBroadphaseProxy* createProxy(const btVector3& aabbMin, const btVector3& aabbMax,int shapeType,void* userPtr ,short int collisionFilterGroup,short int collisionFilterMask, btDispatcher* dispatcher,void* multiSapProxy); + virtual void destroyProxy(btBroadphaseProxy* proxy,btDispatcher* dispatcher); + virtual void rayTest(const btVector3& rayFrom,const btVector3& rayTo, btBroadphaseRayCallback& rayCallback); + virtual void resetPool(btDispatcher* dispatcher); + + static int getFloorPowOfTwo(int val); // returns 2^n : 2^(n+1) > val >= 2^n + +protected: + void _initialize( const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerBody, + btScalar maxSmallProxySize, + int maxBodiesPerCell); + void _finalize(); + void addPairsToCache(btDispatcher* dispatcher); + void addLarge2LargePairsToCache(btDispatcher* dispatcher); + +// overrides for CPU version + virtual void setParameters(bt3DGridBroadphaseParams* hostParams); + virtual void prepareAABB(); + virtual void calcHashAABB(); + virtual void sortHash(); + virtual void findCellStart(); + virtual void findOverlappingPairs(); + virtual void findPairsLarge(); + virtual void computePairCacheChanges(); + virtual void scanOverlappingPairBuff(bool copyToCpu=true); + virtual void squeezeOverlappingPairBuff(); +}; + +//---------------------------------------------------------------------------------------- + +#endif //BTGPU3DGRIDBROADPHASE_H + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedCode.h b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedCode.h new file mode 100644 index 000000000..08df68a65 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedCode.h @@ -0,0 +1,428 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006, 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//---------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +// K E R N E L F U N C T I O N S +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- + +// calculate position in uniform grid +BT_GPU___device__ int3 bt3DGrid_calcGridPos(float4 p) +{ + int3 gridPos; + gridPos.x = (int)floor(p.x * BT_GPU_params.m_invCellSizeX) & (BT_GPU_params.m_gridSizeX - 1); + gridPos.y = (int)floor(p.y * BT_GPU_params.m_invCellSizeY) & (BT_GPU_params.m_gridSizeY - 1); + gridPos.z = (int)floor(p.z * BT_GPU_params.m_invCellSizeZ) & (BT_GPU_params.m_gridSizeZ - 1); + return gridPos; +} // bt3DGrid_calcGridPos() + +//---------------------------------------------------------------------------------------- + +// calculate address in grid from position (clamping to edges) +BT_GPU___device__ uint bt3DGrid_calcGridHash(int3 gridPos) +{ + gridPos.x &= (BT_GPU_params.m_gridSizeX - 1); + gridPos.y &= (BT_GPU_params.m_gridSizeY - 1); + gridPos.z &= (BT_GPU_params.m_gridSizeZ - 1); + return BT_GPU___mul24(BT_GPU___mul24(gridPos.z, BT_GPU_params.m_gridSizeY), BT_GPU_params.m_gridSizeX) + BT_GPU___mul24(gridPos.y, BT_GPU_params.m_gridSizeX) + gridPos.x; +} // bt3DGrid_calcGridHash() + +//---------------------------------------------------------------------------------------- + +// calculate grid hash value for each body using its AABB +BT_GPU___global__ void calcHashAABBD(bt3DGrid3F1U* pAABB, uint2* pHash, uint numBodies) +{ + int index = BT_GPU___mul24(BT_GPU_blockIdx.x, BT_GPU_blockDim.x) + BT_GPU_threadIdx.x; + if(index >= (int)numBodies) + { + return; + } + bt3DGrid3F1U bbMin = pAABB[index*2]; + bt3DGrid3F1U bbMax = pAABB[index*2 + 1]; + float4 pos; + pos.x = (bbMin.fx + bbMax.fx) * 0.5f; + pos.y = (bbMin.fy + bbMax.fy) * 0.5f; + pos.z = (bbMin.fz + bbMax.fz) * 0.5f; + // get address in grid + int3 gridPos = bt3DGrid_calcGridPos(pos); + uint gridHash = bt3DGrid_calcGridHash(gridPos); + // store grid hash and body index + pHash[index] = BT_GPU_make_uint2(gridHash, index); +} // calcHashAABBD() + +//---------------------------------------------------------------------------------------- + +BT_GPU___global__ void findCellStartD(uint2* pHash, uint* cellStart, uint numBodies) +{ + int index = BT_GPU___mul24(BT_GPU_blockIdx.x, BT_GPU_blockDim.x) + BT_GPU_threadIdx.x; + if(index >= (int)numBodies) + { + return; + } + uint2 sortedData = pHash[index]; + // Load hash data into shared memory so that we can look + // at neighboring body's hash value without loading + // two hash values per thread + BT_GPU___shared__ uint sharedHash[257]; + sharedHash[BT_GPU_threadIdx.x+1] = sortedData.x; + if((index > 0) && (BT_GPU_threadIdx.x == 0)) + { + // first thread in block must load neighbor body hash + volatile uint2 prevData = pHash[index-1]; + sharedHash[0] = prevData.x; + } + BT_GPU___syncthreads(); + if((index == 0) || (sortedData.x != sharedHash[BT_GPU_threadIdx.x])) + { + cellStart[sortedData.x] = index; + } +} // findCellStartD() + +//---------------------------------------------------------------------------------------- + +BT_GPU___device__ uint cudaTestAABBOverlap(bt3DGrid3F1U min0, bt3DGrid3F1U max0, bt3DGrid3F1U min1, bt3DGrid3F1U max1) +{ + return (min0.fx <= max1.fx)&& (min1.fx <= max0.fx) && + (min0.fy <= max1.fy)&& (min1.fy <= max0.fy) && + (min0.fz <= max1.fz)&& (min1.fz <= max0.fz); +} // cudaTestAABBOverlap() + +//---------------------------------------------------------------------------------------- + +BT_GPU___device__ void findPairsInCell( int3 gridPos, + uint index, + uint2* pHash, + uint* pCellStart, + bt3DGrid3F1U* pAABB, + uint* pPairBuff, + uint2* pPairBuffStartCurr, + uint numBodies) +{ + uint gridHash = bt3DGrid_calcGridHash(gridPos); + // get start of bucket for this cell + uint bucketStart = pCellStart[gridHash]; + if (bucketStart == 0xffffffff) + { + return; // cell empty + } + // iterate over bodies in this cell + uint2 sortedData = pHash[index]; + uint unsorted_indx = sortedData.y; + bt3DGrid3F1U min0 = BT_GPU_FETCH(pAABB, unsorted_indx*2); + bt3DGrid3F1U max0 = BT_GPU_FETCH(pAABB, unsorted_indx*2 + 1); + uint handleIndex = min0.uw; + uint2 start_curr = pPairBuffStartCurr[handleIndex]; + uint start = start_curr.x; + uint curr = start_curr.y; + uint2 start_curr_next = pPairBuffStartCurr[handleIndex+1]; + uint curr_max = start_curr_next.x - start - 1; + uint bucketEnd = bucketStart + BT_GPU_params.m_maxBodiesPerCell; + bucketEnd = (bucketEnd > numBodies) ? numBodies : bucketEnd; + for(uint index2 = bucketStart; index2 < bucketEnd; index2++) + { + uint2 cellData = pHash[index2]; + if (cellData.x != gridHash) + { + break; // no longer in same bucket + } + uint unsorted_indx2 = cellData.y; + if (unsorted_indx2 < unsorted_indx) // check not colliding with self + { + bt3DGrid3F1U min1 = BT_GPU_FETCH(pAABB, unsorted_indx2*2); + bt3DGrid3F1U max1 = BT_GPU_FETCH(pAABB, unsorted_indx2*2 + 1); + if(cudaTestAABBOverlap(min0, max0, min1, max1)) + { + uint handleIndex2 = min1.uw; + uint k; + for(k = 0; k < curr; k++) + { + uint old_pair = pPairBuff[start+k] & (~BT_3DGRID_PAIR_ANY_FLG); + if(old_pair == handleIndex2) + { + pPairBuff[start+k] |= BT_3DGRID_PAIR_FOUND_FLG; + break; + } + } + if(k == curr) + { + if(curr >= curr_max) + { // not a good solution, but let's avoid crash + break; + } + pPairBuff[start+curr] = handleIndex2 | BT_3DGRID_PAIR_NEW_FLG; + curr++; + } + } + } + } + pPairBuffStartCurr[handleIndex] = BT_GPU_make_uint2(start, curr); + return; +} // findPairsInCell() + +//---------------------------------------------------------------------------------------- + +BT_GPU___global__ void findOverlappingPairsD( bt3DGrid3F1U* pAABB, uint2* pHash, uint* pCellStart, + uint* pPairBuff, uint2* pPairBuffStartCurr, uint numBodies) +{ + int index = BT_GPU___mul24(BT_GPU_blockIdx.x, BT_GPU_blockDim.x) + BT_GPU_threadIdx.x; + if(index >= (int)numBodies) + { + return; + } + uint2 sortedData = pHash[index]; + uint unsorted_indx = sortedData.y; + bt3DGrid3F1U bbMin = BT_GPU_FETCH(pAABB, unsorted_indx*2); + bt3DGrid3F1U bbMax = BT_GPU_FETCH(pAABB, unsorted_indx*2 + 1); + float4 pos; + pos.x = (bbMin.fx + bbMax.fx) * 0.5f; + pos.y = (bbMin.fy + bbMax.fy) * 0.5f; + pos.z = (bbMin.fz + bbMax.fz) * 0.5f; + // get address in grid + int3 gridPos = bt3DGrid_calcGridPos(pos); + // examine only neighbouring cells + for(int z=-1; z<=1; z++) { + for(int y=-1; y<=1; y++) { + for(int x=-1; x<=1; x++) { + findPairsInCell(gridPos + BT_GPU_make_int3(x, y, z), index, pHash, pCellStart, pAABB, pPairBuff, pPairBuffStartCurr, numBodies); + } + } + } +} // findOverlappingPairsD() + +//---------------------------------------------------------------------------------------- + +BT_GPU___global__ void findPairsLargeD( bt3DGrid3F1U* pAABB, uint2* pHash, uint* pCellStart, uint* pPairBuff, + uint2* pPairBuffStartCurr, uint numBodies, uint numLarge) +{ + int index = BT_GPU___mul24(BT_GPU_blockIdx.x, BT_GPU_blockDim.x) + BT_GPU_threadIdx.x; + if(index >= (int)numBodies) + { + return; + } + uint2 sortedData = pHash[index]; + uint unsorted_indx = sortedData.y; + bt3DGrid3F1U min0 = BT_GPU_FETCH(pAABB, unsorted_indx*2); + bt3DGrid3F1U max0 = BT_GPU_FETCH(pAABB, unsorted_indx*2 + 1); + uint handleIndex = min0.uw; + uint2 start_curr = pPairBuffStartCurr[handleIndex]; + uint start = start_curr.x; + uint curr = start_curr.y; + uint2 start_curr_next = pPairBuffStartCurr[handleIndex+1]; + uint curr_max = start_curr_next.x - start - 1; + for(uint i = 0; i < numLarge; i++) + { + uint indx2 = numBodies + i; + bt3DGrid3F1U min1 = BT_GPU_FETCH(pAABB, indx2*2); + bt3DGrid3F1U max1 = BT_GPU_FETCH(pAABB, indx2*2 + 1); + if(cudaTestAABBOverlap(min0, max0, min1, max1)) + { + uint k; + uint handleIndex2 = min1.uw; + for(k = 0; k < curr; k++) + { + uint old_pair = pPairBuff[start+k] & (~BT_3DGRID_PAIR_ANY_FLG); + if(old_pair == handleIndex2) + { + pPairBuff[start+k] |= BT_3DGRID_PAIR_FOUND_FLG; + break; + } + } + if(k == curr) + { + pPairBuff[start+curr] = handleIndex2 | BT_3DGRID_PAIR_NEW_FLG; + if(curr >= curr_max) + { // not a good solution, but let's avoid crash + break; + } + curr++; + } + } + } + pPairBuffStartCurr[handleIndex] = BT_GPU_make_uint2(start, curr); + return; +} // findPairsLargeD() + +//---------------------------------------------------------------------------------------- + +BT_GPU___global__ void computePairCacheChangesD(uint* pPairBuff, uint2* pPairBuffStartCurr, + uint* pPairScan, bt3DGrid3F1U* pAABB, uint numBodies) +{ + int index = BT_GPU___mul24(BT_GPU_blockIdx.x, BT_GPU_blockDim.x) + BT_GPU_threadIdx.x; + if(index >= (int)numBodies) + { + return; + } + bt3DGrid3F1U bbMin = pAABB[index * 2]; + uint handleIndex = bbMin.uw; + uint2 start_curr = pPairBuffStartCurr[handleIndex]; + uint start = start_curr.x; + uint curr = start_curr.y; + uint *pInp = pPairBuff + start; + uint num_changes = 0; + for(uint k = 0; k < curr; k++, pInp++) + { + //if(!((*pInp) & BT_3DGRID_PAIR_FOUND_FLG)) + if(((*pInp) & BT_3DGRID_PAIR_ANY_FLG)) + { + num_changes++; + } + } + pPairScan[index+1] = num_changes; +} // computePairCacheChangesD() + +//---------------------------------------------------------------------------------------- + +BT_GPU___global__ void squeezeOverlappingPairBuffD(uint* pPairBuff, uint2* pPairBuffStartCurr, uint* pPairScan, + uint2* pPairOut, bt3DGrid3F1U* pAABB, uint numBodies) +{ + int index = BT_GPU___mul24(BT_GPU_blockIdx.x, BT_GPU_blockDim.x) + BT_GPU_threadIdx.x; + if(index >= (int)numBodies) + { + return; + } + bt3DGrid3F1U bbMin = pAABB[index * 2]; + uint handleIndex = bbMin.uw; + uint2 start_curr = pPairBuffStartCurr[handleIndex]; + uint start = start_curr.x; + uint curr = start_curr.y; + uint* pInp = pPairBuff + start; + uint2* pOut = pPairOut + pPairScan[index+1]; + uint* pOut2 = pInp; + uint num = 0; + for(uint k = 0; k < curr; k++, pInp++) + { + if((*pInp) & BT_3DGRID_PAIR_ANY_FLG) + //if(!((*pInp) & BT_3DGRID_PAIR_FOUND_FLG)) + { + pOut->x = handleIndex; + pOut->y = (*pInp) & (~BT_3DGRID_PAIR_ANY_FLG); + + pOut++; + } + if((*pInp) & BT_3DGRID_PAIR_ANY_FLG) + { + *pOut2 = (*pInp) & (~BT_3DGRID_PAIR_ANY_FLG); + pOut2++; + num++; + } + } + pPairBuffStartCurr[handleIndex] = BT_GPU_make_uint2(start, num); +} // squeezeOverlappingPairBuffD() + + +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +// E N D O F K E R N E L F U N C T I O N S +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------- + +extern "C" +{ + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(calcHashAABB)(bt3DGrid3F1U* pAABB, unsigned int* hash, unsigned int numBodies) +{ + int numThreads, numBlocks; + BT_GPU_PREF(computeGridSize)(numBodies, 256, numBlocks, numThreads); + // execute the kernel + BT_GPU_EXECKERNEL(numBlocks, numThreads, calcHashAABBD, (pAABB, (uint2*)hash, numBodies)); + // check if kernel invocation generated an error + BT_GPU_CHECK_ERROR("calcHashAABBD kernel execution failed"); +} // calcHashAABB() + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(findCellStart(unsigned int* hash, unsigned int* cellStart, unsigned int numBodies, unsigned int numCells)) +{ + int numThreads, numBlocks; + BT_GPU_PREF(computeGridSize)(numBodies, 256, numBlocks, numThreads); + BT_GPU_SAFE_CALL(BT_GPU_Memset(cellStart, 0xffffffff, numCells*sizeof(uint))); + BT_GPU_EXECKERNEL(numBlocks, numThreads, findCellStartD, ((uint2*)hash, (uint*)cellStart, numBodies)); + BT_GPU_CHECK_ERROR("Kernel execution failed: findCellStartD"); +} // findCellStart() + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(findOverlappingPairs(bt3DGrid3F1U* pAABB, unsigned int* pHash, unsigned int* pCellStart, unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int numBodies)) +{ +#if B_CUDA_USE_TEX + BT_GPU_SAFE_CALL(cudaBindTexture(0, pAABBTex, pAABB, numBodies * 2 * sizeof(bt3DGrid3F1U))); +#endif + int numThreads, numBlocks; + BT_GPU_PREF(computeGridSize)(numBodies, 64, numBlocks, numThreads); + BT_GPU_EXECKERNEL(numBlocks, numThreads, findOverlappingPairsD, (pAABB,(uint2*)pHash,(uint*)pCellStart,(uint*)pPairBuff,(uint2*)pPairBuffStartCurr,numBodies)); + BT_GPU_CHECK_ERROR("Kernel execution failed: bt_CudaFindOverlappingPairsD"); +#if B_CUDA_USE_TEX + BT_GPU_SAFE_CALL(cudaUnbindTexture(pAABBTex)); +#endif +} // findOverlappingPairs() + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(findPairsLarge(bt3DGrid3F1U* pAABB, unsigned int* pHash, unsigned int* pCellStart, unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int numBodies, unsigned int numLarge)) +{ +#if B_CUDA_USE_TEX + BT_GPU_SAFE_CALL(cudaBindTexture(0, pAABBTex, pAABB, (numBodies+numLarge) * 2 * sizeof(bt3DGrid3F1U))); +#endif + int numThreads, numBlocks; + BT_GPU_PREF(computeGridSize)(numBodies, 64, numBlocks, numThreads); + BT_GPU_EXECKERNEL(numBlocks, numThreads, findPairsLargeD, (pAABB,(uint2*)pHash,(uint*)pCellStart,(uint*)pPairBuff,(uint2*)pPairBuffStartCurr,numBodies,numLarge)); + BT_GPU_CHECK_ERROR("Kernel execution failed: btCuda_findPairsLargeD"); +#if B_CUDA_USE_TEX + BT_GPU_SAFE_CALL(cudaUnbindTexture(pAABBTex)); +#endif +} // findPairsLarge() + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(computePairCacheChanges(unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int* pPairScan, bt3DGrid3F1U* pAABB, unsigned int numBodies)) +{ + int numThreads, numBlocks; + BT_GPU_PREF(computeGridSize)(numBodies, 256, numBlocks, numThreads); + BT_GPU_EXECKERNEL(numBlocks, numThreads, computePairCacheChangesD, ((uint*)pPairBuff,(uint2*)pPairBuffStartCurr,(uint*)pPairScan,pAABB,numBodies)); + BT_GPU_CHECK_ERROR("Kernel execution failed: btCudaComputePairCacheChangesD"); +} // computePairCacheChanges() + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(squeezeOverlappingPairBuff(unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int* pPairScan, unsigned int* pPairOut, bt3DGrid3F1U* pAABB, unsigned int numBodies)) +{ + int numThreads, numBlocks; + BT_GPU_PREF(computeGridSize)(numBodies, 256, numBlocks, numThreads); + BT_GPU_EXECKERNEL(numBlocks, numThreads, squeezeOverlappingPairBuffD, ((uint*)pPairBuff,(uint2*)pPairBuffStartCurr,(uint*)pPairScan,(uint2*)pPairOut,pAABB,numBodies)); + BT_GPU_CHECK_ERROR("Kernel execution failed: btCudaSqueezeOverlappingPairBuffD"); +} // btCuda_squeezeOverlappingPairBuff() + +//------------------------------------------------------------------------------------------------ + +} // extern "C" + +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedDefs.h b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedDefs.h new file mode 100644 index 000000000..607bda7ed --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedDefs.h @@ -0,0 +1,61 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006, 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//---------------------------------------------------------------------------------------- + +// Shared definitions for GPU-based 3D Grid collision detection broadphase + +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// Keep this file free from Bullet headers +// it is included into both CUDA and CPU code +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +//---------------------------------------------------------------------------------------- + +#ifndef BTGPU3DGRIDBROADPHASESHAREDDEFS_H +#define BTGPU3DGRIDBROADPHASESHAREDDEFS_H + +//---------------------------------------------------------------------------------------- + +#include "btGpu3DGridBroadphaseSharedTypes.h" + +//---------------------------------------------------------------------------------------- + +extern "C" +{ + +//---------------------------------------------------------------------------------------- + +void BT_GPU_PREF(calcHashAABB)(bt3DGrid3F1U* pAABB, unsigned int* hash, unsigned int numBodies); + +void BT_GPU_PREF(findCellStart)(unsigned int* hash, unsigned int* cellStart, unsigned int numBodies, unsigned int numCells); + +void BT_GPU_PREF(findOverlappingPairs)(bt3DGrid3F1U* pAABB, unsigned int* pHash, unsigned int* pCellStart, unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int numBodies); + +void BT_GPU_PREF(findPairsLarge)(bt3DGrid3F1U* pAABB, unsigned int* pHash, unsigned int* pCellStart, unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int numBodies, unsigned int numLarge); + +void BT_GPU_PREF(computePairCacheChanges)(unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int* pPairScan, bt3DGrid3F1U* pAABB, unsigned int numBodies); + +void BT_GPU_PREF(squeezeOverlappingPairBuff)(unsigned int* pPairBuff, unsigned int* pPairBuffStartCurr, unsigned int* pPairScan, unsigned int* pPairOut, bt3DGrid3F1U* pAABB, unsigned int numBodies); + + +//---------------------------------------------------------------------------------------- + +} // extern "C" + +//---------------------------------------------------------------------------------------- + +#endif // BTGPU3DGRIDBROADPHASESHAREDDEFS_H + diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedTypes.h b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedTypes.h new file mode 100644 index 000000000..5b2e65ec0 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpu3DGridBroadphaseSharedTypes.h @@ -0,0 +1,64 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006, 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//---------------------------------------------------------------------------------------- + +// Shared definitions for GPU-based 3D Grid collision detection broadphase + +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// Keep this file free from Bullet headers +// it is included into both CUDA and CPU code +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +//---------------------------------------------------------------------------------------- + +#ifndef BTGPU3DGRIDBROADPHASESHAREDTYPES_H +#define BTGPU3DGRIDBROADPHASESHAREDTYPES_H + +//---------------------------------------------------------------------------------------- + +#define BT_3DGRID_PAIR_FOUND_FLG (0x40000000) +#define BT_3DGRID_PAIR_NEW_FLG (0x20000000) +#define BT_3DGRID_PAIR_ANY_FLG (BT_3DGRID_PAIR_FOUND_FLG | BT_3DGRID_PAIR_NEW_FLG) + +//---------------------------------------------------------------------------------------- + +struct bt3DGridBroadphaseParams +{ + unsigned int m_gridSizeX; + unsigned int m_gridSizeY; + unsigned int m_gridSizeZ; + unsigned int m_numCells; + float m_invCellSizeX; + float m_invCellSizeY; + float m_invCellSizeZ; + unsigned int m_numBodies; + unsigned int m_maxBodiesPerCell; +}; + +//---------------------------------------------------------------------------------------- + +struct bt3DGrid3F1U +{ + float fx; + float fy; + float fz; + unsigned int uw; +}; + +//---------------------------------------------------------------------------------------- + +#endif // BTGPU3DGRIDBROADPHASESHAREDTYPES_H + diff --git a/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpuDefines.h b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpuDefines.h new file mode 100644 index 000000000..f9315ab64 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/3dGridBroadphase/Shared/btGpuDefines.h @@ -0,0 +1,211 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006, 2009 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + + +// definitions for "GPU on CPU" code + + +#ifndef BT_GPU_DEFINES_H +#define BT_GPU_DEFINES_H + +typedef unsigned int uint; + +struct int2 +{ + int x, y; +}; + +struct uint2 +{ + unsigned int x, y; +}; + +struct int3 +{ + int x, y, z; +}; + +struct uint3 +{ + unsigned int x, y, z; +}; + +struct float4 +{ + float x, y, z, w; +}; + +struct float3 +{ + float x, y, z; +}; + + +#define BT_GPU___device__ inline +#define BT_GPU___devdata__ +#define BT_GPU___constant__ +#define BT_GPU_max(a, b) ((a) > (b) ? (a) : (b)) +#define BT_GPU_min(a, b) ((a) < (b) ? (a) : (b)) +#define BT_GPU_params s3DGridBroadphaseParams +#define BT_GPU___mul24(a, b) ((a)*(b)) +#define BT_GPU___global__ inline +#define BT_GPU___shared__ static +#define BT_GPU___syncthreads() +#define CUDART_PI_F SIMD_PI + +static inline uint2 bt3dGrid_make_uint2(unsigned int x, unsigned int y) +{ + uint2 t; t.x = x; t.y = y; return t; +} +#define BT_GPU_make_uint2(x, y) bt3dGrid_make_uint2(x, y) + +static inline int3 bt3dGrid_make_int3(int x, int y, int z) +{ + int3 t; t.x = x; t.y = y; t.z = z; return t; +} +#define BT_GPU_make_int3(x, y, z) bt3dGrid_make_int3(x, y, z) + +static inline float3 bt3dGrid_make_float3(float x, float y, float z) +{ + float3 t; t.x = x; t.y = y; t.z = z; return t; +} +#define BT_GPU_make_float3(x, y, z) bt3dGrid_make_float3(x, y, z) + +static inline float3 bt3dGrid_make_float34(float4 f) +{ + float3 t; t.x = f.x; t.y = f.y; t.z = f.z; return t; +} +#define BT_GPU_make_float34(f) bt3dGrid_make_float34(f) + +static inline float3 bt3dGrid_make_float31(float f) +{ + float3 t; t.x = t.y = t.z = f; return t; +} +#define BT_GPU_make_float31(x) bt3dGrid_make_float31(x) + +static inline float4 bt3dGrid_make_float42(float3 v, float f) +{ + float4 t; t.x = v.x; t.y = v.y; t.z = v.z; t.w = f; return t; +} +#define BT_GPU_make_float42(a, b) bt3dGrid_make_float42(a, b) + +static inline float4 bt3dGrid_make_float44(float a, float b, float c, float d) +{ + float4 t; t.x = a; t.y = b; t.z = c; t.w = d; return t; +} +#define BT_GPU_make_float44(a, b, c, d) bt3dGrid_make_float44(a, b, c, d) + +inline int3 operator+(int3 a, int3 b) +{ + return bt3dGrid_make_int3(a.x + b.x, a.y + b.y, a.z + b.z); +} + +inline float4 operator+(const float4& a, const float4& b) +{ + float4 r; r.x = a.x+b.x; r.y = a.y+b.y; r.z = a.z+b.z; r.w = a.w+b.w; return r; +} +inline float4 operator*(const float4& a, float fact) +{ + float4 r; r.x = a.x*fact; r.y = a.y*fact; r.z = a.z*fact; r.w = a.w*fact; return r; +} +inline float4 operator*(float fact, float4& a) +{ + return (a * fact); +} +inline float4& operator*=(float4& a, float fact) +{ + a = fact * a; + return a; +} +inline float4& operator+=(float4& a, const float4& b) +{ + a = a + b; + return a; +} + +inline float3 operator+(const float3& a, const float3& b) +{ + float3 r; r.x = a.x+b.x; r.y = a.y+b.y; r.z = a.z+b.z; return r; +} +inline float3 operator-(const float3& a, const float3& b) +{ + float3 r; r.x = a.x-b.x; r.y = a.y-b.y; r.z = a.z-b.z; return r; +} +static inline float bt3dGrid_dot(float3& a, float3& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z; +} +#define BT_GPU_dot(a,b) bt3dGrid_dot(a,b) + +static inline float bt3dGrid_dot4(float4& a, float4& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w; +} +#define BT_GPU_dot4(a,b) bt3dGrid_dot4(a,b) + +static inline float3 bt3dGrid_cross(const float3& a, const float3& b) +{ + float3 r; r.x = a.y*b.z-a.z*b.y; r.y = -a.x*b.z+a.z*b.x; r.z = a.x*b.y-a.y*b.x; return r; +} +#define BT_GPU_cross(a,b) bt3dGrid_cross(a,b) + + +inline float3 operator*(const float3& a, float fact) +{ + float3 r; r.x = a.x*fact; r.y = a.y*fact; r.z = a.z*fact; return r; +} + + +inline float3& operator+=(float3& a, const float3& b) +{ + a = a + b; + return a; +} +inline float3& operator-=(float3& a, const float3& b) +{ + a = a - b; + return a; +} +inline float3& operator*=(float3& a, float fact) +{ + a = a * fact; + return a; +} +inline float3 operator-(const float3& v) +{ + float3 r; r.x = -v.x; r.y = -v.y; r.z = -v.z; return r; +} + + +#define BT_GPU_FETCH(a, b) a[b] +#define BT_GPU_FETCH4(a, b) a[b] +#define BT_GPU_PREF(func) btGpu_##func +#define BT_GPU_SAFE_CALL(func) func +#define BT_GPU_Memset memset +#define BT_GPU_MemcpyToSymbol(a, b, c) memcpy(&a, b, c) +#define BT_GPU_BindTexture(a, b, c, d) +#define BT_GPU_UnbindTexture(a) + +static uint2 s_blockIdx, s_blockDim, s_threadIdx; +#define BT_GPU_blockIdx s_blockIdx +#define BT_GPU_blockDim s_blockDim +#define BT_GPU_threadIdx s_threadIdx +#define BT_GPU_EXECKERNEL(numb, numt, kfunc, args) {s_blockDim.x=numt;for(int nb=0;nb +#else +#include +#endif +#else +#ifdef USE_MINICL +#include +#else +#include +#ifdef _WIN32 +#include "CL/cl_gl.h" +#endif //_WIN32 +#endif +#endif //__APPLE__ + +#include +#include +#define oclCHECKERROR(a, b) if((a)!=(b)) { printf("OCL Error : %d\n", (a)); assert((a) == (b)); } + + +#endif //BT_OPENCL_INCLUDE_H + diff --git a/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.cpp b/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.cpp new file mode 100644 index 000000000..e278401e9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.cpp @@ -0,0 +1,731 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006 - 2011 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//original author: Roman Ponomarev +//cleanup by Erwin Coumans + +#include + +#include "btOpenCLUtils.h" +#include +#include + +#define BT_MAX_CL_DEVICES 16 //who needs 16 devices? + +#ifdef _WIN32 +#include +#include + +#define btAssert assert +#endif + +//Set the preferred platform vendor using the OpenCL SDK +static char* spPlatformVendor = +#if defined(CL_PLATFORM_MINI_CL) +"MiniCL, SCEA"; +#elif defined(CL_PLATFORM_AMD) +"Advanced Micro Devices, Inc."; +#elif defined(CL_PLATFORM_NVIDIA) +"NVIDIA Corporation"; +#elif defined(CL_PLATFORM_INTEL) +"Intel(R) Corporation"; +#else +"Unknown Vendor"; +#endif + +#ifndef CL_PLATFORM_MINI_CL +#ifdef _WIN32 +#include "CL/cl_gl.h" +#endif //_WIN32 +#endif + +int btOpenCLUtils::getNumPlatforms(cl_int* pErrNum) +{ + cl_uint numPlatforms=0; + cl_int ciErrNum = clGetPlatformIDs(0, NULL, &numPlatforms); + + if(ciErrNum != CL_SUCCESS) + { + if(pErrNum != NULL) + *pErrNum = ciErrNum; + } + return numPlatforms; +} + +const char* btOpenCLUtils::getSdkVendorName() +{ + return spPlatformVendor; +} + +cl_platform_id btOpenCLUtils::getPlatform(int platformIndex, cl_int* pErrNum) +{ + cl_platform_id platform = 0; + + cl_uint numPlatforms; + cl_int ciErrNum = clGetPlatformIDs(0, NULL, &numPlatforms); + + if (platformIndex>=0 && platformIndex=0 && preferredDeviceIndex 0) + { + cl_platform_id* platforms = new cl_platform_id[numPlatforms]; + ciErrNum = clGetPlatformIDs(numPlatforms, platforms, NULL); + if(ciErrNum != CL_SUCCESS) + { + if(pErrNum != NULL) *pErrNum = ciErrNum; + return NULL; + } + int i; + + + for ( i = 0; i < numPlatforms; ++i) + { + char pbuf[128]; + ciErrNum = clGetPlatformInfo( platforms[i], + CL_PLATFORM_VENDOR, + sizeof(pbuf), + pbuf, + NULL); + if(ciErrNum != CL_SUCCESS) + { + if(pErrNum != NULL) *pErrNum = ciErrNum; + return NULL; + } + + if (preferredPlatformIndex>=0 && i==preferredPlatformIndex) + { + cl_platform_id tmpPlatform = platforms[0]; + platforms[0] = platforms[i]; + platforms[i] = tmpPlatform; + break; + } else + { + if(!strcmp(pbuf, spPlatformVendor)) + { + cl_platform_id tmpPlatform = platforms[0]; + platforms[0] = platforms[i]; + platforms[i] = tmpPlatform; + break; + } + } + } + + for (i = 0; i < numPlatforms; ++i) + { + cl_platform_id platform = platforms[i]; + assert(platform); + + retContext = btOpenCLUtils::createContextFromPlatform(platform,deviceType,pErrNum,pGLContext,pGLDC,preferredDeviceIndex); + + if (retContext) + { +// printf("OpenCL platform details:\n"); + btOpenCLPlatformInfo platformInfo; + + btOpenCLUtils::getPlatformInfo(platform, platformInfo); + + printf(" CL_PLATFORM_VENDOR: \t\t\t%s\n",platformInfo.m_platformVendor); + printf(" CL_PLATFORM_NAME: \t\t\t%s\n",platformInfo.m_platformName); + printf(" CL_PLATFORM_VERSION: \t\t\t%s\n",platformInfo.m_platformVersion); + + break; + } + } + + delete[] platforms; + } + return retContext; +} + + +////////////////////////////////////////////////////////////////////////////// +//! Gets the id of the nth device from the context +//! +//! @return the id or -1 when out of range +//! @param cxMainContext OpenCL context +//! @param device_idx index of the device of interest +////////////////////////////////////////////////////////////////////////////// +cl_device_id btOpenCLUtils::getDevice(cl_context cxMainContext, int deviceIndex) +{ + size_t szParmDataBytes; + cl_device_id* cdDevices; + + // get the list of devices associated with context + clGetContextInfo(cxMainContext, CL_CONTEXT_DEVICES, 0, NULL, &szParmDataBytes); + + if( szParmDataBytes / sizeof(cl_device_id) < deviceIndex ) { + return (cl_device_id)-1; + } + + cdDevices = (cl_device_id*) malloc(szParmDataBytes); + + clGetContextInfo(cxMainContext, CL_CONTEXT_DEVICES, szParmDataBytes, cdDevices, NULL); + + cl_device_id device = cdDevices[deviceIndex]; + free(cdDevices); + + return device; +} + +int btOpenCLUtils::getNumDevices(cl_context cxMainContext) +{ + size_t szParamDataBytes; + clGetContextInfo(cxMainContext, CL_CONTEXT_DEVICES, 0, NULL, &szParamDataBytes); + int device_count = (int) szParamDataBytes/ sizeof(cl_device_id); + return device_count; +} + +void btOpenCLUtils::printDeviceInfo(cl_device_id device) +{ + btOpenCLDeviceInfo info; + getDeviceInfo(device,info); + + printf(" CL_DEVICE_NAME: \t\t\t%s\n", info.m_deviceName); + printf(" CL_DEVICE_VENDOR: \t\t\t%s\n", info.m_deviceVendor); + printf(" CL_DRIVER_VERSION: \t\t\t%s\n", info.m_driverVersion); + + if( info.m_deviceType & CL_DEVICE_TYPE_CPU ) + printf(" CL_DEVICE_TYPE:\t\t\t%s\n", "CL_DEVICE_TYPE_CPU"); + if( info.m_deviceType & CL_DEVICE_TYPE_GPU ) + printf(" CL_DEVICE_TYPE:\t\t\t%s\n", "CL_DEVICE_TYPE_GPU"); + if( info.m_deviceType & CL_DEVICE_TYPE_ACCELERATOR ) + printf(" CL_DEVICE_TYPE:\t\t\t%s\n", "CL_DEVICE_TYPE_ACCELERATOR"); + if( info.m_deviceType & CL_DEVICE_TYPE_DEFAULT ) + printf(" CL_DEVICE_TYPE:\t\t\t%s\n", "CL_DEVICE_TYPE_DEFAULT"); + + printf(" CL_DEVICE_MAX_COMPUTE_UNITS:\t\t%u\n", info.m_computeUnits); + printf(" CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS:\t%u\n", info.m_workitemDims); + printf(" CL_DEVICE_MAX_WORK_ITEM_SIZES:\t%u / %u / %u \n", info.m_workItemSize[0], info.m_workItemSize[1], info.m_workItemSize[2]); + printf(" CL_DEVICE_MAX_WORK_GROUP_SIZE:\t%u\n", info.m_workgroupSize); + printf(" CL_DEVICE_MAX_CLOCK_FREQUENCY:\t%u MHz\n", info.m_clockFrequency); + printf(" CL_DEVICE_ADDRESS_BITS:\t\t%u\n", info.m_addressBits); + printf(" CL_DEVICE_MAX_MEM_ALLOC_SIZE:\t\t%u MByte\n", (unsigned int)(info.m_maxMemAllocSize/ (1024 * 1024))); + printf(" CL_DEVICE_GLOBAL_MEM_SIZE:\t\t%u MByte\n", (unsigned int)(info.m_globalMemSize/ (1024 * 1024))); + printf(" CL_DEVICE_ERROR_CORRECTION_SUPPORT:\t%s\n", info.m_errorCorrectionSupport== CL_TRUE ? "yes" : "no"); + printf(" CL_DEVICE_LOCAL_MEM_TYPE:\t\t%s\n", info.m_localMemType == 1 ? "local" : "global"); + printf(" CL_DEVICE_LOCAL_MEM_SIZE:\t\t%u KByte\n", (unsigned int)(info.m_localMemSize / 1024)); + printf(" CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE:\t%u KByte\n", (unsigned int)(info.m_constantBufferSize / 1024)); + if( info.m_queueProperties & CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE ) + printf(" CL_DEVICE_QUEUE_PROPERTIES:\t\t%s\n", "CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE"); + if( info.m_queueProperties & CL_QUEUE_PROFILING_ENABLE ) + printf(" CL_DEVICE_QUEUE_PROPERTIES:\t\t%s\n", "CL_QUEUE_PROFILING_ENABLE"); + + printf(" CL_DEVICE_IMAGE_SUPPORT:\t\t%u\n", info.m_imageSupport); + + printf(" CL_DEVICE_MAX_READ_IMAGE_ARGS:\t%u\n", info.m_maxReadImageArgs); + printf(" CL_DEVICE_MAX_WRITE_IMAGE_ARGS:\t%u\n", info.m_maxWriteImageArgs); + printf("\n CL_DEVICE_IMAGE "); + printf("\t\t\t2D_MAX_WIDTH\t %u\n", info.m_image2dMaxWidth); + printf("\t\t\t\t\t2D_MAX_HEIGHT\t %u\n", info.m_image2dMaxHeight); + printf("\t\t\t\t\t3D_MAX_WIDTH\t %u\n", info.m_image3dMaxWidth); + printf("\t\t\t\t\t3D_MAX_HEIGHT\t %u\n", info.m_image3dMaxHeight); + printf("\t\t\t\t\t3D_MAX_DEPTH\t %u\n", info.m_image3dMaxDepth); + if (info.m_deviceExtensions != 0) + printf("\n CL_DEVICE_EXTENSIONS:%s\n",info.m_deviceExtensions); + else + printf(" CL_DEVICE_EXTENSIONS: None\n"); + printf(" CL_DEVICE_PREFERRED_VECTOR_WIDTH_\t"); + printf("CHAR %u, SHORT %u, INT %u,LONG %u, FLOAT %u, DOUBLE %u\n\n\n", + info.m_vecWidthChar, info.m_vecWidthShort, info.m_vecWidthInt, info.m_vecWidthLong,info.m_vecWidthFloat, info.m_vecWidthDouble); + + +} + +void btOpenCLUtils::getDeviceInfo(cl_device_id device, btOpenCLDeviceInfo& info) +{ + + // CL_DEVICE_NAME + clGetDeviceInfo(device, CL_DEVICE_NAME, BT_MAX_STRING_LENGTH, &info.m_deviceName, NULL); + + // CL_DEVICE_VENDOR + clGetDeviceInfo(device, CL_DEVICE_VENDOR, BT_MAX_STRING_LENGTH, &info.m_deviceVendor, NULL); + + // CL_DRIVER_VERSION + clGetDeviceInfo(device, CL_DRIVER_VERSION, BT_MAX_STRING_LENGTH, &info.m_driverVersion, NULL); + + // CL_DEVICE_INFO + clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(cl_device_type), &info.m_deviceType, NULL); + + // CL_DEVICE_MAX_COMPUTE_UNITS + clGetDeviceInfo(device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(info.m_computeUnits), &info.m_computeUnits, NULL); + + // CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(info.m_workitemDims), &info.m_workitemDims, NULL); + + // CL_DEVICE_MAX_WORK_ITEM_SIZES + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(info.m_workItemSize), &info.m_workItemSize, NULL); + + // CL_DEVICE_MAX_WORK_GROUP_SIZE + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(info.m_workgroupSize), &info.m_workgroupSize, NULL); + + // CL_DEVICE_MAX_CLOCK_FREQUENCY + clGetDeviceInfo(device, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(info.m_clockFrequency), &info.m_clockFrequency, NULL); + + // CL_DEVICE_ADDRESS_BITS + clGetDeviceInfo(device, CL_DEVICE_ADDRESS_BITS, sizeof(info.m_addressBits), &info.m_addressBits, NULL); + + // CL_DEVICE_MAX_MEM_ALLOC_SIZE + clGetDeviceInfo(device, CL_DEVICE_MAX_MEM_ALLOC_SIZE, sizeof(info.m_maxMemAllocSize), &info.m_maxMemAllocSize, NULL); + + // CL_DEVICE_GLOBAL_MEM_SIZE + clGetDeviceInfo(device, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(info.m_globalMemSize), &info.m_globalMemSize, NULL); + + // CL_DEVICE_ERROR_CORRECTION_SUPPORT + clGetDeviceInfo(device, CL_DEVICE_ERROR_CORRECTION_SUPPORT, sizeof(info.m_errorCorrectionSupport), &info.m_errorCorrectionSupport, NULL); + + // CL_DEVICE_LOCAL_MEM_TYPE + clGetDeviceInfo(device, CL_DEVICE_LOCAL_MEM_TYPE, sizeof(info.m_localMemType), &info.m_localMemType, NULL); + + // CL_DEVICE_LOCAL_MEM_SIZE + clGetDeviceInfo(device, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(info.m_localMemSize), &info.m_localMemSize, NULL); + + // CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE + clGetDeviceInfo(device, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, sizeof(info.m_constantBufferSize), &info.m_constantBufferSize, NULL); + + // CL_DEVICE_QUEUE_PROPERTIES + clGetDeviceInfo(device, CL_DEVICE_QUEUE_PROPERTIES, sizeof(info.m_queueProperties), &info.m_queueProperties, NULL); + + // CL_DEVICE_IMAGE_SUPPORT + clGetDeviceInfo(device, CL_DEVICE_IMAGE_SUPPORT, sizeof(info.m_imageSupport), &info.m_imageSupport, NULL); + + // CL_DEVICE_MAX_READ_IMAGE_ARGS + clGetDeviceInfo(device, CL_DEVICE_MAX_READ_IMAGE_ARGS, sizeof(info.m_maxReadImageArgs), &info.m_maxReadImageArgs, NULL); + + // CL_DEVICE_MAX_WRITE_IMAGE_ARGS + clGetDeviceInfo(device, CL_DEVICE_MAX_WRITE_IMAGE_ARGS, sizeof(info.m_maxWriteImageArgs), &info.m_maxWriteImageArgs, NULL); + + // CL_DEVICE_IMAGE2D_MAX_WIDTH, CL_DEVICE_IMAGE2D_MAX_HEIGHT, CL_DEVICE_IMAGE3D_MAX_WIDTH, CL_DEVICE_IMAGE3D_MAX_HEIGHT, CL_DEVICE_IMAGE3D_MAX_DEPTH + clGetDeviceInfo(device, CL_DEVICE_IMAGE2D_MAX_WIDTH, sizeof(size_t), &info.m_image2dMaxWidth, NULL); + clGetDeviceInfo(device, CL_DEVICE_IMAGE2D_MAX_HEIGHT, sizeof(size_t), &info.m_image2dMaxHeight, NULL); + clGetDeviceInfo(device, CL_DEVICE_IMAGE3D_MAX_WIDTH, sizeof(size_t), &info.m_image3dMaxWidth, NULL); + clGetDeviceInfo(device, CL_DEVICE_IMAGE3D_MAX_HEIGHT, sizeof(size_t), &info.m_image3dMaxHeight, NULL); + clGetDeviceInfo(device, CL_DEVICE_IMAGE3D_MAX_DEPTH, sizeof(size_t), &info.m_image3dMaxDepth, NULL); + + // CL_DEVICE_EXTENSIONS: get device extensions, and if any then parse & log the string onto separate lines + clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, BT_MAX_STRING_LENGTH, &info.m_deviceExtensions, NULL); + + // CL_DEVICE_PREFERRED_VECTOR_WIDTH_ + clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR, sizeof(cl_uint), &info.m_vecWidthChar, NULL); + clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT, sizeof(cl_uint), &info.m_vecWidthShort, NULL); + clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT, sizeof(cl_uint), &info.m_vecWidthInt, NULL); + clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG, sizeof(cl_uint), &info.m_vecWidthLong, NULL); + clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT, sizeof(cl_uint), &info.m_vecWidthFloat, NULL); + clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE, sizeof(cl_uint), &info.m_vecWidthDouble, NULL); +} + +static const char* strip2(const char* name, const char* pattern) +{ + size_t const patlen = strlen(pattern); + size_t patcnt = 0; + const char * oriptr; + const char * patloc; + // find how many times the pattern occurs in the original string + for (oriptr = name; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen) + { + patcnt++; + } + return oriptr; +} + +cl_program btOpenCLUtils::compileCLProgramFromString(cl_context clContext, cl_device_id device, const char* kernelSource, cl_int* pErrNum, const char* additionalMacros , const char* clFileNameForCaching) +{ + + cl_program m_cpProgram=0; + cl_int status; + + char binaryFileName[522]; + + if (clFileNameForCaching) + { + + char deviceName[256]; + char driverVersion[256]; + clGetDeviceInfo(device, CL_DEVICE_NAME, 256, &deviceName, NULL); + clGetDeviceInfo(device, CL_DRIVER_VERSION, 256, &driverVersion, NULL); + + + const char* strippedName = strip2(clFileNameForCaching,"\\"); + strippedName = strip2(strippedName,"/"); + + sprintf_s(binaryFileName,"cache/%s.%s.%s.bin",strippedName, deviceName,driverVersion ); + //printf("searching for %s\n", binaryFileName); + + bool fileUpToDate = false; + bool binaryFileValid=false; + + FILETIME modtimeBinary; + +#ifdef _WIN32 + CreateDirectory("cache",0); + { + + HANDLE binaryFileHandle = CreateFile(binaryFileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); + if (binaryFileHandle ==INVALID_HANDLE_VALUE) + { + DWORD errorCode; + errorCode = GetLastError(); + switch (errorCode) + { + case ERROR_FILE_NOT_FOUND: + { + printf("\nCached file not found %s\n", binaryFileName); + break; + } + case ERROR_PATH_NOT_FOUND: + { + printf("\nCached file path not found %s\n", binaryFileName); + break; + } + default: + { + printf("\nFailed reading cached file with errorCode = %d\n", errorCode); + } + } + } else + { + if (GetFileTime(binaryFileHandle, NULL, NULL, &modtimeBinary)==0) + { + DWORD errorCode; + errorCode = GetLastError(); + printf("\nGetFileTime errorCode = %d\n", errorCode); + } else + { + binaryFileValid = true; + } + CloseHandle(binaryFileHandle); + } + + if (binaryFileValid) + { + HANDLE srcFileHandle = CreateFile(clFileNameForCaching,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); + if (srcFileHandle!=INVALID_HANDLE_VALUE) + { + FILETIME modtimeSrc; + if (GetFileTime(srcFileHandle, NULL, NULL, &modtimeSrc)==0) + { + DWORD errorCode; + errorCode = GetLastError(); + printf("\nGetFileTime errorCode = %d\n", errorCode); + } + if ( ( modtimeSrc.dwHighDateTime < modtimeBinary.dwHighDateTime) + ||(( modtimeSrc.dwHighDateTime == modtimeBinary.dwHighDateTime)&&(modtimeSrc.dwLowDateTime <= modtimeBinary.dwLowDateTime))) + { + fileUpToDate=true; + } else + { + printf("\nCached binary file out-of-date (%s)\n",binaryFileName); + } + CloseHandle(srcFileHandle); + } + else + { +#ifdef _DEBUG + DWORD errorCode; + errorCode = GetLastError(); + switch (errorCode) + { + case ERROR_FILE_NOT_FOUND: + { + printf("\nSrc file not found %s\n", clFileNameForCaching); + break; + } + case ERROR_PATH_NOT_FOUND: + { + printf("\nSrc path not found %s\n", clFileNameForCaching); + break; + } + default: + { + printf("\nnSrc file reading errorCode = %d\n", errorCode); + } + } + + //we should make sure the src file exists so we can verify the timestamp with binary + assert(0); +#else + //if we cannot find the source, assume it is OK in release builds + fileUpToDate = true; +#endif + } + } + + + } + + if( fileUpToDate) + { + FILE* file = fopen(binaryFileName, "rb"); + if (file) + { + fseek( file, 0L, SEEK_END ); + size_t binarySize = ftell( file ); + rewind( file ); + char* binary = new char[binarySize]; + fread( binary, sizeof(char), binarySize, file ); + fclose( file ); + + m_cpProgram = clCreateProgramWithBinary( clContext, 1,&device, &binarySize, (const unsigned char**)&binary, 0, &status ); + btAssert( status == CL_SUCCESS ); + status = clBuildProgram( m_cpProgram, 1, &device, additionalMacros, 0, 0 ); + btAssert( status == CL_SUCCESS ); + + if( status != CL_SUCCESS ) + { + char *build_log; + size_t ret_val_size; + clGetProgramBuildInfo(m_cpProgram, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size); + build_log = new char[ret_val_size+1]; + clGetProgramBuildInfo(m_cpProgram, device, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL); + build_log[ret_val_size] = '\0'; + printf("%s\n", build_log); + delete build_log; + btAssert(0); + m_cpProgram = 0; + } + delete[] binary; + } + } +#endif //_WIN32 + + } + + if (!m_cpProgram) + { + cl_kernel kernel; + cl_int localErrNum; + size_t program_length = strlen(kernelSource); + + m_cpProgram = clCreateProgramWithSource(clContext, 1, (const char**)&kernelSource, &program_length, &localErrNum); + if (localErrNum!= CL_SUCCESS) + { + if (pErrNum) + *pErrNum = localErrNum; + return 0; + } + + // Build the program with 'mad' Optimization option + + + #ifdef MAC + char* flags = "-cl-mad-enable -DMAC -DGUID_ARG"; + #else + //const char* flags = "-DGUID_ARG= -fno-alias"; + const char* flags = "-DGUID_ARG= "; + #endif + + char* compileFlags = new char[strlen(additionalMacros) + strlen(flags) + 5]; + sprintf(compileFlags, "%s %s", flags, additionalMacros); + localErrNum = clBuildProgram(m_cpProgram, 1, &device, compileFlags, NULL, NULL); + if (localErrNum!= CL_SUCCESS) + { + char *build_log; + size_t ret_val_size; + clGetProgramBuildInfo(m_cpProgram, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size); + build_log = new char[ret_val_size+1]; + clGetProgramBuildInfo(m_cpProgram, device, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL); + + // to be carefully, terminate with \0 + // there's no information in the reference whether the string is 0 terminated or not + build_log[ret_val_size] = '\0'; + + + printf("Error in clBuildProgram, Line %u in file %s, Log: \n%s\n !!!\n\n", __LINE__, __FILE__, build_log); + delete[] build_log; + if (pErrNum) + *pErrNum = localErrNum; + return 0; + } + + if( clFileNameForCaching ) + { // write to binary + + cl_uint numAssociatedDevices; + status = clGetProgramInfo( m_cpProgram, CL_PROGRAM_NUM_DEVICES, sizeof(cl_uint), &numAssociatedDevices, 0 ); + btAssert( status == CL_SUCCESS ); + if (numAssociatedDevices==1) + { + + size_t binarySize; + status = clGetProgramInfo( m_cpProgram, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &binarySize, 0 ); + btAssert( status == CL_SUCCESS ); + + char* binary = new char[binarySize]; + + status = clGetProgramInfo( m_cpProgram, CL_PROGRAM_BINARIES, sizeof(char*), &binary, 0 ); + btAssert( status == CL_SUCCESS ); + + { + FILE* file = fopen(binaryFileName, "wb"); + if (file) + { + fwrite( binary, sizeof(char), binarySize, file ); + fclose( file ); + } else + { + printf("cannot write file %s\n", binaryFileName); + } + } + + delete [] binary; + } + } + delete [] compileFlags; + } + + return m_cpProgram; +} + + +cl_kernel btOpenCLUtils::compileCLKernelFromString(cl_context clContext, cl_device_id device, const char* kernelSource, const char* kernelName, cl_int* pErrNum, cl_program prog, const char* additionalMacros ) +{ + printf("compiling kernel %s ",kernelName); + cl_kernel kernel; + cl_int localErrNum; + size_t program_length = strlen(kernelSource); + + + cl_program m_cpProgram = prog; + if (!m_cpProgram) + { + m_cpProgram = compileCLProgramFromString(clContext,device,kernelSource,pErrNum, additionalMacros); + } + + + // Create the kernel + kernel = clCreateKernel(m_cpProgram, kernelName, &localErrNum); + if (localErrNum != CL_SUCCESS) + { + printf("Error in clCreateKernel, Line %u in file %s, cannot find kernel function %s !!!\n\n", __LINE__, __FILE__, kernelName); + if (pErrNum) + *pErrNum = localErrNum; + return 0; + } + + if (!prog && m_cpProgram) + { + clReleaseProgram(m_cpProgram); + } + printf("ready. \n"); + + + if (pErrNum) + *pErrNum = CL_SUCCESS; + return kernel; + +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.h b/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.h new file mode 100644 index 000000000..4e41b415b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/btOpenCLUtils.h @@ -0,0 +1,104 @@ +/* +Bullet Continuous Collision Detection and Physics Library, http://bulletphysics.org +Copyright (C) 2006 - 2011 Sony Computer Entertainment Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//original author: Roman Ponomarev +//cleanup by Erwin Coumans + +#ifndef BT_OPENCL_UTILS_H +#define BT_OPENCL_UTILS_H + +#include "btOpenCLInclude.h" + + +#define BT_MAX_STRING_LENGTH 1024 + +struct btOpenCLDeviceInfo +{ + char m_deviceName[BT_MAX_STRING_LENGTH]; + char m_deviceVendor[BT_MAX_STRING_LENGTH]; + char m_driverVersion[BT_MAX_STRING_LENGTH]; + char m_deviceExtensions[BT_MAX_STRING_LENGTH]; + + cl_device_type m_deviceType; + cl_uint m_computeUnits; + size_t m_workitemDims; + size_t m_workItemSize[3]; + size_t m_image2dMaxWidth; + size_t m_image2dMaxHeight; + size_t m_image3dMaxWidth; + size_t m_image3dMaxHeight; + size_t m_image3dMaxDepth; + size_t m_workgroupSize; + cl_uint m_clockFrequency; + cl_ulong m_constantBufferSize; + cl_ulong m_localMemSize; + cl_ulong m_globalMemSize; + cl_bool m_errorCorrectionSupport; + cl_device_local_mem_type m_localMemType; + cl_uint m_maxReadImageArgs; + cl_uint m_maxWriteImageArgs; + + + + cl_uint m_addressBits; + cl_ulong m_maxMemAllocSize; + cl_command_queue_properties m_queueProperties; + cl_bool m_imageSupport; + cl_uint m_vecWidthChar; + cl_uint m_vecWidthShort; + cl_uint m_vecWidthInt; + cl_uint m_vecWidthLong; + cl_uint m_vecWidthFloat; + cl_uint m_vecWidthDouble; + +}; + +struct btOpenCLPlatformInfo +{ + char m_platformVendor[BT_MAX_STRING_LENGTH]; + char m_platformName[BT_MAX_STRING_LENGTH]; + char m_platformVersion[BT_MAX_STRING_LENGTH]; +}; + +class btOpenCLUtils +{ +public: + + /// CL Context optionally takes a GL context. This is a generic type because we don't really want this code + /// to have to understand GL types. It is a HGLRC in _WIN32 or a GLXContext otherwise. + static cl_context createContextFromType(cl_device_type deviceType, cl_int* pErrNum, void* pGLCtx = 0, void* pGLDC = 0, int preferredDeviceIndex = -1, int preferredPlatformIndex= - 1); + + static int getNumDevices(cl_context cxMainContext); + static cl_device_id getDevice(cl_context cxMainContext, int nr); + static void getDeviceInfo(cl_device_id device, btOpenCLDeviceInfo& info); + static void printDeviceInfo(cl_device_id device); + + static cl_kernel compileCLKernelFromString( cl_context clContext,cl_device_id device, const char* kernelSource, const char* kernelName, cl_int* pErrNum=0, cl_program prog=0,const char* additionalMacros = "" ); + + //optional + static cl_program compileCLProgramFromString( cl_context clContext,cl_device_id device, const char* kernelSource, cl_int* pErrNum=0,const char* additionalMacros = "" , const char* srcFileNameForCaching=0); + + //the following optional APIs provide access using specific platform information + static int getNumPlatforms(cl_int* pErrNum=0); + ///get the nr'th platform, where nr is in the range [0..getNumPlatforms) + static cl_platform_id getPlatform(int nr, cl_int* pErrNum=0); + static void getPlatformInfo(cl_platform_id platform, btOpenCLPlatformInfo& platformInfo); + static const char* getSdkVendorName(); + static cl_context createContextFromPlatform(cl_platform_id platform, cl_device_type deviceType, cl_int* pErrNum, void* pGLCtx = 0, void* pGLDC = 0,int preferredDeviceIndex = -1, int preferredPlatformIndex= -1); +}; + + + +#endif // BT_OPENCL_UTILS_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/main.cpp new file mode 100644 index 000000000..2890d7d19 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/basic_initialize/main.cpp @@ -0,0 +1,92 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2011 Advanced Micro Devices, Inc. http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +///original author: Erwin Coumans + +#include "btOpenCLUtils.h" +#include + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; + + + +int main(int argc, char* argv[]) +{ + int ciErrNum = 0; + + cl_device_type deviceType = CL_DEVICE_TYPE_ALL; + const char* vendorSDK = btOpenCLUtils::getSdkVendorName(); + + printf("This program was compiled using the %s OpenCL SDK\n",vendorSDK); + int numPlatforms = btOpenCLUtils::getNumPlatforms(); + printf("Num Platforms = %d\n", numPlatforms); + + for (int i=0;i=0) + { + btAABBCL minAabb = plocalShapeAABB[shapeIndex*2]; + btAABBCL maxAabb = plocalShapeAABB[shapeIndex*2+1]; + + float4 halfExtents = ((float4)(maxAabb.fx - minAabb.fx,maxAabb.fy - minAabb.fy,maxAabb.fz - minAabb.fz,0.f))*0.5f; + + Matrix3x3 abs_b = qtGetRotationMatrix(orientation); + float4 extent = (float4) ( dot(abs_b.m_row[0],halfExtents),dot(abs_b.m_row[1],halfExtents),dot(abs_b.m_row[2],halfExtents),0.f); + + + pAABB[nodeID*2].fx = position.x-extent.x; + pAABB[nodeID*2].fy = position.y-extent.y; + pAABB[nodeID*2].fz = position.z-extent.z; + pAABB[nodeID*2].uw = nodeID; + + pAABB[nodeID*2+1].fx = position.x+extent.x; + pAABB[nodeID*2+1].fy = position.y+extent.y; + pAABB[nodeID*2+1].fz = position.z+extent.z; + pAABB[nodeID*2+1].uw = nodeID; + } + } +} + + +__kernel void + broadphaseColorKernel( const int startOffset, const int numNodes, __global float4 *g_vertexBuffer, __global int2* pOverlappingPairs, const int numOverlap) +{ + int nodeID = get_global_id(0); + if( nodeID < numOverlap ) + { + int2 pair = pOverlappingPairs[nodeID]; + float4 red = (float4)(1.f,0.4f,0.4f,1.f); + + g_vertexBuffer[pair.x + startOffset/4+numNodes+numNodes] = red; + g_vertexBuffer[pair.y + startOffset/4+numNodes+numNodes] = red; + } +} + + + +__kernel void + broadphaseKernel( const int startOffset, const int numNodes, __global float4 *g_vertexBuffer) +{ + int nodeID = get_global_id(0); + +// float BT_GPU_ANGULAR_MOTION_THRESHOLD = (0.25f * 3.14159254); + + if( nodeID < numNodes ) + { + float4 position = g_vertexBuffer[nodeID + startOffset/4]; + //float4 orientation = g_vertexBuffer[nodeID + startOffset/4+numNodes]; + float4 color = g_vertexBuffer[nodeID + startOffset/4+numNodes+numNodes]; + + float4 red = (float4)(1.f,0.f,0.f,0.f); + float4 green = (float4)(0.f,1.f,0.f,0.f); + float4 blue = (float4)(0.f,0.f,1.f,0.f); + float overlap=0; + int equal = 0; + + g_vertexBuffer[nodeID + startOffset/4+numNodes+numNodes] = green; + + for (int i=0;i0.f) + g_vertexBuffer[nodeID + startOffset/4+numNodes+numNodes]=red*overlap; + else + g_vertexBuffer[nodeID + startOffset/4+numNodes+numNodes]=green; + } + } +} + +); \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.cpp b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.cpp new file mode 100644 index 000000000..9e0ea62cc --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.cpp @@ -0,0 +1,231 @@ + +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Roman Ponomarev, Erwin Coumans + +#ifdef RELEASE_ME +#define COMPUTE_AABB_KERNEL_PATH "computeAabbKernelOCL.cl" +#else +#define COMPUTE_AABB_KERNEL_PATH "..\\..\\opencl\\broadphase_benchmark\\computeAabbKernelOCL" +#endif + + +#include "btGridBroadphaseCl.h" +#include "LinearMath/btQuickprof.h" +#include "Adl/Adl.h" +#include "AdlPrimitives/Math/Math.h" + +#include "Adl/AdlKernel.h" +#include "../basic_initialize/btOpenCLUtils.h" +#define MSTRINGIFY(A) #A +static const char* spComputeAabbSource= +#include "computeAabbKernelOCL.cl" + +struct btTmpAabb +{ + float minfx; + float minfy; + float minfz; + unsigned int index0; + float maxfx; + float maxfy; + float maxfz; + unsigned int index1; +} ; + + + + +btGridBroadphaseCl::btGridBroadphaseCl( btOverlappingPairCache* overlappingPairCache, + const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerSmallProxy, + btScalar maxSmallProxySize, + int maxSmallProxiesPerCell, + cl_context context, + cl_device_id device, + cl_command_queue queue, + adl::DeviceCL* deviceCL) +:bt3dGridBroadphaseOCL(overlappingPairCache,cellSize, + gridSizeX, gridSizeY, gridSizeZ, + maxSmallProxies, maxLargeProxies, maxPairsPerSmallProxy, + maxSmallProxySize,maxSmallProxiesPerCell, + context,device,queue,deviceCL) +{ + m_computeAabbKernel = m_deviceCL->getKernel(COMPUTE_AABB_KERNEL_PATH,"computeAabb","",spComputeAabbSource); + + m_countOverlappingPairs = m_deviceCL->getKernel(COMPUTE_AABB_KERNEL_PATH,"countOverlappingpairs","",spComputeAabbSource); + + m_squeezePairCaches = m_deviceCL->getKernel(COMPUTE_AABB_KERNEL_PATH,"squeezePairCaches","",spComputeAabbSource); + + m_aabbConstBuffer = new adl::Buffer(m_deviceCL,1,adl::BufferBase::BUFFER_CONST); + + size_t memSize = m_maxHandles * m_maxPairsPerBody * sizeof(unsigned int)*2; + cl_int ciErrNum=0; + m_dAllOverlappingPairs = clCreateBuffer(m_cxMainContext, CL_MEM_READ_WRITE, memSize, NULL, &ciErrNum); + + memset(m_hAllOverlappingPairs, 0x00, sizeof(MyUint2)*m_maxHandles * m_maxPairsPerBody); + copyArrayToDevice(m_dAllOverlappingPairs, m_hAllOverlappingPairs, m_maxHandles * m_maxPairsPerBody * sizeof(MyUint2)); + + + + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + + +} + +btGridBroadphaseCl::~btGridBroadphaseCl() +{ + clReleaseMemObject(m_dAllOverlappingPairs); + + delete m_aabbConstBuffer; + +} + + + +void btGridBroadphaseCl::prepareAABB(float* positions, int numObjects) +{ + return; +#if 0 +bt3dGridBroadphaseOCL::prepareAABB(); +#else + BT_PROFILE("prepareAABB"); + bt3DGrid3F1U* pBB = m_hAABB; + + int new_largest_index = numObjects; + unsigned int num_small = numObjects; + m_LastHandleIndex = new_largest_index; + new_largest_index = -1; + unsigned int num_large = 0; + m_LastLargeHandleIndex = new_largest_index; + // paranoid checks + //btAssert(num_small == m_numHandles); + //btAssert(num_large == m_numLargeHandles); + + //copyArrayFromDevice( m_hAABB, m_dAABB, sizeof(bt3DGrid3F1U) * 2 * (m_numHandles + m_numLargeHandles)); + //clFinish(m_cqCommandQue); +#endif + +} +void btGridBroadphaseCl::calcHashAABB() +{ + bt3dGridBroadphaseOCL::calcHashAABB(); +} + + +void btGridBroadphaseCl::calculateOverlappingPairs(float* positions, int numObjects) +{ + btDispatcher* dispatcher=0; + + // update constants + { + BT_PROFILE("setParameters"); + setParameters(&m_params); + } + + // prepare AABB array + { + BT_PROFILE("prepareAABB"); + prepareAABB(positions, numObjects); + } + // calculate hash + { + BT_PROFILE("calcHashAABB"); + calcHashAABB(); + } + + { + BT_PROFILE("sortHash"); + // sort bodies based on hash + sortHash(); + } + + // find start of each cell + { + BT_PROFILE("findCellStart"); + findCellStart(); + } + + { + BT_PROFILE("findOverlappingPairs"); + // findOverlappingPairs (small/small) + findOverlappingPairs(); + } + + // add pairs to CPU cache + { + BT_PROFILE("computePairCacheChanges"); +#if 0 + computePairCacheChanges(); +#else + int ciErrNum=0; + + ciErrNum=clSetKernelArg((cl_kernel)m_countOverlappingPairs->m_kernel, 0, sizeof(int), (void*)&numObjects); + ciErrNum=clSetKernelArg((cl_kernel)m_countOverlappingPairs->m_kernel, 1, sizeof(cl_mem),(void*)&m_dPairBuff); + ciErrNum=clSetKernelArg((cl_kernel)m_countOverlappingPairs->m_kernel, 2, sizeof(cl_mem),(void*)&m_dPairBuffStartCurr); + ciErrNum=clSetKernelArg((cl_kernel)m_countOverlappingPairs->m_kernel, 3, sizeof(cl_mem),(void*)&m_dPairScanChanged); + ciErrNum=clSetKernelArg((cl_kernel)m_countOverlappingPairs->m_kernel, 4, sizeof(cl_mem),(void*)&m_dAABB); + + + size_t localWorkSize=64; + size_t numWorkItems = localWorkSize*((numObjects+ (localWorkSize)) / localWorkSize); + + + ciErrNum = clEnqueueNDRangeKernel(m_cqCommandQue, (cl_kernel)m_countOverlappingPairs->m_kernel, 1, NULL, &numWorkItems, &localWorkSize, 0,0,0 ); +oclCHECKERROR(ciErrNum, CL_SUCCESS); + ciErrNum = clFlush(m_cqCommandQue); +#endif + + + } + { + BT_PROFILE("scanOverlappingPairBuff"); + scanOverlappingPairBuff(false); + } + { + BT_PROFILE("squeezeOverlappingPairBuff"); +//#define FORCE_CPU +#ifdef FORCE_CPU + bt3dGridBroadphaseOCL::squeezeOverlappingPairBuff(); + copyArrayToDevice(m_dPairsChangedXY, m_hPairsChangedXY, sizeof( MyUint2) * m_numPrefixSum); //gSum +#else + //squeezeOverlappingPairBuff(); + int ciErrNum = 0; + ciErrNum=clSetKernelArg((cl_kernel)m_squeezePairCaches->m_kernel, 0, sizeof(int), (void*)&numObjects); + ciErrNum=clSetKernelArg((cl_kernel)m_squeezePairCaches->m_kernel, 1, sizeof(cl_mem),(void*)&m_dPairBuff); + ciErrNum=clSetKernelArg((cl_kernel)m_squeezePairCaches->m_kernel, 2, sizeof(cl_mem),(void*)&m_dPairBuffStartCurr); + ciErrNum=clSetKernelArg((cl_kernel)m_squeezePairCaches->m_kernel, 3, sizeof(cl_mem),(void*)&m_dPairScanChanged); + ciErrNum=clSetKernelArg((cl_kernel)m_squeezePairCaches->m_kernel, 4, sizeof(cl_mem),(void*)&m_dAllOverlappingPairs); + ciErrNum=clSetKernelArg((cl_kernel)m_squeezePairCaches->m_kernel, 5, sizeof(cl_mem),(void*)&m_dAABB); + + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((numObjects+ (workGroupSize)) / workGroupSize); + + + ciErrNum = clEnqueueNDRangeKernel(m_cqCommandQue, (cl_kernel)m_squeezePairCaches->m_kernel, 1, NULL, &numWorkItems, &workGroupSize, 0,0,0 ); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + +// copyArrayFromDevice(m_hAllOverlappingPairs, m_dAllOverlappingPairs, sizeof(unsigned int) * m_numPrefixSum*2); //gSum +// clFinish(m_cqCommandQue); +#endif + + } + + + return; +} + diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.h b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.h new file mode 100644 index 000000000..7f064488d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/btGridBroadphaseCL.h @@ -0,0 +1,73 @@ + +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Roman Ponomarev, Erwin Coumans + +#ifndef GRID_BROADPHASE_CL_H +#define GRID_BROADPHASE_CL_H + +#include "../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h" + +#include "Adl/Adl.h" +#include "Adl/AdlKernel.h" + + +struct MyAabbConstData +{ + int bla; + int numElem; +}; + + + +class btGridBroadphaseCl : public bt3dGridBroadphaseOCL +{ +protected: + + adl::Kernel* m_computeAabbKernel; + adl::Kernel* m_countOverlappingPairs; + adl::Kernel* m_squeezePairCaches; + + + adl::Buffer* m_aabbConstBuffer; + + + public: + + cl_mem m_dAllOverlappingPairs; + + + btGridBroadphaseCl( btOverlappingPairCache* overlappingPairCache, + const btVector3& cellSize, + int gridSizeX, int gridSizeY, int gridSizeZ, + int maxSmallProxies, int maxLargeProxies, int maxPairsPerSmallProxy, + btScalar maxSmallProxySize, + int maxSmallProxiesPerCell = 4, + cl_context context = NULL, + cl_device_id device = NULL, + cl_command_queue queue = NULL, + adl::DeviceCL* deviceCL=0 + ); + + virtual void prepareAABB(float* positions, int numObjects); + virtual void calcHashAABB(); + + void calculateOverlappingPairs(float* positions, int numObjects); + + virtual ~btGridBroadphaseCl(); + +}; + +#endif //GRID_BROADPHASE_CL_H + diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/computeAabbKernelOCL.cl b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/computeAabbKernelOCL.cl new file mode 100644 index 000000000..3cf7550c0 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/computeAabbKernelOCL.cl @@ -0,0 +1,112 @@ +MSTRINGIFY( + +typedef struct +{ + int bla; + int numElem; +} MyAabbConstDataCL ; + +typedef struct +{ + float minfx; + float minfy; + float minfz; + unsigned int index0; + float maxfx; + float maxfy; + float maxfz; + unsigned int index1; +} btAabbCL; + + +__kernel void computeAabb( __global btAabbCL* aabbs,__global float4* positions, MyAabbConstDataCL cb) +{ + int nodeID = get_global_id(0); + + if( nodeID < cb.numElem ) + { + aabbs[nodeID].minfx = positions[nodeID].x -1.f; + aabbs[nodeID].minfy = positions[nodeID].y -1.f; + aabbs[nodeID].minfz = positions[nodeID].z -1.f; + aabbs[nodeID].index0 = nodeID; + aabbs[nodeID].maxfx = positions[nodeID].x +1.f; + aabbs[nodeID].maxfy = positions[nodeID].y +1.f; + aabbs[nodeID].maxfz = positions[nodeID].z +1.f; + aabbs[nodeID].index1 = nodeID; + } +} + + +__kernel void countOverlappingpairs( int numObjects, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + __global int* pPairScan, + __global float4* pAABB ) +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + float4 bbMin = pAABB[index * 2]; + int handleIndex = as_int(bbMin.w); + int2 start_curr = pPairBuffStartCurr[handleIndex]; + int start = start_curr.x; + int curr = start_curr.y; + __global int *pInp = pPairBuff + start; + int num_changes = 0; + for(int k = 0; k < curr; k++, pInp++) + { + if(((*pInp) & 0x60000000))//either new or existing pairs (ignore old non-overlapping pairs) + { + num_changes++; + } + } + pPairScan[index+1] = num_changes; +} + + +__kernel void squeezePairCaches( int numObjects, + __global int* pPairBuff, + __global int2* pPairBuffStartCurr, + __global int* pPairScan, + __global int2* pPairOut, + __global float4* pAABB ) +{ + int index = get_global_id(0); + if(index >= numObjects) + { + return; + } + float4 bbMin = pAABB[index * 2]; + int handleIndex = as_int(bbMin.w); + int2 start_curr = pPairBuffStartCurr[handleIndex]; + int start = start_curr.x; + int curr = start_curr.y; + __global int* pInp = pPairBuff + start; + __global int2* pOut = pPairOut + pPairScan[index+1]; + __global int* pOut2 = pInp; + int num = 0; + for(int k = 0; k < curr; k++, pInp++) + { + if(((*pInp) & 0x60000000)) + { + int2 newpair; + newpair.x = handleIndex; + newpair.y = (*pInp) & (~0x60000000); + *pOut = newpair; + pOut++; + } + if((*pInp) & 0x60000000) + { + *pOut2 = (*pInp) & (~0x60000000); + pOut2++; + num++; + } + } + int2 newStartCurr; + newStartCurr.x = start; + newStartCurr.y = num; + pPairBuffStartCurr[handleIndex] = newStartCurr; +} +); \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.cpp b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.cpp new file mode 100644 index 000000000..d90e37054 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.cpp @@ -0,0 +1,204 @@ + +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Roman Ponomarev, Erwin Coumans + +#include "findPairsOpenCL.h" +#include "../basic_initialize/btOpenCLUtils.h" + +#define MSTRINGIFY(A) #A +static char* broadphaseKernelString = +#include "broadphaseKernel.cl" + +#define GRID_BROADPHASE_PATH "..\\..\\opencl\\broadphase_benchmark\\broadphaseKernel.cl" + + + + +void initFindPairs(btFindPairsIO& fpio,cl_context cxMainContext, cl_device_id device, cl_command_queue commandQueue, int maxHandles, int maxPairsPerBody) +{ + + //m_proxies.push_back( proxy ); + + fpio.m_mainContext = cxMainContext; + fpio.m_cqCommandQue = commandQueue; + fpio.m_device = device; + cl_int pErrNum; + cl_program prog = btOpenCLUtils::compileCLProgramFromString(cxMainContext, device, broadphaseKernelString, &pErrNum ,"",GRID_BROADPHASE_PATH); + + fpio.m_broadphaseBruteForceKernel = btOpenCLUtils::compileCLKernelFromString(cxMainContext,device, broadphaseKernelString, "broadphaseKernel" ,&pErrNum,prog); + fpio.m_initializeGpuAabbsKernelSimple = btOpenCLUtils::compileCLKernelFromString(cxMainContext,device, broadphaseKernelString, "initializeGpuAabbsSimple" ,&pErrNum,prog); + fpio.m_initializeGpuAabbsKernelFull = btOpenCLUtils::compileCLKernelFromString(cxMainContext,device, broadphaseKernelString, "initializeGpuAabbsFull" ,&pErrNum,prog); + + fpio.m_broadphaseColorKernel = btOpenCLUtils::compileCLKernelFromString(cxMainContext,device, broadphaseKernelString, "broadphaseColorKernel" ,&pErrNum,prog); + + fpio.m_setupBodiesKernel = btOpenCLUtils::compileCLKernelFromString(cxMainContext,device, broadphaseKernelString, "setupBodiesKernel" ,&pErrNum,prog); + fpio.m_copyVelocitiesKernel = btOpenCLUtils::compileCLKernelFromString(cxMainContext,device, broadphaseKernelString, "copyVelocitiesKernel" ,&pErrNum,prog); + + + +} + +void findPairsOpenCLBruteForce(btFindPairsIO& fpio) +{ + + int ciErrNum = 0; + + int numObjects = fpio.m_numObjects; + int offset = fpio.m_positionOffset; + + ciErrNum = clSetKernelArg(fpio.m_broadphaseBruteForceKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(fpio.m_broadphaseBruteForceKernel, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(fpio.m_broadphaseBruteForceKernel, 2, sizeof(cl_mem), (void*)&fpio.m_clObjectsBuffer); + + size_t numWorkItems = numObjects;///workGroupSize*((NUM_OBJECTS + (workGroupSize)) / workGroupSize); + size_t workGroupSize = 64; + ciErrNum = clEnqueueNDRangeKernel(fpio.m_cqCommandQue, fpio.m_broadphaseBruteForceKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); +} + +void setupGpuAabbsFull(btFindPairsIO& fpio, cl_mem bodies) +{ + + int ciErrNum = 0; + + int numObjects = fpio.m_numObjects; + int offset = fpio.m_positionOffset; + + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelFull, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelFull, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelFull, 2, sizeof(cl_mem), (void*)&fpio.m_clObjectsBuffer); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelFull, 3, sizeof(cl_mem), (void*)&bodies); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelFull, 4, sizeof(cl_mem), (void*)&fpio.m_dlocalShapeAABB); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelFull, 5, sizeof(cl_mem), (void*)&fpio.m_dAABB); + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((numObjects+ (workGroupSize)) / workGroupSize); + + ciErrNum = clEnqueueNDRangeKernel(fpio.m_cqCommandQue, fpio.m_initializeGpuAabbsKernelFull, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); +} + +void setupGpuAabbsSimple(btFindPairsIO& fpio) +{ + + int ciErrNum = 0; + + int numObjects = fpio.m_numObjects; + int offset = fpio.m_positionOffset; + + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelSimple, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelSimple, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelSimple, 2, sizeof(cl_mem), (void*)&fpio.m_clObjectsBuffer); + ciErrNum = clSetKernelArg(fpio.m_initializeGpuAabbsKernelSimple, 3, sizeof(cl_mem), (void*)&fpio.m_dAABB); + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((numObjects+ (workGroupSize)) / workGroupSize); + + ciErrNum = clEnqueueNDRangeKernel(fpio.m_cqCommandQue, fpio.m_initializeGpuAabbsKernelSimple, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); +} + + +void setupBodies(btFindPairsIO& fpio, cl_mem linVelMem, cl_mem angVelMem, cl_mem bodies, cl_mem bodyInertias) +{ + int ciErrNum = 0; + + int numObjects = fpio.m_numObjects; + int offset = fpio.m_positionOffset; + + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 1, sizeof(int), &fpio.m_numObjects); + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 2, sizeof(cl_mem), (void*)&fpio.m_clObjectsBuffer); + + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 3, sizeof(cl_mem), (void*)&linVelMem); + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 4, sizeof(cl_mem), (void*)&angVelMem); + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 5, sizeof(cl_mem), (void*)&bodies); + ciErrNum = clSetKernelArg(fpio.m_setupBodiesKernel, 6, sizeof(cl_mem), (void*)&bodyInertias); + + if (numObjects) + { + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((numObjects+ (workGroupSize)) / workGroupSize); + + ciErrNum = clEnqueueNDRangeKernel(fpio.m_cqCommandQue, fpio.m_setupBodiesKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + +} + + +void copyBodyVelocities(btFindPairsIO& fpio, cl_mem linVelMem, cl_mem angVelMem, cl_mem bodies, cl_mem bodyInertias) +{ + int ciErrNum = 0; + + int numObjects = fpio.m_numObjects; + int offset = fpio.m_positionOffset; + + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 1, sizeof(int), &fpio.m_numObjects); + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 2, sizeof(cl_mem), (void*)&fpio.m_clObjectsBuffer); + + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 3, sizeof(cl_mem), (void*)&linVelMem); + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 4, sizeof(cl_mem), (void*)&angVelMem); + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 5, sizeof(cl_mem), (void*)&bodies); + ciErrNum = clSetKernelArg(fpio.m_copyVelocitiesKernel, 6, sizeof(cl_mem), (void*)&bodyInertias); + + if (numObjects) + { + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((numObjects+ (workGroupSize)) / workGroupSize); + + ciErrNum = clEnqueueNDRangeKernel(fpio.m_cqCommandQue, fpio.m_copyVelocitiesKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + +} + +void colorPairsOpenCL(btFindPairsIO& fpio) +{ + int ciErrNum = 0; + + int numObjects = fpio.m_numObjects; + int offset = fpio.m_positionOffset; + + ciErrNum = clSetKernelArg(fpio.m_broadphaseColorKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(fpio.m_broadphaseColorKernel, 1, sizeof(int), &fpio.m_numObjects); + ciErrNum = clSetKernelArg(fpio.m_broadphaseColorKernel, 2, sizeof(cl_mem), (void*)&fpio.m_clObjectsBuffer); + ciErrNum = clSetKernelArg(fpio.m_broadphaseColorKernel, 3, sizeof(cl_mem), (void*)&fpio.m_dAllOverlappingPairs); + ciErrNum = clSetKernelArg(fpio.m_broadphaseColorKernel, 4, sizeof(int), &fpio.m_numOverlap); + + + if (fpio.m_numOverlap) + { + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((fpio.m_numOverlap+ (workGroupSize)) / workGroupSize); + + ciErrNum = clEnqueueNDRangeKernel(fpio.m_cqCommandQue, fpio.m_broadphaseColorKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } +} + + + +void releaseFindPairs(btFindPairsIO& fpio) +{ + clReleaseKernel(fpio.m_initializeGpuAabbsKernelSimple); + clReleaseKernel(fpio.m_initializeGpuAabbsKernelFull); + clReleaseKernel(fpio.m_broadphaseColorKernel); + clReleaseKernel(fpio.m_broadphaseBruteForceKernel); + clReleaseKernel(fpio.m_setupBodiesKernel); + clReleaseKernel(fpio.m_copyVelocitiesKernel); + + +} + diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.h b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.h new file mode 100644 index 000000000..771543685 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/findPairsOpenCL.h @@ -0,0 +1,90 @@ + +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Roman Ponomarev, Erwin Coumans + +#ifndef FIND_PAIRS_H +#define FIND_PAIRS_H + +#include "../basic_initialize/btOpenCLInclude.h" + +struct btKernelInfo +{ + int m_Id; + cl_kernel m_kernel; + char* m_name; + int m_workgroupSize; +}; + + + +struct btFindPairsIO +{ + int m_numObjects; + + cl_mem m_clObjectsBuffer; //for memory layout details see main.cpp (todo, make it flexible) + int m_positionOffset;//offset in m_clObjectsBuffer where position array starts + + cl_command_queue m_cqCommandQue; + cl_kernel m_initializeGpuAabbsKernelSimple; + cl_kernel m_initializeGpuAabbsKernelFull; + cl_kernel m_broadphaseColorKernel; + cl_kernel m_broadphaseBruteForceKernel; + + cl_kernel m_setupBodiesKernel; + cl_kernel m_copyVelocitiesKernel; + + cl_context m_mainContext; + cl_device_id m_device; + + cl_kernel m_calcHashAabbKernel; + cl_kernel m_clearCellStartKernel; + cl_kernel m_findCellStartKernel; + cl_kernel m_findOverlappingPairsKernel; + cl_kernel m_computePairChangeKernel; + cl_kernel m_squeezePairBuffKernel; + + + cl_mem m_dAllOverlappingPairs; + int m_numOverlap; + + cl_mem m_dBpParams; + cl_mem m_dBodiesHash; + cl_mem m_dCellStart; + cl_mem m_dPairBuff; + cl_mem m_dPairBuffStartCurr; + cl_mem m_dlocalShapeAABB; + cl_mem m_dAABB; + cl_mem m_dPairScan; + cl_mem m_dPairOut; +}; + + +void initFindPairs(btFindPairsIO& fpio,cl_context cxMainContext, cl_device_id device, cl_command_queue commandQueue, int maxHandles,int maxPairsPerBody = 16); + +void findPairsOpenCLBruteForce(btFindPairsIO& fpio); + +void setupGpuAabbsSimple(btFindPairsIO& fpio); + +void setupGpuAabbsFull(btFindPairsIO& fpio, cl_mem bodies); + + +void colorPairsOpenCL(btFindPairsIO& fpio); + +void setupBodies(btFindPairsIO& fpio, cl_mem linVelMem, cl_mem angVelMem, cl_mem bodies, cl_mem bodyInertias); +void copyBodyVelocities(btFindPairsIO& fpio, cl_mem linVelMem, cl_mem angVelMem, cl_mem bodies, cl_mem bodyInertias); + +void releaseFindPairs(btFindPairsIO& fpio); + +#endif //FIND_PAIRS_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/integrateKernel.cl b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/integrateKernel.cl new file mode 100644 index 000000000..87d2b2569 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/integrateKernel.cl @@ -0,0 +1,116 @@ +MSTRINGIFY( + +float4 quatMult(float4 q1, float4 q2) +{ + float4 q; + q.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; + q.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z; + q.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x; + q.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z; + return q; +} + +float4 quatNorm(float4 q) +{ + float len = native_sqrt(dot(q, q)); + if(len > 0.f) + { + q *= 1.f / len; + } + else + { + q.x = q.y = q.z = 0.f; + q.w = 1.f; + } + return q; +} + + + + + +__kernel void + integrateTransformsKernel( const int startOffset, const int numNodes, __global float4 *g_vertexBuffer, + __global float4 *linVel, + __global float4 *pAngVel, + __global float* pBodyTimes) +{ + int nodeID = get_global_id(0); + + + + float BT_GPU_ANGULAR_MOTION_THRESHOLD = (0.25f * 3.14159254f); + float mAmplitude = 66.f; + float timeStep = 0.0166666f; + + if( nodeID < numNodes ) + { + + //g_vertexBuffer[nodeID + startOffset/4+numNodes] += pAngVel[nodeID]; + if (1) + { + float4 axis; + //add some hardcoded angular damping + pAngVel[nodeID].x *= 0.99f; + pAngVel[nodeID].y *= 0.99f; + pAngVel[nodeID].z *= 0.99f; + + float4 angvel = pAngVel[nodeID]; + float fAngle = native_sqrt(dot(angvel, angvel)); + //limit the angular motion + if(fAngle*timeStep > BT_GPU_ANGULAR_MOTION_THRESHOLD) + { + fAngle = BT_GPU_ANGULAR_MOTION_THRESHOLD / timeStep; + } + if(fAngle < 0.001f) + { + // use Taylor's expansions of sync function + axis = angvel * (0.5f*timeStep-(timeStep*timeStep*timeStep)*0.020833333333f * fAngle * fAngle); + } + else + { + // sync(fAngle) = sin(c*fAngle)/t + axis = angvel * ( native_sin(0.5f * fAngle * timeStep) / fAngle); + } + float4 dorn = axis; + dorn.w = native_cos(fAngle * timeStep * 0.5f); + float4 orn0 = g_vertexBuffer[nodeID + startOffset/4+numNodes]; + float4 predictedOrn = quatMult(dorn, orn0); + predictedOrn = quatNorm(predictedOrn); + g_vertexBuffer[nodeID + startOffset/4+numNodes]=predictedOrn; + } + + //linear velocity + g_vertexBuffer[nodeID + startOffset/4] += linVel[nodeID] * timeStep; + + } +} + + +__kernel void + sineWaveKernel( const int startOffset, const int numNodes, __global float4 *g_vertexBuffer, + __global float4 *linVel, + __global float4 *pAngVel, + __global float* pBodyTimes) +{ + int nodeID = get_global_id(0); + float timeStepPos = 0.000166666; + + float BT_GPU_ANGULAR_MOTION_THRESHOLD = (0.25f * 3.14159254f); + float mAmplitude = 166.f; + + + if( nodeID < numNodes ) + { + pBodyTimes[nodeID] += timeStepPos; + float4 position = g_vertexBuffer[nodeID + startOffset/4]; + position.x = native_cos(pBodyTimes[nodeID]*2.17f)*mAmplitude + native_sin(pBodyTimes[nodeID])*mAmplitude*0.5f; + position.y = native_cos(pBodyTimes[nodeID]*1.38f)*mAmplitude + native_sin(pBodyTimes[nodeID]*mAmplitude); + position.z = native_cos(pBodyTimes[nodeID]*2.17f)*mAmplitude + native_sin(pBodyTimes[nodeID]*0.777f)*mAmplitude; + g_vertexBuffer[nodeID + startOffset/4] = position; + } +} + + + +); \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/main.cpp new file mode 100644 index 000000000..3e52c1976 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/broadphase_benchmark/main.cpp @@ -0,0 +1,1565 @@ + +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +//starts crashing when more than 32700 objects on my Geforce 260, unless _USE_SUB_DATA is defined (still unstable though) +//runs fine with fewer objects + +#define NUM_OBJECTS_X 42 +//327 +#define NUM_OBJECTS_Y 42 +#define NUM_OBJECTS_Z 42 +//#define NUM_OBJECTS_Z 20 + +//#define _USE_SUB_DATA + +//#define NUM_OBJECTS_X 100 +//#define NUM_OBJECTS_Y 100 +//#define NUM_OBJECTS_Z 100 + +///RECREATE_CL_AND_SHADERS_ON_RESIZE will delete and re-create OpenCL and GLSL shaders/buffers at each resize +//#define RECREATE_CL_AND_SHADERS_ON_RESIZE + +/// +/// OpenCL - OpenGL interop example. Updating transforms of many cubes on GPU, without going through main memory/using the PCIe bus +/// Create all OpenGL resources AFTER create OpenCL context! +/// + + +#include +#include + +#include "btGlutInclude.h" +#include "../opengl_interop/btStopwatch.h" + + +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuaternion.h" +#include "LinearMath/btMatrix3x3.h" +static float sAngle(0); + +#include + +#ifdef _WIN32 +#include +#endif + +#include +#include +#include "../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h" +#include "../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h" +#include "btGridBroadphaseCl.h" + +#define USE_NEW +#ifdef USE_NEW +btGridBroadphaseCl* sBroadphase=0; +#else +btGpu3DGridBroadphase* sBroadphase=0; +#endif + +btAlignedObjectArray proxyArray; + + +#define RS_SCALE (1.0 / (1.0 + RAND_MAX)) + + +int randbiased (double x) { + for (;;) { + double p = rand () * RS_SCALE; + if (p >= x) return 0; + if (p+RS_SCALE <= x) return 1; + /* p < x < p+RS_SCALE */ + x = (x - p) * (1.0 + RAND_MAX); + } +} + +size_t randrange (size_t n) +{ + double xhi; + double resolution = n * RS_SCALE; + double x = resolution * rand (); /* x in [0,n) */ + size_t lo = (size_t) floor (x); + + xhi = x + resolution; + + for (;;) { + lo++; + if (lo >= xhi || randbiased ((lo - x) / (xhi - x))) return lo-1; + x = lo; + } +} + +//OpenCL stuff +#include "../basic_initialize/btOpenCLUtils.h" +#include "../opengl_interop/btOpenCLGLInteropBuffer.h" +#include "findPairsOpenCL.h" + +btFindPairsIO gFpIO; + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; +cl_device_id g_device; +static const size_t workGroupSize = 64; +cl_mem gLinVelMem; +cl_mem gAngVelMem; +cl_mem gBodyTimes; + +btVector3 m_cameraPosition(142,220,142); +btVector3 m_cameraTargetPosition(0,-30,0); +btScalar m_cameraDistance = 200; +btVector3 m_cameraUp(0,1,0); +float m_azi=-50.f; +float m_ele=0.f; + + + + +btOpenCLGLInteropBuffer* g_interopBuffer = 0; +cl_kernel g_sineWaveKernel; + + + +////for Adl +#include + +adl::DeviceCL* g_deviceCL=0; + + + +bool useCPU = false; +bool printStats = false; +bool runOpenCLKernels = true; + +#define MSTRINGIFY(A) #A +static char* interopKernelString = +#include "integrateKernel.cl" + + +btStopwatch gStopwatch; +int m_glutScreenWidth = 640; +int m_glutScreenHeight= 480; + +bool m_ortho = false; + +static GLuint instancingShader; // The instancing renderer +static GLuint cube_vao; +static GLuint cube_vbo; +static GLuint index_vbo; +static GLuint m_texturehandle; + +static bool done = false; +static GLint angle_loc = 0; +static GLint ModelViewMatrix; +static GLint ProjectionMatrix; + +void writeTransforms(); + +static GLint uniform_texture_diffuse = 0; + +//used for dynamic loading from disk (default switched off) +#define MAX_SHADER_LENGTH 8192 +static GLubyte shaderText[MAX_SHADER_LENGTH]; + +static const char* vertexShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"\n" +"\n" +"layout (location = 0) in vec4 position;\n" +"layout (location = 1) in vec4 instance_position;\n" +"layout (location = 2) in vec4 instance_quaternion;\n" +"layout (location = 3) in vec2 uvcoords;\n" +"layout (location = 4) in vec3 vertexnormal;\n" +"layout (location = 5) in vec4 instance_color;\n" +"layout (location = 6) in vec3 instance_scale;\n" +"\n" +"\n" +"uniform float angle = 0.0;\n" +"uniform mat4 ModelViewMatrix;\n" +"uniform mat4 ProjectionMatrix;\n" +"\n" +"out Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"out Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"\n" +"vec4 quatMul ( in vec4 q1, in vec4 q2 )\n" +"{\n" +" vec3 im = q1.w * q2.xyz + q1.xyz * q2.w + cross ( q1.xyz, q2.xyz );\n" +" vec4 dt = q1 * q2;\n" +" float re = dot ( dt, vec4 ( -1.0, -1.0, -1.0, 1.0 ) );\n" +" return vec4 ( im, re );\n" +"}\n" +"\n" +"vec4 quatFromAxisAngle(vec4 axis, in float angle)\n" +"{\n" +" float cah = cos(angle*0.5);\n" +" float sah = sin(angle*0.5);\n" +" float d = inversesqrt(dot(axis,axis));\n" +" vec4 q = vec4(axis.x*sah*d,axis.y*sah*d,axis.z*sah*d,cah);\n" +" return q;\n" +"}\n" +"//\n" +"// vector rotation via quaternion\n" +"//\n" +"vec4 quatRotate3 ( in vec3 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, vec4 ( p, 0.0 ) );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"vec4 quatRotate ( in vec4 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, p );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"\n" +"out vec3 lightDir,normal,ambient;\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 q = instance_quaternion;\n" +" ambient = vec3(0.2,0.2,0.2);\n" +" \n" +" \n" +" vec4 local_normal = (quatRotate3( vertexnormal,q));\n" +" vec3 light_pos = vec3(10000,10000,10000);\n" +" normal = normalize(ModelViewMatrix * local_normal).xyz;\n" +"\n" +" lightDir = normalize(light_pos);//gl_LightSource[0].position.xyz));\n" +"// lightDir = normalize(vec3(gl_LightSource[0].position));\n" +" \n" +" vec4 axis = vec4(1,1,1,0);\n" +" vec4 localcoord = quatRotate3( position.xyz*instance_scale,q);\n" +" vec4 vertexPos = ProjectionMatrix * ModelViewMatrix *(instance_position+localcoord);\n" +"\n" +" gl_Position = vertexPos;\n" +" \n" +" fragment.color = instance_color;\n" +" vert.texcoord = uvcoords;\n" +"}\n" +; + + +static const char* fragmentShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"in Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"in Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"uniform sampler2D Diffuse;\n" +"\n" +"in vec3 lightDir,normal,ambient;\n" +"\n" +"out vec4 color;\n" +"\n" +"void main_textured(void)\n" +"{\n" +" color = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 texel = fragment.color*texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +" vec3 ct,cf;\n" +" float intensity,at,af;\n" +" intensity = max(dot(lightDir,normalize(normal)),0.5);\n" +" cf = intensity*vec3(1.0,1.0,1.0);//intensity * (gl_FrontMaterial.diffuse).rgb+ambient;//gl_FrontMaterial.ambient.rgb;\n" +" af = 1.0;\n" +" \n" +" ct = texel.rgb;\n" +" at = texel.a;\n" +" \n" +" color = vec4(ct * cf, at * af); \n" +"}\n" +; + + +// Load the shader from the source text +void gltLoadShaderSrc(const char *szShaderSrc, GLuint shader) +{ + GLchar *fsStringPtr[1]; + + fsStringPtr[0] = (GLchar *)szShaderSrc; + glShaderSource(shader, 1, (const GLchar **)fsStringPtr, NULL); +} + + +//////////////////////////////////////////////////////////////// +// Load the shader from the specified file. Returns false if the +// shader could not be loaded +bool gltLoadShaderFile(const char *szFile, GLuint shader) +{ + GLint shaderLength = 0; + FILE *fp; + + // Open the shader file + fp = fopen(szFile, "r"); + if(fp != NULL) + { + // See how long the file is + while (fgetc(fp) != EOF) + shaderLength++; + + // Allocate a block of memory to send in the shader + assert(shaderLength < MAX_SHADER_LENGTH); // make me bigger! + if(shaderLength > MAX_SHADER_LENGTH) + { + fclose(fp); + return false; + } + + // Go back to beginning of file + rewind(fp); + + // Read the whole file in + if (shaderText != NULL) + fread(shaderText, 1, shaderLength, fp); + + // Make sure it is null terminated and close the file + shaderText[shaderLength] = '\0'; + fclose(fp); + } + else + return false; + + // printf(shaderText); + // Load the string + gltLoadShaderSrc((const char *)shaderText, shader); + + return true; +} + + +///////////////////////////////////////////////////////////////// +// Load a pair of shaders, compile, and link together. Specify the complete +// file path for each shader. Note, there is no support for +// just loading say a vertex program... you have to do both. +GLuint gltLoadShaderPair(const char *szVertexProg, const char *szFragmentProg, bool loadFromFile) +{ + // Temporary Shader objects + GLuint hVertexShader; + GLuint hFragmentShader; + GLuint hReturn = 0; + GLint testVal; + + // Create shader objects + hVertexShader = glCreateShader(GL_VERTEX_SHADER); + hFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (loadFromFile) + { + + if(gltLoadShaderFile(szVertexProg, hVertexShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + if(gltLoadShaderFile(szFragmentProg, hFragmentShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + } else + { + gltLoadShaderSrc(vertexShader, hVertexShader); + gltLoadShaderSrc(fragmentShader, hFragmentShader); + } + // Compile them + glCompileShader(hVertexShader); + glCompileShader(hFragmentShader); + + // Check for errors + glGetShaderiv(hVertexShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hVertexShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + glGetShaderiv(hFragmentShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hFragmentShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + // Link them - assuming it works... + hReturn = glCreateProgram(); + glAttachShader(hReturn, hVertexShader); + glAttachShader(hReturn, hFragmentShader); + + glLinkProgram(hReturn); + + // These are no longer needed + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + + // Make sure link worked too + glGetProgramiv(hReturn, GL_LINK_STATUS, &testVal); + if(testVal == GL_FALSE) + { + glDeleteProgram(hReturn); + return (GLuint)NULL; + } + + return hReturn; +} + +///position xyz, unused w, normal, uv +static const GLfloat cube_vertices[] = +{ + -1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 0,0,//0 + 1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 1,0,//1 + 1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 1,1,//2 + -1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 0,1 ,//3 + + -1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 0,0,//4 + 1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 1,0,//5 + 1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 1,1,//6 + -1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 0,1,//7 + + -1.0f, -1.0f, -1.0f, 1.0f, -1,0,0, 0,0, + -1.0f, 1.0f, -1.0f, 1.0f, -1,0,0, 1,0, + -1.0f, 1.0f, 1.0f, 1.0f, -1,0,0, 1,1, + -1.0f, -1.0f, 1.0f, 1.0f, -1,0,0, 0,1, + + 1.0f, -1.0f, -1.0f, 1.0f, 1,0,0, 0,0, + 1.0f, 1.0f, -1.0f, 1.0f, 1,0,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 1,0,0, 1,1, + 1.0f, -1.0f, 1.0f, 1.0f, 1,0,0, 0,1, + + -1.0f, -1.0f, -1.0f, 1.0f, 0,-1,0, 0,0, + -1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,0, + 1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,1, + 1.0f,-1.0f, -1.0f, 1.0f, 0,-1,0, 0,1, + + -1.0f, 1.0f, -1.0f, 1.0f, 0,1,0, 0,0, + -1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,1, + 1.0f,1.0f, -1.0f, 1.0f, 0,1,0, 0,1, +}; + +static const int cube_indices[]= +{ + 0,1,2,0,2,3,//ground face + 4,5,6,4,6,7,//top face + 8,9,10,8,10,11, + 12,13,14,12,14,15, + 16,17,18,16,18,19, + 20,21,22,20,22,23 +}; + +int m_mouseOldX = -1; +int m_mouseOldY = -1; +int m_mouseButtons = 0; + + +void mouseFunc(int button, int state, int x, int y) +{ + if (state == 0) + { + m_mouseButtons |= 1<0) + { + g_device= btOpenCLUtils::getDevice(g_cxMainContext,0); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(g_device,clInfo); + btOpenCLUtils::printDeviceInfo(g_device); + // create a command-queue + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, g_device, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + //normally you would create and execute kernels using this command queue + + } + + +} + +#define NUM_OBJECTS (NUM_OBJECTS_X*NUM_OBJECTS_Y*NUM_OBJECTS_Z) +#define POSITION_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) +#define ORIENTATION_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) +#define COLOR_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) +#define SCALE_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*3) + + +GLfloat* instance_positions_ptr = 0; +GLfloat* instance_quaternion_ptr = 0; +GLfloat* instance_colors_ptr = 0; +GLfloat* instance_scale_ptr= 0; + + +void DeleteShaders() +{ + glDeleteVertexArrays(1, &cube_vao); + glDeleteBuffers(1,&index_vbo); + glDeleteBuffers(1,&cube_vbo); + glDeleteProgram(instancingShader); +} + + +void InitShaders() +{ + + btOverlappingPairCache* overlappingPairCache=0; +#ifdef USE_NEW + sBroadphase = new btGridBroadphaseCl(overlappingPairCache,btVector3(3.f, 3.f, 3.f), 32, 32, 32,NUM_OBJECTS, NUM_OBJECTS, 64, 100.f, 16, + g_cxMainContext ,g_device,g_cqCommandQue); +#else + sBroadphase = new btGpu3DGridBroadphase(btVector3(10.f, 10.f, 10.f), 32, 32, 32,NUM_OBJECTS, NUM_OBJECTS, 64, 100.f, 16); +#endif + + + +// sBroadphase = new bt3dGridBroadphaseOCL(overlappingPairCache,btVector3(10.f, 10.f, 10.f), 32, 32, 32,NUM_OBJECTS, NUM_OBJECTS, 64, 100.f, 16, +// g_cxMainContext ,g_device,g_cqCommandQue); + + + + bool loadFromFile = false; + instancingShader = gltLoadShaderPair("instancing.vs","instancing.fs", loadFromFile); + + glLinkProgram(instancingShader); + glUseProgram(instancingShader); + angle_loc = glGetUniformLocation(instancingShader, "angle"); + ModelViewMatrix = glGetUniformLocation(instancingShader, "ModelViewMatrix"); + ProjectionMatrix = glGetUniformLocation(instancingShader, "ProjectionMatrix"); + uniform_texture_diffuse = glGetUniformLocation(instancingShader, "Diffuse"); + + GLuint offset = 0; + + + glGenBuffers(1, &cube_vbo); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + + instance_positions_ptr = (GLfloat*)new float[NUM_OBJECTS*4]; + instance_quaternion_ptr = (GLfloat*)new float[NUM_OBJECTS*4]; + instance_colors_ptr = (GLfloat*)new float[NUM_OBJECTS*4]; + instance_scale_ptr = (GLfloat*)new float[NUM_OBJECTS*3]; + + int index=0; + for (int i=0;icreateProxy(aabbMin,aabbMax,shapeType,myptr,1,1,0,0);//m_dispatcher); + proxyArray.push_back(proxy); + + instance_quaternion_ptr[index*4]=0; + instance_quaternion_ptr[index*4+1]=0; + instance_quaternion_ptr[index*4+2]=0; + instance_quaternion_ptr[index*4+3]=1; + + instance_colors_ptr[index*4]=j m_glutScreenHeight) + { + aspect = m_glutScreenWidth / (float)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + } else + { + aspect = m_glutScreenHeight / (float)m_glutScreenWidth; + extents.setValue(1.0f, aspect*1.f,0); + } + + + if (m_ortho) + { + // reset matrix + glLoadIdentity(); + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + glOrtho(lower.getX(), upper.getX(), lower.getY(), upper.getY(),-1000,1000); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } else + { + if (m_glutScreenWidth > m_glutScreenHeight) + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } else + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(m_cameraPosition[0], m_cameraPosition[1], m_cameraPosition[2], + m_cameraTargetPosition[0], m_cameraTargetPosition[1], m_cameraTargetPosition[2], + m_cameraUp.getX(),m_cameraUp.getY(),m_cameraUp.getZ()); + } + +} + + + +void myinit() +{ + + + + // GLfloat light_ambient[] = { btScalar(0.2), btScalar(0.2), btScalar(0.2), btScalar(1.0) }; + GLfloat light_ambient[] = { btScalar(1.0), btScalar(1.2), btScalar(0.2), btScalar(1.0) }; + + GLfloat light_diffuse[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0) }; + GLfloat light_specular[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0 )}; + /* light_position is NOT default value */ + GLfloat light_position0[] = { btScalar(10000.0), btScalar(10000.0), btScalar(10000.0), btScalar(0.0 )}; + GLfloat light_position1[] = { btScalar(-1.0), btScalar(-10.0), btScalar(-1.0), btScalar(0.0) }; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + + glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + + // glShadeModel(GL_FLAT);//GL_SMOOTH); + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glClearColor(float(0.7),float(0.7),float(0.7),float(0)); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + + static bool m_textureenabled = true; + static bool m_textureinitialized = false; + + + if(m_textureenabled) + { + if(!m_textureinitialized) + { + glActiveTexture(GL_TEXTURE0); + + GLubyte* image=new GLubyte[256*256*3]; + for(int y=0;y<256;++y) + { + const int t=y>>5; + GLubyte* pi=image+y*256*3; + for(int x=0;x<256;++x) + { + const int s=x>>5; + const GLubyte b=180; + GLubyte c=b+((s+t&1)&1)*(255-b); + pi[0]=c; + pi[1]=c; + pi[2]=c; + pi+=3; + } + } + + glGenTextures(1,(GLuint*)&m_texturehandle); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + gluBuild2DMipmaps(GL_TEXTURE_2D,3,256,256,GL_RGB,GL_UNSIGNED_BYTE,image); + delete[] image; + m_textureinitialized=true; + } + // glMatrixMode(GL_TEXTURE); + // glLoadIdentity(); + // glMatrixMode(GL_MODELVIEW); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + + } else + { + glDisable(GL_TEXTURE_2D); + } + + glEnable(GL_COLOR_MATERIAL); + + + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); +} + +//#pragma optimize( "g", off ) + + + +void writeTransforms() +{ + + + glFlush(); + char* bla = (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + + float* positions = (float*)(bla+sizeof(cube_vertices)); + float* orientations = (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE); + float* colors= (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE); + float* scaling= (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE); + + // positions[0]+=0.001f; + + static int offset=0; + //offset++; + + static btVector3 axis(1,0,0); + sAngle += 0.01f; + int index=0; + btQuaternion orn(axis,sAngle); + for (int i=0;igetCLBUffer(); + cl_int ciErrNum = CL_SUCCESS; + ciErrNum = clEnqueueAcquireGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, NULL); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + if (runOpenCLKernels) + { + int numObjects = NUM_OBJECTS; + int offset = (sizeof(cube_vertices) )/4; + + ciErrNum = clSetKernelArg(g_sineWaveKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(g_sineWaveKernel, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(g_sineWaveKernel, 2, sizeof(cl_mem), (void*)&clBuffer ); + + ciErrNum = clSetKernelArg(g_sineWaveKernel, 3, sizeof(cl_mem), (void*)&gLinVelMem); + ciErrNum = clSetKernelArg(g_sineWaveKernel, 4, sizeof(cl_mem), (void*)&gAngVelMem); + ciErrNum = clSetKernelArg(g_sineWaveKernel, 5, sizeof(cl_mem), (void*)&gBodyTimes); + + + + + + size_t numWorkItems = workGroupSize*((NUM_OBJECTS + (workGroupSize)) / workGroupSize); + ciErrNum = clEnqueueNDRangeKernel(g_cqCommandQue, g_sineWaveKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + + ciErrNum = clEnqueueReleaseGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, 0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + clFinish(g_cqCommandQue); + + } + +} + + +void cpuBroadphase() +{ + glFlush(); + char* bla = (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + + float* positions = (float*)(bla+sizeof(cube_vertices)); + float* orientations = (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE); + float* colors= (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE); + float* scaling= (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE); + + int index=0; + + for (int i=0;isetAabb(proxyArray[index],aabbMin,aabbMax,0); + + index++; + } + } + } + +#ifdef USE_NEW + + +#else + sBroadphase->calculateOverlappingPairs(0); + int overlap = sBroadphase->getOverlappingPairCache()->getNumOverlappingPairs(); + for (int i=0;igetOverlappingPairCache()->getOverlappingPairArray()[i]; + int indexA = (int)pair.m_pProxy0->m_clientObject; + int indexB = (int)pair.m_pProxy1->m_clientObject; + colors[indexA*4] = 1.f; + colors[indexA*4+1] = 0.f; + colors[indexA*4+2] = 0.f; + colors[indexA*4+3] = 1.f; + + colors[indexB*4] = 1.f; + colors[indexB*4+1] = 0.f; + colors[indexB*4+2] = 0.f; + colors[indexB*4+3] = 1.f; + } +#endif + + //now color the overlap + + + + glUnmapBuffer( GL_ARRAY_BUFFER); + //if this glFinish is removed, the animation is not always working/blocks + //@todo: figure out why + glFlush(); +} + +void broadphase() +{ + if (useCPU) + { + cpuBroadphase(); + + } + else + { + + glFinish(); + + cl_mem clBuffer = g_interopBuffer->getCLBUffer(); + cl_int ciErrNum = CL_SUCCESS; + ciErrNum = clEnqueueAcquireGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, NULL); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + if (runOpenCLKernels) + { + + gFpIO.m_numObjects = NUM_OBJECTS; + gFpIO.m_positionOffset = (sizeof(cube_vertices) )/4; + gFpIO.m_clObjectsBuffer = clBuffer; + gFpIO.m_dAABB = sBroadphase->m_dAABB; + setupGpuAabbsSimple(gFpIO); + + sBroadphase->calculateOverlappingPairs(0, NUM_OBJECTS); + + + gFpIO.m_dAllOverlappingPairs = sBroadphase->m_dAllOverlappingPairs; + gFpIO.m_numOverlap = sBroadphase->m_numPrefixSum; + + colorPairsOpenCL(gFpIO); + + } + + ciErrNum = clEnqueueReleaseGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, 0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + clFinish(g_cqCommandQue); + + + + } +} + + +//#pragma optimize( "g", on ) + +void RenderScene(void) +{ + +#if 0 + float modelview[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + // get the current modelview matrix + glGetFloatv(GL_MODELVIEW_MATRIX , modelview); + float projection[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + glGetFloatv(GL_PROJECTION_MATRIX, projection); +#endif + + myinit(); + + updateCamera(); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //render coordinate system + glBegin(GL_LINES); + glColor3f(1,0,0); + glVertex3f(0,0,0); + glVertex3f(1,0,0); + glColor3f(0,1,0); + glVertex3f(0,0,0); + glVertex3f(0,1,0); + glColor3f(0,0,1); + glVertex3f(0,0,0); + glVertex3f(0,0,1); + glEnd(); + + //do a finish, to make sure timings are clean + // glFinish(); + + float start = gStopwatch.getTimeMilliseconds(); + + // glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + + updatePos(); + + broadphase(); + + //useCPU = true; + + float stop = gStopwatch.getTimeMilliseconds(); + gStopwatch.reset(); + + if (printStats) + { + printf("updatePos=%f ms on ",stop-start); + + if (useCPU) + { + printf("CPU \n"); + } else + { + printf("OpenCL "); + if (runOpenCLKernels) + printf("running the kernels"); + else + printf("without running the kernels"); + printf("\n"); + } + } + + glBindVertexArray(cube_vao); + + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 9*sizeof(float), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices))); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE)); + int uvoffset = 7*sizeof(float); + int normaloffset = 4*sizeof(float); + + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)uvoffset); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)normaloffset); + glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE)); + glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE)); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + glEnableVertexAttribArray(6); + + glVertexAttribDivisor(0, 0); + glVertexAttribDivisor(1, 1); + glVertexAttribDivisor(2, 1); + glVertexAttribDivisor(3, 0); + glVertexAttribDivisor(4, 0); + glVertexAttribDivisor(5, 1); + glVertexAttribDivisor(6, 1); + + glUseProgram(instancingShader); + glUniform1f(angle_loc, 0); + GLfloat pm[16]; + glGetFloatv(GL_PROJECTION_MATRIX, pm); + glUniformMatrix4fv(ProjectionMatrix, 1, false, &pm[0]); + + GLfloat mvm[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mvm); + glUniformMatrix4fv(ModelViewMatrix, 1, false, &mvm[0]); + + glUniform1i(uniform_texture_diffuse, 0); + + glFlush(); + int numInstances = NUM_OBJECTS; + int indexCount = sizeof(cube_indices)/sizeof(int); + int indexOffset = 0; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_vbo); + glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void*)indexOffset, numInstances); + + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + glutSwapBuffers(); + glutPostRedisplay(); + + GLint err = glGetError(); + assert(err==GL_NO_ERROR); +} + + +void ChangeSize(int w, int h) +{ + m_glutScreenWidth = w; + m_glutScreenHeight = h; + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + delete g_interopBuffer; + clReleaseKernel(g_sineWaveKernel); + releaseFindPairs(fpio); + DeleteCL(); + DeleteShaders(); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + + // Set Viewport to window dimensions + glViewport(0, 0, w, h); + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + InitCL(); + InitShaders(); + + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); + g_sineWaveKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, interopKernelString, "interopKernel" ); + initFindPairs(...); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + +} + +void Keyboard(unsigned char key, int x, int y) +{ + switch (key) + { + case 27: + done = true; + break; + case 'O': + case 'o': + { + m_ortho = !m_ortho; + break; + } + case 'c': + case 'C': + { + useCPU = !useCPU; + if (useCPU) + printf("using CPU\n"); + else + printf("using OpenCL\n"); + break; + } + case 's': + case 'S': + { + printStats = !printStats; + break; + } + case 'k': + case 'K': + { + runOpenCLKernels=!runOpenCLKernels; + break; + } + case 'q': + case 'Q': + exit(0); + default: + break; + } +} + +// Cleanup +void ShutdownRC(void) +{ + glDeleteBuffers(1, &cube_vbo); + glDeleteVertexArrays(1, &cube_vao); +} + +int main(int argc, char* argv[]) +{ + srand(0); + // printf("vertexShader = \n%s\n",vertexShader); + // printf("fragmentShader = \n%s\n",fragmentShader); + + glutInit(&argc, argv); + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); + + + glutInitWindowSize(m_glutScreenWidth, m_glutScreenHeight); + char buf[1024]; + sprintf(buf,"OpenCL broadphase benchmark, %d cubes on the GPU", NUM_OBJECTS); + glutCreateWindow(buf); + + glutReshapeFunc(ChangeSize); + + glutMouseFunc(mouseFunc); + glutMotionFunc(mouseMotionFunc); + + glutKeyboardFunc(Keyboard); + glutDisplayFunc(RenderScene); + + GLenum err = glewInit(); + if (GLEW_OK != err) + { + /* Problem: glewInit failed, something is seriously wrong. */ + fprintf(stderr, "Error: %s\n", glewGetErrorString(err)); + } + + //ChangeSize(m_glutScreenWidth,m_glutScreenHeight); + + InitCL(); + + +#define CUSTOM_CL_INITIALIZATION +#ifdef CUSTOM_CL_INITIALIZATION + g_deviceCL = new adl::DeviceCL(); + g_deviceCL->m_deviceIdx = g_device; + g_deviceCL->m_context = g_cxMainContext; + g_deviceCL->m_commandQueue = g_cqCommandQue; + +#else + DeviceUtils::Config cfg; + cfg.m_type = DeviceUtils::Config::DEVICE_CPU; + g_deviceCL = DeviceUtils::allocate( TYPE_CL, cfg ); +#endif + + int size = NUM_OBJECTS; + adl::Buffer linvelBuf( g_deviceCL, size ); + adl::Buffer angvelBuf( g_deviceCL, size ); + adl::Buffer bodyTimes(g_deviceCL,size); + + gLinVelMem = (cl_mem)linvelBuf.m_ptr; + gAngVelMem = (cl_mem)angvelBuf.m_ptr; + gBodyTimes = (cl_mem)bodyTimes.m_ptr; + + btVector3* linVelHost= new btVector3[size]; + btVector3* angVelHost = new btVector3[size]; + float* bodyTimesHost = new float[size]; + + for (int i=0;i + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; +cl_kernel g_atomicsKernel; +static const size_t workGroupSize = 128;//todo figure out an appropriate workgroup size suitable for the OpenCL platform/context/device/kernel +#define NUM_OBJECTS 1024 + +#include "globalAtomicsKernel.h" + + +char * findAndReplace( char const * const original, char const * const pattern, char const * const replacement); + + +#include +#include + + +int main(int argc, char* argv[]) +{ + int ciErrNum = 0; + + printf("press a key to start\n"); + getchar(); + + const char* vendorSDK = btOpenCLUtils::getSdkVendorName(); + printf("This program was compiled using the %s OpenCL SDK\n",vendorSDK); + + cl_device_type deviceType = CL_DEVICE_TYPE_GPU;//CL_DEVICE_TYPE_ALL + + void* glCtx=0; + void* glDC = 0; + printf("Initialize OpenCL using btOpenCLUtils::createContextFromType for CL_DEVICE_TYPE_GPU\n"); + g_cxMainContext = btOpenCLUtils::createContextFromType(deviceType, &ciErrNum, glCtx, glDC); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + int numDev = btOpenCLUtils::getNumDevices(g_cxMainContext); + + if (numDev>0) + { + int deviceIndex=0; + + cl_device_id device; + device = btOpenCLUtils::getDevice(g_cxMainContext,deviceIndex); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(device,clInfo); + btOpenCLUtils::printDeviceInfo(device); + + + const char* globalAtomicsKernelStringPatched = globalAtomicsKernelString; + if (!strstr(clInfo.m_deviceExtensions,"cl_ext_atomic_counters_32")) + { + globalAtomicsKernelStringPatched = findAndReplace(globalAtomicsKernelString,"counter32_t", "volatile __global int*"); + } + + + + // create a command-queue + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, device, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + cl_mem counterBuffer = clCreateBuffer(g_cxMainContext, CL_MEM_READ_WRITE, sizeof(int), NULL, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + char* kernelMethods[] = + { + "globalAtomicKernelOpenCL1_1", + "counterAtomicKernelExt", + "globalAtomicKernelExt", + "globalAtomicKernelCounters32Broken" + }; + int numKernelMethods = sizeof(kernelMethods)/sizeof(char*); + + for (int i=0;i +#include + +char * findAndReplace( + char const * const original, + char const * const pattern, + char const * const replacement +) { + size_t const replen = strlen(replacement); + size_t const patlen = strlen(pattern); + size_t const orilen = strlen(original); + + size_t patcnt = 0; + const char * oriptr; + const char * patloc; + + // find how many times the pattern occurs in the original string + for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen) + { + patcnt++; + } + + { + // allocate memory for the new string + size_t const retlen = orilen + patcnt * (replen - patlen); + char * const returned = (char *) malloc( sizeof(char) * (retlen + 1) ); + + if (returned != NULL) + { + // copy the original string, + // replacing all the instances of the pattern + char * retptr = returned; + for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen) + { + size_t const skplen = patloc - oriptr; + // copy the section until the occurence of the pattern + strncpy(retptr, oriptr, skplen); + retptr += skplen; + // copy the replacement + strncpy(retptr, replacement, replen); + retptr += replen; + } + // copy the rest of the string. + strcpy(retptr, oriptr); + } + return returned; + } +} + +#ifdef _WIN32 +#pragma warning( pop ) +#endif //_WIN32 diff --git a/Extras/RigidBodyGpuPipeline/opencl/global_atomics/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/global_atomics/premake4.lua new file mode 100644 index 000000000..3a926c990 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/global_atomics/premake4.lua @@ -0,0 +1,4 @@ + + include "AMD" + --include "Intel" + --include "NVIDIA" diff --git a/Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringify.py b/Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringify.py new file mode 100644 index 000000000..e79e281e4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringify.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import sys +import os +import shutil + +arg = sys.argv[1] +fh = open(arg) + +print 'static const char* '+sys.argv[2]+'= \\' +for line in fh.readlines(): + a = line.strip('\n') + print '"'+a+'\\n"' +print ';' diff --git a/Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringifykernels.bat b/Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringifykernels.bat new file mode 100644 index 000000000..1415f8e5b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/global_atomics/stringifykernels.bat @@ -0,0 +1,5 @@ +stringify.py global_atomics.cl globalAtomicsKernelString >globalAtomicsKernel.h + + + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/AMD/premake4.lua new file mode 100644 index 000000000..2000f3ea2 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/AMD/premake4.lua @@ -0,0 +1,58 @@ + + hasCL = findOpenCL_AMD() + + if (hasCL) then + + project "OpenCL_gpu_rigidbody_pipeline_AMD" + + initOpenCL_AMD() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + initOpenGL() + initGlut() + initGlew() + + + includedirs { + "../../../rendering/BulletMath", + "../../primitives", + "../../../../../src" + } + + files { + "../main.cpp", + "../btConvexUtility.cpp", + "../btConvexUtility.h", + "../btGpuNarrowPhaseAndSolver.cpp", + "../btGpuNarrowPhaseAndSolver.h", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.cpp", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.h", + "../../../../../src/LinearMath/btConvexHullComputer.cpp", + "../../../../../src/LinearMath/btConvexHullComputer.h", + "../../broadphase_benchmark/findPairsOpenCL.cpp", + "../../broadphase_benchmark/findPairsOpenCL.h", + "../../broadphase_benchmark/btGridBroadphaseCL.cpp", + "../../broadphase_benchmark/btGridBroadphaseCL.h", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h", + "../../../../../src/LinearMath/btAlignedAllocator.cpp", + "../../../../../src/LinearMath/btQuickprof.cpp", + "../../../../../src/LinearMath/btQuickprof.h", + "../../../../../src/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/CommandLineArgs.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/CommandLineArgs.h new file mode 100644 index 000000000..b64610303 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/CommandLineArgs.h @@ -0,0 +1,91 @@ +#ifndef COMMAND_LINE_ARGS_H +#define COMMAND_LINE_ARGS_H + +/****************************************************************************** + * Command-line parsing + ******************************************************************************/ +#include +#include +#include +#include +class CommandLineArgs +{ +protected: + + std::map pairs; + +public: + + // Constructor + CommandLineArgs(int argc, char **argv) + { + using namespace std; + + for (int i = 1; i < argc; i++) + { + string arg = argv[i]; + + if ((arg[0] != '-') || (arg[1] != '-')) { + continue; + } + + string::size_type pos; + string key, val; + if ((pos = arg.find( '=')) == string::npos) { + key = string(arg, 2, arg.length() - 2); + val = ""; + } else { + key = string(arg, 2, pos - 2); + val = string(arg, pos + 1, arg.length() - 1); + } + pairs[key] = val; + } + } + + bool CheckCmdLineFlag(const char* arg_name) + { + using namespace std; + map::iterator itr; + if ((itr = pairs.find(arg_name)) != pairs.end()) { + return true; + } + return false; + } + + template + void GetCmdLineArgument(const char *arg_name, T &val); + + int ParsedArgc() + { + return pairs.size(); + } +}; + +template +void CommandLineArgs::GetCmdLineArgument(const char *arg_name, T &val) +{ + using namespace std; + map::iterator itr; + if ((itr = pairs.find(arg_name)) != pairs.end()) { + istringstream strstream(itr->second); + strstream >> val; + } +} + +template <> +void CommandLineArgs::GetCmdLineArgument(const char* arg_name, char* &val) +{ + using namespace std; + map::iterator itr; + if ((itr = pairs.find(arg_name)) != pairs.end()) { + + string s = itr->second; + val = (char*) malloc(sizeof(char) * (s.length() + 1)); + strcpy(val, s.c_str()); + + } else { + val = NULL; + } +} + +#endif //COMMAND_LINE_ARGS_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/Intel/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/Intel/premake4.lua new file mode 100644 index 000000000..541dc7941 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/Intel/premake4.lua @@ -0,0 +1,58 @@ + + hasCL = findOpenCL_Intel() + + if (hasCL) then + + project "OpenCL_gpu_rigidbody_pipeline_Intel" + + initOpenCL_Intel() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + initOpenGL() + initGlut() + initGlew() + + + includedirs { + "../../../rendering/BulletMath", + "../../primitives", + "../../../../../src" + } + + files { + "../main.cpp", + "../btConvexUtility.cpp", + "../btConvexUtility.h", + "../btGpuNarrowPhaseAndSolver.cpp", + "../btGpuNarrowPhaseAndSolver.h", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.cpp", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.h", + "../../../../../src/LinearMath/btConvexHullComputer.cpp", + "../../../../../src/LinearMath/btConvexHullComputer.h", + "../../broadphase_benchmark/findPairsOpenCL.cpp", + "../../broadphase_benchmark/findPairsOpenCL.h", + "../../broadphase_benchmark/btGridBroadphaseCL.cpp", + "../../broadphase_benchmark/btGridBroadphaseCL.h", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h", + "../../../../../src/LinearMath/btAlignedAllocator.cpp", + "../../../../../src/LinearMath/btQuickprof.cpp", + "../../../../../src/LinearMath/btQuickprof.h", + "../../../../../src/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/NVIDIA/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/NVIDIA/premake4.lua new file mode 100644 index 000000000..095e08f39 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/NVIDIA/premake4.lua @@ -0,0 +1,57 @@ + + hasCL = findOpenCL_NVIDIA() + + if (hasCL) then + + project "OpenCL_gpu_rigidbody_pipeline_NVIDIA" + + initOpenCL_NVIDIA() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + initOpenGL() + initGlut() + initGlew() + + includedirs { + "../../../rendering/BulletMath", + "../../primitives", + "../../../../../src" + } + + files { + "../main.cpp", + "../btConvexUtility.cpp", + "../btConvexUtility.h", + "../btGpuNarrowPhaseAndSolver.cpp", + "../btGpuNarrowPhaseAndSolver.h", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.cpp", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.h", + "../../../../../src/LinearMath/btConvexHullComputer.cpp", + "../../../../../src/LinearMath/btConvexHullComputer.h", + "../../broadphase_benchmark/findPairsOpenCL.cpp", + "../../broadphase_benchmark/findPairsOpenCL.h", + "../../broadphase_benchmark/btGridBroadphaseCL.cpp", + "../../broadphase_benchmark/btGridBroadphaseCL.h", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h", + "../../../../../src/LinearMath/btAlignedAllocator.cpp", + "../../../../../src/LinearMath/btQuickprof.cpp", + "../../../../../src/LinearMath/btQuickprof.h", + "../../../../../src/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btConvexUtility.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btConvexUtility.cpp new file mode 100644 index 000000000..7f24449f7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btConvexUtility.cpp @@ -0,0 +1,240 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + + +#include "btConvexUtility.h" +#include "LinearMath/btConvexHullComputer.h" +#include "LinearMath/btGrahamScan2dConvexHull.h" +#include "LinearMath/btQuaternion.h" + +bool btConvexUtility::initializePolyhedralFeatures(const btAlignedObjectArray& orgVertices, bool mergeCoplanarTriangles) +{ + + + btConvexHullComputer conv; + conv.compute(&orgVertices[0].getX(), sizeof(btVector3),orgVertices.size(),0.f,0.f); + + btAlignedObjectArray faceNormals; + int numFaces = conv.faces.size(); + faceNormals.resize(numFaces); + btConvexHullComputer* convexUtil = &conv; + + + btAlignedObjectArray tmpFaces; + tmpFaces.resize(numFaces); + + int numVertices = convexUtil->vertices.size(); + m_vertices.resize(numVertices); + for (int p=0;pvertices[p]; + } + + + for (int i=0;ifaces[i]; + //printf("face=%d\n",face); + const btConvexHullComputer::Edge* firstEdge = &convexUtil->edges[face]; + const btConvexHullComputer::Edge* edge = firstEdge; + + btVector3 edges[3]; + int numEdges = 0; + //compute face normals + + btScalar maxCross2 = 0.f; + int chosenEdge = -1; + + do + { + + int src = edge->getSourceVertex(); + tmpFaces[i].m_indices.push_back(src); + int targ = edge->getTargetVertex(); + btVector3 wa = convexUtil->vertices[src]; + + btVector3 wb = convexUtil->vertices[targ]; + btVector3 newEdge = wb-wa; + newEdge.normalize(); + if (numEdges<2) + edges[numEdges++] = newEdge; + + edge = edge->getNextEdgeOfFace(); + } while (edge!=firstEdge); + + btScalar planeEq = 1e30f; + + + if (numEdges==2) + { + faceNormals[i] = edges[0].cross(edges[1]); + faceNormals[i].normalize(); + tmpFaces[i].m_plane[0] = faceNormals[i].getX(); + tmpFaces[i].m_plane[1] = faceNormals[i].getY(); + tmpFaces[i].m_plane[2] = faceNormals[i].getZ(); + tmpFaces[i].m_plane[3] = planeEq; + + } + else + { + btAssert(0);//degenerate? + faceNormals[i].setZero(); + } + + for (int v=0;veq) + { + planeEq=eq; + } + } + tmpFaces[i].m_plane[3] = -planeEq; + } + + //merge coplanar faces + + btScalar faceWeldThreshold= 0.999f; + btAlignedObjectArray todoFaces; + for (int i=0;i coplanarFaceGroup; + int refFace = todoFaces[todoFaces.size()-1]; + + coplanarFaceGroup.push_back(refFace); + btFace& faceA = tmpFaces[refFace]; + todoFaces.pop_back(); + + btVector3 faceNormalA(faceA.m_plane[0],faceA.m_plane[1],faceA.m_plane[2]); + for (int j=todoFaces.size()-1;j>=0;j--) + { + int i = todoFaces[j]; + btFace& faceB = tmpFaces[i]; + btVector3 faceNormalB(faceB.m_plane[0],faceB.m_plane[1],faceB.m_plane[2]); + if (faceNormalA.dot(faceNormalB)>faceWeldThreshold) + { + coplanarFaceGroup.push_back(i); + todoFaces.remove(i); + } + } + + + bool did_merge = false; + if (mergeCoplanarTriangles && coplanarFaceGroup.size()>1) + { + //do the merge: use Graham Scan 2d convex hull + + btAlignedObjectArray orgpoints; + + for (int i=0;i hull; + GrahamScanConvexHull2D(orgpoints,hull); + + for (int i=0;i m_indices; +// btAlignedObjectArray m_connectedFaces; + btScalar m_plane[4]; +}; + +class btConvexUtility +{ + public: + + btAlignedObjectArray m_vertices; + btAlignedObjectArray m_faces; + + bool initializePolyhedralFeatures(const btAlignedObjectArray& orgVertices, bool mergeCoplanarTriangles); + +}; +#endif + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.cpp new file mode 100644 index 000000000..6f377afd5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.cpp @@ -0,0 +1,730 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "btGpuNarrowphaseAndSolver.h" + +//#include "CustomConvexShape.h" +//#include "CustomConvexPairCollision.h" +#include "LinearMath/btQuickprof.h" + + +//#include "BulletDynamics/Dynamics/btRigidBody.h" + +#include "Adl/Adl.h" +#include "../../dynamics/basic_demo/Stubs/AdlMath.h" +#include "../../dynamics/basic_demo/Stubs/AdlContact4.h" +#include "../../dynamics/basic_demo/Stubs/AdlQuaternion.h" +#include "../../dynamics/basic_demo/Stubs/ChNarrowPhase.h" +#include "../../dynamics/basic_demo/Stubs/Solver.h" +#include + +int gpuBatchContacts = 1; + +int numPairsOut =0; +struct CPUSolveData +{ + u32 m_n[adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT]; + u32 m_offset[adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT]; +}; + + +struct ParallelSolveData +{ + adl::Buffer* m_numConstraints; + adl::Buffer* m_offsets; +}; + +struct CustomDispatchData +{ + adl::DeviceCL* m_deviceCL; + adl::Device* m_deviceHost; + ShapeDataType m_ShapeBuffer; + adl::HostBuffer* m_shapePointers; + + adl::HostBuffer* m_pBufPairsCPU; + + adl::Buffer* m_convexPairsOutGPU; + adl::Buffer* m_planePairs; + + adl::Buffer* m_pBufContactOutGPU; + adl::HostBuffer* m_pBufContactOutCPU; + adl::ChNarrowphase::Data* m_Data; + + + + adl::HostBuffer* m_bodyBufferCPU; + adl::Buffer* m_bodyBufferGPU; + + adl::Buffer* m_inertiaBufferCPU; + adl::Buffer* m_inertiaBufferGPU; + + adl::Solver::Data* m_solverDataGPU; + SolverData m_contactCGPU; + void* m_frictionCGPU; + + int m_numAcceleratedShapes; + int m_numAcceleratedRigidBodies; +}; + + +btGpuNarrowphaseAndSolver::btGpuNarrowphaseAndSolver(adl::DeviceCL* deviceCL) + :m_internalData(0) ,m_planeBodyIndex(-1) +{ + + if (deviceCL) + { + m_internalData = new CustomDispatchData(); + memset(m_internalData,0,sizeof(CustomDispatchData)); + + adl::DeviceUtils::Config cfg; + m_internalData->m_deviceCL = deviceCL; + + + m_internalData->m_deviceHost = adl::DeviceUtils::allocate( adl::TYPE_HOST, cfg ); + m_internalData->m_pBufPairsCPU = new adl::HostBuffer(m_internalData->m_deviceHost, MAX_BROADPHASE_COLLISION_CL); + + m_internalData->m_convexPairsOutGPU = new adl::Buffer(m_internalData->m_deviceCL,MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_planePairs = new adl::Buffer(m_internalData->m_deviceCL,MAX_BROADPHASE_COLLISION_CL); + + m_internalData->m_pBufContactOutCPU = new adl::HostBuffer(m_internalData->m_deviceHost, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_bodyBufferCPU = new adl::HostBuffer(m_internalData->m_deviceHost, MAX_CONVEX_BODIES_CL); + + m_internalData->m_inertiaBufferCPU = new adl::Buffer(m_internalData->m_deviceHost,MAX_CONVEX_BODIES_CL); + m_internalData->m_pBufContactOutGPU = new adl::Buffer(m_internalData->m_deviceCL, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_inertiaBufferGPU = new adl::Buffer(m_internalData->m_deviceCL,MAX_CONVEX_BODIES_CL); + + m_internalData->m_solverDataGPU = adl::Solver::allocate( m_internalData->m_deviceCL, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_bodyBufferGPU = new adl::Buffer(m_internalData->m_deviceCL, MAX_CONVEX_BODIES_CL); + m_internalData->m_Data = adl::ChNarrowphase::allocate(m_internalData->m_deviceCL); +// m_internalData->m_DataCPU = adl::ChNarrowphase::allocate(m_internalData->m_deviceHost); + + + m_internalData->m_ShapeBuffer = adl::ChNarrowphase::allocateShapeBuffer(m_internalData->m_deviceCL, MAX_CONVEX_SHAPES_CL); + + m_internalData->m_shapePointers = new adl::HostBuffer(m_internalData->m_deviceHost,MAX_CONVEX_SHAPES_CL); + + m_internalData->m_numAcceleratedShapes = 0; + m_internalData->m_numAcceleratedRigidBodies = 0; + + m_internalData->m_contactCGPU = adl::Solver::allocateConstraint4( m_internalData->m_deviceCL, MAX_BROADPHASE_COLLISION_CL); + m_internalData->m_frictionCGPU = adl::Solver::allocateFrictionConstraint( m_internalData->m_deviceCL, MAX_BROADPHASE_COLLISION_CL); + + } +} + +int btGpuNarrowphaseAndSolver::registerShape(ConvexHeightField* convexShape) +{ + (*m_internalData->m_shapePointers)[m_internalData->m_numAcceleratedShapes] = convexShape; + adl::ChNarrowphase::setShape(m_internalData->m_ShapeBuffer, convexShape, m_internalData->m_numAcceleratedShapes, 0.01f); + return m_internalData->m_numAcceleratedShapes++; +} + +cl_mem btGpuNarrowphaseAndSolver::getBodiesGpu() +{ + return (cl_mem)m_internalData->m_bodyBufferGPU->m_ptr; +} + +cl_mem btGpuNarrowphaseAndSolver::getBodyInertiasGpu() +{ + return (cl_mem)m_internalData->m_inertiaBufferGPU->m_ptr; +} + + +int btGpuNarrowphaseAndSolver::registerRigidBody(int shapeIndex, float mass, const float* position, const float* orientation , bool writeToGpu) +{ + assert(m_internalData->m_numAcceleratedRigidBodies< (MAX_CONVEX_BODIES_CL-1)); + + RigidBodyBase::Body& body = m_internalData->m_bodyBufferCPU->m_ptr[m_internalData->m_numAcceleratedRigidBodies]; + + float friction = 1.f; + float restitution = 0.f; + + body.m_frictionCoeff = friction; + body.m_restituitionCoeff = restitution; + body.m_angVel = make_float4(0.f); + body.m_linVel = make_float4(0.f); + body.m_pos = make_float4(position[0],position[1],position[2],0.f); + body.m_quat = make_float4(orientation[0],orientation[1],orientation[2],orientation[3]); + body.m_shapeIdx = shapeIndex; + if (shapeIndex<0) + { + body.m_shapeType = CollisionShape::SHAPE_PLANE; + m_planeBodyIndex = m_internalData->m_numAcceleratedRigidBodies; + } else + { + body.m_shapeType = CollisionShape::SHAPE_CONVEX_HEIGHT_FIELD; + } + + body.m_invMass = mass? 1.f/mass : 0.f; + + if (writeToGpu) + m_internalData->m_bodyBufferGPU->write(&body,1,m_internalData->m_numAcceleratedRigidBodies); + + RigidBodyBase::Inertia& shapeInfo = m_internalData->m_inertiaBufferCPU->m_ptr[m_internalData->m_numAcceleratedRigidBodies]; + + if (mass==0.f) + { + shapeInfo.m_initInvInertia = mtZero(); + shapeInfo.m_invInertia = mtZero(); + } else + { + + assert(body.m_shapeIdx>=0); + + //approximate using the aabb of the shape + + Aabb aabb = (*m_internalData->m_shapePointers)[shapeIndex]->m_aabb; + float4 halfExtents = (aabb.m_max - aabb.m_min); + + float4 localInertia; + + float lx=2.f*halfExtents.x; + float ly=2.f*halfExtents.y; + float lz=2.f*halfExtents.z; + + localInertia = make_float4( (mass/12.0f) * (ly*ly + lz*lz), + (mass/12.0f) * (lx*lx + lz*lz), + (mass/12.0f) * (lx*lx + ly*ly)); + + float4 invLocalInertia; + invLocalInertia.x = 1.f/localInertia.x; + invLocalInertia.y = 1.f/localInertia.y; + invLocalInertia.z = 1.f/localInertia.z; + invLocalInertia.w = 0.f; + + shapeInfo.m_initInvInertia = mtZero(); + shapeInfo.m_initInvInertia.m_row[0].x = invLocalInertia.x; + shapeInfo.m_initInvInertia.m_row[1].y = invLocalInertia.y; + shapeInfo.m_initInvInertia.m_row[2].z = invLocalInertia.z; + + Matrix3x3 m = qtGetRotationMatrix( body.m_quat); + Matrix3x3 mT = mtTranspose( m ); + shapeInfo.m_invInertia = mtMul( mtMul( m, shapeInfo.m_initInvInertia ), mT ); + + } + + if (writeToGpu) + m_internalData->m_inertiaBufferGPU->write(&shapeInfo,1,m_internalData->m_numAcceleratedRigidBodies); + return m_internalData->m_numAcceleratedRigidBodies++; +} + +void btGpuNarrowphaseAndSolver::writeAllBodiesToGpu() +{ + m_internalData->m_bodyBufferGPU->write(m_internalData->m_bodyBufferCPU->m_ptr,m_internalData->m_numAcceleratedRigidBodies); + m_internalData->m_inertiaBufferGPU->write( m_internalData->m_inertiaBufferCPU->m_ptr,m_internalData->m_numAcceleratedRigidBodies); +} + + + +btGpuNarrowphaseAndSolver::~btGpuNarrowphaseAndSolver(void) +{ + if (m_internalData) + { + delete m_internalData->m_pBufPairsCPU; + delete m_internalData->m_convexPairsOutGPU; + delete m_internalData->m_planePairs; + delete m_internalData->m_pBufContactOutGPU; + delete m_internalData->m_inertiaBufferGPU; + delete m_internalData->m_pBufContactOutCPU; + delete m_internalData->m_shapePointers; + adl::ChNarrowphase::deallocateShapeBuffer(m_internalData->m_ShapeBuffer); + delete m_internalData->m_inertiaBufferCPU; + adl::Solver::deallocateConstraint4( m_internalData->m_contactCGPU ); + adl::Solver::deallocateFrictionConstraint( m_internalData->m_frictionCGPU ); + + delete m_internalData->m_bodyBufferGPU; + adl::Solver::deallocate( m_internalData->m_solverDataGPU); + delete m_internalData->m_bodyBufferCPU; + adl::ChNarrowphase::deallocate(m_internalData->m_Data); + + + + adl::DeviceUtils::deallocate(m_internalData->m_deviceHost); + + delete m_internalData; + } + +} + + + + + +void btGpuNarrowphaseAndSolver::computeContactsAndSolver(cl_mem broadphasePairs, int numBroadphasePairs) +{ + + BT_PROFILE("computeContactsAndSolver"); + bool bGPU = (m_internalData != 0); + int maxBodyIndex = m_internalData->m_numAcceleratedRigidBodies; + + if (!maxBodyIndex) + return; + int numOfConvexRBodies = maxBodyIndex; + + adl::ChNarrowphaseBase::Config cfgNP; + cfgNP.m_collisionMargin = 0.01f; + int nContactOut = 0; + //printf("convexPairsOut.m_size = %d\n",m_internalData->m_convexPairsOutGPU->m_size); + + + adl::Buffer broadphasePairsGPU; + broadphasePairsGPU.m_ptr = (int2*)broadphasePairs; + broadphasePairsGPU.m_size = numBroadphasePairs; + broadphasePairsGPU.m_device = m_internalData->m_deviceCL; + + + bool useCulling = true; + if (useCulling) + { + BT_PROFILE("ChNarrowphase::culling"); + adl::DeviceUtils::waitForCompletion(m_internalData->m_deviceCL); + + numPairsOut = adl::ChNarrowphase::culling( + m_internalData->m_Data, + &broadphasePairsGPU, + numBroadphasePairs, + m_internalData->m_bodyBufferGPU, m_internalData->m_ShapeBuffer, + m_internalData->m_convexPairsOutGPU, + cfgNP); + } + + { + BT_PROFILE("ChNarrowphase::execute"); + if (useCulling) + { + + if (m_planeBodyIndex>=0) + { + BT_PROFILE("ChNarrowphase:: plane versus convex"); + //todo: get rid of this dynamic allocation + int2* hostPairs = new int2[m_internalData->m_numAcceleratedRigidBodies-1]; + int index=0; + for (int i=0;im_numAcceleratedRigidBodies;i++) + { + if (i!=m_planeBodyIndex) + { + hostPairs[index].x = m_planeBodyIndex; + hostPairs[index].y = i; + index++; + } + } + assert(m_internalData->m_numAcceleratedRigidBodies-1 == index); + m_internalData->m_planePairs->write(hostPairs,index); + adl::DeviceUtils::waitForCompletion(m_internalData->m_deviceCL); + delete[]hostPairs; + //convex versus plane + adl::ChNarrowphase::execute(m_internalData->m_Data, m_internalData->m_planePairs, index, m_internalData->m_bodyBufferGPU, m_internalData->m_ShapeBuffer, + 0,0,m_internalData->m_pBufContactOutGPU, nContactOut, cfgNP); + } + + //convex versus convex + adl::ChNarrowphase::execute(m_internalData->m_Data, m_internalData->m_convexPairsOutGPU,numPairsOut, m_internalData->m_bodyBufferGPU, m_internalData->m_ShapeBuffer, m_internalData->m_pBufContactOutGPU, nContactOut, cfgNP); + } else + { + adl::ChNarrowphase::execute(m_internalData->m_Data, &broadphasePairsGPU, numBroadphasePairs, m_internalData->m_bodyBufferGPU, m_internalData->m_ShapeBuffer, m_internalData->m_pBufContactOutGPU, nContactOut, cfgNP); + } + + adl::DeviceUtils::waitForCompletion(m_internalData->m_deviceCL); + } + + if (!nContactOut) + return; + + + bool useSolver = true;//true;//false; + + if (useSolver) + { + float dt=1./60.; + adl::SolverBase::ConstraintCfg csCfg( dt ); + csCfg.m_enableParallelSolve = true; + csCfg.m_averageExtent = 0.2f;//@TODO m_averageObjExtent; + csCfg.m_staticIdx = m_planeBodyIndex; + + + bool exposeInternalBatchImplementation=true; + + adl::Solver::Data* cpuSolverData = 0; + if (exposeInternalBatchImplementation) + { + BT_PROFILE("Batching"); + + cpuSolverData = adl::Solver::allocate( m_internalData->m_deviceHost, nContactOut); + + adl::Buffer* contactsIn = m_internalData->m_pBufContactOutGPU; + const adl::Buffer* bodyBuf = m_internalData->m_bodyBufferGPU; + void* additionalData = m_internalData->m_frictionCGPU; + const adl::Buffer* shapeBuf = m_internalData->m_inertiaBufferGPU; + SolverData contactCOut = m_internalData->m_contactCGPU; + int nContacts = nContactOut; + + bool useCPU=false; + + if (useCPU) + { + BT_PROFILE("CPU batch"); + { + BT_PROFILE("CPU sortContacts2"); + sortContacts2( cpuSolverData, bodyBuf, contactsIn, additionalData, nContacts, csCfg ); + } + + CPUSolveData* dataCPU = (CPUSolveData*)cpuSolverData->m_parallelSolveData; + { + BT_PROFILE("CPU batchContacts2"); + + adl::Buffer n; n.setRawPtr( cpuSolverData->m_device, dataCPU->m_n, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + adl::Buffer offsets; offsets.setRawPtr( cpuSolverData->m_device, dataCPU->m_offset, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + batchContacts2( cpuSolverData, contactsIn, nContacts, &n, &offsets, csCfg.m_staticIdx ); + } + + { + BT_PROFILE("CPU convertToConstraints2"); + convertToConstraints2( cpuSolverData, bodyBuf, shapeBuf, contactsIn, contactCOut, additionalData, nContacts, csCfg ); + } + + { + BT_PROFILE("CPU -> GPU copy"); + ParallelSolveData* dataGPU = (ParallelSolveData*)m_internalData->m_solverDataGPU->m_parallelSolveData; + dataGPU->m_numConstraints->write(dataCPU->m_n,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + dataGPU->m_offsets->write(dataCPU->m_offset,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL); + } + + } + else + { + BT_PROFILE("GPU batch"); + + adl::Solver::Data* data = m_internalData->m_solverDataGPU; + + { + if( data->m_contactBuffer ) + { + if( data->m_contactBuffer->getSize() < nContacts ) + { + BT_PROFILE("delete data->m_contactBuffer;"); + delete data->m_contactBuffer; + data->m_contactBuffer = 0; + } + } + if( data->m_contactBuffer == 0 ) + { + data->m_contactBuffer = new adl::Buffer( data->m_device, nContacts ); + } + + adl::Buffer* contactNative = contactsIn; + + ParallelSolveData* nativeSolveData = (ParallelSolveData*)data->m_parallelSolveData; + + { + + ADLASSERT( data->m_device->m_type == adl::TYPE_CL ); + adl::Buffer* bodyNative = adl::BufferUtils::map( data->m_device, bodyBuf ); + adl::Buffer* contactNative = adl::BufferUtils::map( data->m_device, contactsIn ); + + const int sortAlignment = 512; // todo. get this out of sort + if( csCfg.m_enableParallelSolve ) + { + ParallelSolveData* nativeSolveData = (ParallelSolveData*)data->m_parallelSolveData; + + int sortSize = NEXTMULTIPLEOF( nContacts, sortAlignment ); + + adl::Buffer* countsNative = nativeSolveData->m_numConstraints;//BufferUtils::map( data->m_device, &countsHost ); + adl::Buffer* offsetsNative = nativeSolveData->m_offsets;//BufferUtils::map( data->m_device, &offsetsHost ); + + { // 2. set cell idx + BT_PROFILE("GPU set cell idx"); + struct CB + { + int m_nContacts; + int m_staticIdx; + float m_scale; + int m_nSplit; + }; + + ADLASSERT( sortSize%64 == 0 ); + CB cdata; + cdata.m_nContacts = nContacts; + cdata.m_staticIdx = csCfg.m_staticIdx; + cdata.m_scale = 1.f/(adl::SolverBase::N_OBJ_PER_SPLIT*csCfg.m_averageExtent); + cdata.m_nSplit = adl::SolverBase::N_SPLIT; + + adl::Buffer constBuffer( data->m_device, 1, adl::BufferBase::BUFFER_CONST ); + adl::Launcher::BufferInfo bInfo[] = { adl::Launcher::BufferInfo( contactNative ), adl::Launcher::BufferInfo( bodyNative ), adl::Launcher::BufferInfo( data->m_sortDataBuffer ) }; + adl::Launcher launcher( data->m_device, data->m_setSortDataKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(adl::Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( sortSize, 64 ); + } + bool gpuRadixSort=true; + if (gpuRadixSort) + { // 3. sort by cell idx + BT_PROFILE("gpuRadixSort"); + int n = adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT; + int sortBit = 32; + //if( n <= 0xffff ) sortBit = 16; + //if( n <= 0xff ) sortBit = 8; + //adl::RadixSort::execute( data->m_sort, *data->m_sortDataBuffer, sortSize ); + adl::RadixSort32::execute( data->m_sort32, *data->m_sortDataBuffer, sortSize ); + + } else + { + BT_PROFILE("cpu RadixSort"); + adl::HostBuffer sortData(m_internalData->m_deviceHost,nContacts); + data->m_sortDataBuffer->read(sortData.m_ptr,nContacts); + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL); + + adl::RadixSort::Data* sData = adl::RadixSort::allocate( m_internalData->m_deviceHost, nContacts ); + adl::RadixSort::execute( sData, sortData, nContacts ); + adl::RadixSort::deallocate( sData ); + + data->m_sortDataBuffer->write(sortData.m_ptr,nContacts); + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL); + } + + + + bool gpuBoundSearch=true; + if (gpuBoundSearch) + { // 4. find entries + BT_PROFILE("gpuBoundSearch"); + adl::BoundSearch::execute( data->m_search, *data->m_sortDataBuffer, nContacts, *countsNative, + adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT, adl::BoundSearchBase::COUNT ); + + adl::PrefixScan::execute( data->m_scan, *countsNative, *offsetsNative, + adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + } else + { + BT_PROFILE("cpuBoundSearch"); + adl::HostBuffer sortData(m_internalData->m_deviceHost,nContacts); + data->m_sortDataBuffer->read(sortData.m_ptr,nContacts); + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL); + + adl::HostBuffer n0( m_internalData->m_deviceHost, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + adl::HostBuffer offset0( m_internalData->m_deviceHost, adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT ); + for(int i=0; i=0); + assert(idxwrite(n0.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + offsetsNative->write(offset0.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::DeviceUtils::waitForCompletion( data->m_device ); + + } + { // 5. sort constraints by cellIdx + { + BT_PROFILE("gpu m_reorderContactKernel"); + adl::Buffer constBuffer( data->m_device, 1, adl::BufferBase::BUFFER_CONST ); + + int4 cdata; cdata.x = nContacts; + adl::Launcher::BufferInfo bInfo[] = { adl::Launcher::BufferInfo( contactNative ), adl::Launcher::BufferInfo( data->m_contactBuffer ), adl::Launcher::BufferInfo( data->m_sortDataBuffer ) }; + adl::Launcher launcher( data->m_device, data->m_reorderContactKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(adl::Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nContacts, 64 ); + } + } + + } + + adl::BufferUtils::unmap( bodyNative, bodyBuf ); + adl::BufferUtils::unmap( contactNative, contactsIn ); + + } + + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL); + + { + BT_PROFILE("gpu m_copyConstraintKernel"); + adl::Buffer constBuffer( data->m_device, 1, adl::BufferBase::BUFFER_CONST ); + int4 cdata; cdata.x = nContacts; + adl::Launcher::BufferInfo bInfo[] = { adl::Launcher::BufferInfo( data->m_contactBuffer ), adl::Launcher::BufferInfo( contactNative ) }; + adl::Launcher launcher( data->m_device, data->m_copyConstraintKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(adl::Launcher::BufferInfo) ); + launcher.setConst( constBuffer, cdata ); + launcher.launch1D( nContacts, 64 ); + adl::DeviceUtils::waitForCompletion( data->m_device ); + } + + bool compareGPU = false; + if (gpuBatchContacts) + { + BT_PROFILE("gpu batchContacts"); + adl::Solver::batchContacts( data, contactNative, nContacts, nativeSolveData->m_numConstraints, nativeSolveData->m_offsets, csCfg.m_staticIdx ); + } + else + { + BT_PROFILE("cpu batchContacts2"); + cpuSolverData->m_parallelSolveData = 0;// + ParallelSolveData* dataGPU = (ParallelSolveData*)m_internalData->m_solverDataGPU->m_parallelSolveData; + adl::Buffer numConstraints(cpuSolverData->m_device,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::Buffer offsets(cpuSolverData->m_device,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + + { + BT_PROFILE("gpu->cpu read m_numConstraints"); + dataGPU->m_numConstraints->read(numConstraints.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + dataGPU->m_offsets->read(offsets.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::DeviceUtils::waitForCompletion( data->m_device ); + } + + adl::Buffer gpunumConstraints(cpuSolverData->m_device,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::Buffer gpuoffsets(cpuSolverData->m_device,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + + if (compareGPU) + { + adl::Buffer contactNativeCopy (data->m_device,contactNative->getSize()); + contactNativeCopy.write(*contactNative,contactNative->getSize()); + adl::DeviceUtils::waitForCompletion( data->m_device ); + + adl::Buffer tmpNumGPU(data->m_device,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::Buffer tmpOffsetGPU(data->m_device,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + tmpNumGPU.write(numConstraints.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + tmpOffsetGPU.write(offsets.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::DeviceUtils::waitForCompletion( data->m_device ); + + BT_PROFILE("gpu batchContacts"); + //adl::Solver::batchContacts( data, contactNative, nContacts, nativeSolveData->m_numConstraints, nativeSolveData->m_offsets, csCfg.m_staticIdx ); + adl::Solver::batchContacts( data, &contactNativeCopy, nContacts, &tmpNumGPU, &tmpOffsetGPU, csCfg.m_staticIdx ); + + + adl::DeviceUtils::waitForCompletion( data->m_device ); + + //compare now + tmpNumGPU.read(gpunumConstraints,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + tmpOffsetGPU.read(gpuoffsets,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::DeviceUtils::waitForCompletion( data->m_device ); + + } + + CPUSolveData* dataCPU = (CPUSolveData*)cpuSolverData->m_parallelSolveData; + + { + BT_PROFILE("cpu batchContacts2"); + batchContacts2( cpuSolverData, contactNative, nContacts, &numConstraints, &offsets, csCfg.m_staticIdx ); + } + + + if (compareGPU) + { + adl::DeviceUtils::waitForCompletion( data->m_device ); + dataGPU->m_numConstraints->write(numConstraints.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + dataGPU->m_offsets->write(offsets.m_ptr,adl::SolverBase::N_SPLIT*adl::SolverBase::N_SPLIT); + adl::DeviceUtils::waitForCompletion( data->m_device ); + + + for (int i=0;i::convertToConstraints( data, bodyBuf, shapeBuf, contactNative, contactCOut, additionalData, nContacts, csCfg ); + adl::DeviceUtils::waitForCompletion( data->m_device ); + } + if (compareGPU) + { + adl::Buffer contactNativeCPU(cpuSolverData->m_device,contactNative->getSize()); + contactNative->read(contactNativeCPU,nContacts); + adl::DeviceUtils::waitForCompletion( data->m_device ); + for (int i=0;i::reorderConvertToConstraints( + m_internalData->m_solverDataGPU, + m_internalData->m_bodyBufferGPU, + m_internalData->m_inertiaBufferGPU, + m_internalData->m_pBufContactOutGPU, + m_internalData->m_contactCGPU, + m_internalData->m_frictionCGPU, + nContactOut, + csCfg ); + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL ); + } + + + if (1) + { + BT_PROFILE("GPU solveContactConstraint"); + m_internalData->m_solverDataGPU->m_nIterations = 5; + + adl::Solver::solveContactConstraint( m_internalData->m_solverDataGPU, + m_internalData->m_bodyBufferGPU, + m_internalData->m_inertiaBufferGPU, + m_internalData->m_contactCGPU, + 0, + nContactOut ); + + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL ); + } + + if (cpuSolverData) + adl::Solver::deallocate( cpuSolverData ); + + if (0) + { + BT_PROFILE("read body velocities back to CPU"); + //read body updated linear/angular velocities back to CPU + m_internalData->m_bodyBufferGPU->read( + m_internalData->m_bodyBufferCPU->m_ptr,numOfConvexRBodies); + adl::DeviceUtils::waitForCompletion( m_internalData->m_deviceCL ); + } + } + +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h new file mode 100644 index 000000000..9d5941334 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h @@ -0,0 +1,72 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef GPU_NARROWPHASE_SOLVER_H +#define GPU_NARROWPHASE_SOLVER_H + + + +//#define MAX_CONVEX_BODIES_CL 8*1024 +#define MAX_CONVEX_BODIES_CL 128*1024 +#define MAX_PAIRS_PER_BODY_CL 16 +#define MAX_CONVEX_SHAPES_CL 8192 +#define MAX_BROADPHASE_COLLISION_CL (MAX_CONVEX_BODIES_CL*MAX_PAIRS_PER_BODY_CL) + +/* +#define MAX_CONVEX_BODIES_CL 1024 +#define MAX_PAIRS_PER_BODY_CL 32 +#define MAX_CONVEX_SHAPES_CL 8192 +#define MAX_BROADPHASE_COLLISION_CL (MAX_CONVEX_BODIES_CL*MAX_PAIRS_PER_BODY_CL) +*/ + +namespace adl +{ + struct DeviceCL; +}; + + +struct CustomDispatchData; + +#include "../basic_initialize/btOpenCLInclude.h" + + +class btGpuNarrowphaseAndSolver +{ +protected: + + CustomDispatchData* m_internalData; + int m_acceleratedCompanionShapeIndex; + int m_planeBodyIndex; + +public: + btGpuNarrowphaseAndSolver(adl::DeviceCL* deviceCL); + + virtual ~btGpuNarrowphaseAndSolver(void); + + int registerShape(class ConvexHeightField* convexShape); + int registerRigidBody(int shapeIndex, float mass, const float* position, const float* orientation, bool writeToGpu = true); + void writeAllBodiesToGpu(); + + //btBroadphasePair* GetPair(btBroadphasePairArray& pairArray, int idxBodyA, int idxBodyB); + + virtual void computeContactsAndSolver(cl_mem broadphasePairs, int numBroadphasePairs); + + cl_mem getBodiesGpu(); + + cl_mem getBodyInertiasGpu(); + +}; + +#endif //GPU_NARROWPHASE_SOLVER_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/main.cpp new file mode 100644 index 000000000..07dfc82b2 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/main.cpp @@ -0,0 +1,1775 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + + +int NUM_OBJECTS_X = 54; +int NUM_OBJECTS_Y = 35; +int NUM_OBJECTS_Z = 54; + +float X_GAP = 12.f; +float Y_GAP = 2.f; +float Z_GAP = 2.f; + +int preferredGPU = -1; +int preferredPlatform=-1; +int USE_GL_CL_INTEROP=1; +extern int gpuBatchContacts; + + +#include +#include + +#include "btGlutInclude.h" +#include "../opengl_interop/btStopwatch.h" +#include "../../dynamics/basic_demo/ConvexHeightFieldShape.h" +#include "../../dynamics/basic_demo/Stubs/AdlRigidBody.h" + +#include "btGpuNarrowphaseAndSolver.h" + +#include "LinearMath/btQuickprof.h" + +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuaternion.h" +#include "LinearMath/btMatrix3x3.h" +static float sAngle(0); + +#include + +#ifdef _WIN32 +#include +#endif + +#include +#include +#include "../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h" +#include "../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h" +#include "../broadphase_benchmark/btGridBroadphaseCl.h" +#include "btConvexUtility.h" + +#define USE_NEW +#ifdef USE_NEW +btGridBroadphaseCl* sBroadphase=0; +#else +btGpu3DGridBroadphase* sBroadphase=0; +#endif + +btAlignedObjectArray proxyArray; + +int gShapeIndex=0; + + +#define RS_SCALE (1.0 / (1.0 + RAND_MAX)) + + +int randbiased (double x) { + for (;;) { + double p = rand () * RS_SCALE; + if (p >= x) return 0; + if (p+RS_SCALE <= x) return 1; + /* p < x < p+RS_SCALE */ + x = (x - p) * (1.0 + RAND_MAX); + } +} + +size_t randrange (size_t n) +{ + double xhi; + double resolution = n * RS_SCALE; + double x = resolution * rand (); /* x in [0,n) */ + size_t lo = (size_t) floor (x); + + xhi = x + resolution; + + for (;;) { + lo++; + if (lo >= xhi || randbiased ((lo - x) / (xhi - x))) return lo-1; + x = lo; + } +} + +//OpenCL stuff +#include "../basic_initialize/btOpenCLUtils.h" +#include "../opengl_interop/btOpenCLGLInteropBuffer.h" + +#include "../broadphase_benchmark/findPairsOpenCL.h" + +btFindPairsIO gFpIO; + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; +cl_device_id g_device; + +cl_mem gLinVelMem; +cl_mem gAngVelMem; +cl_mem gBodyTimes; + +btVector3 m_cameraPosition(142,20,142); +btVector3 m_cameraTargetPosition(0,10,0); +btScalar m_cameraDistance = 55; +btVector3 m_cameraUp(0,1,0); +float m_azi=30.f; +float m_ele=5.f; + + + + +btOpenCLGLInteropBuffer* g_interopBuffer = 0; +cl_mem clBuffer=0; +char* hostPtr=0; +cl_bool blocking= CL_TRUE; + + +cl_kernel g_integrateTransformsKernel; + + + +////for Adl +#include + +adl::DeviceCL* g_deviceCL=0; + + + +bool useCPU = false; +bool printStats = true; +bool runOpenCLKernels = true; + +#define MSTRINGIFY(A) #A +static char* interopKernelString = +#include "../broadphase_benchmark/integrateKernel.cl" + +#define INTEROPKERNEL_SRC_PATH "../../opencl/broadphase_benchmark/integrateKernel.cl" + + +ConvexHeightField* s_convexHeightField = 0 ; +btGpuNarrowphaseAndSolver* narrowphaseAndSolver =0; + + +btStopwatch gStopwatch; +int m_glutScreenWidth = 640; +int m_glutScreenHeight= 480; + +bool m_ortho = false; + +static GLuint instancingShader; // The instancing renderer +static GLuint cube_vao; +static GLuint cube_vbo; +static GLuint index_vbo; +static GLuint m_texturehandle; + +static bool done = false; +static GLint angle_loc = 0; +static GLint ModelViewMatrix; +static GLint ProjectionMatrix; + +void writeTransforms(); + +static GLint uniform_texture_diffuse = 0; + +//used for dynamic loading from disk (default switched off) +#define MAX_SHADER_LENGTH 8192 +static GLubyte shaderText[MAX_SHADER_LENGTH]; + +static const char* vertexShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"\n" +"\n" +"layout (location = 0) in vec4 position;\n" +"layout (location = 1) in vec4 instance_position;\n" +"layout (location = 2) in vec4 instance_quaternion;\n" +"layout (location = 3) in vec2 uvcoords;\n" +"layout (location = 4) in vec3 vertexnormal;\n" +"layout (location = 5) in vec4 instance_color;\n" +"layout (location = 6) in vec3 instance_scale;\n" +"\n" +"\n" +"uniform float angle = 0.0;\n" +"uniform mat4 ModelViewMatrix;\n" +"uniform mat4 ProjectionMatrix;\n" +"\n" +"out Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"out Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"\n" +"vec4 quatMul ( in vec4 q1, in vec4 q2 )\n" +"{\n" +" vec3 im = q1.w * q2.xyz + q1.xyz * q2.w + cross ( q1.xyz, q2.xyz );\n" +" vec4 dt = q1 * q2;\n" +" float re = dot ( dt, vec4 ( -1.0, -1.0, -1.0, 1.0 ) );\n" +" return vec4 ( im, re );\n" +"}\n" +"\n" +"vec4 quatFromAxisAngle(vec4 axis, in float angle)\n" +"{\n" +" float cah = cos(angle*0.5);\n" +" float sah = sin(angle*0.5);\n" +" float d = inversesqrt(dot(axis,axis));\n" +" vec4 q = vec4(axis.x*sah*d,axis.y*sah*d,axis.z*sah*d,cah);\n" +" return q;\n" +"}\n" +"//\n" +"// vector rotation via quaternion\n" +"//\n" +"vec4 quatRotate3 ( in vec3 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, vec4 ( p, 0.0 ) );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"vec4 quatRotate ( in vec4 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, p );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"\n" +"out vec3 lightDir,normal,ambient;\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 q = instance_quaternion;\n" +" ambient = vec3(0.3,.3,0.3);\n" +" \n" +" \n" +" vec4 local_normal = (quatRotate3( vertexnormal,q));\n" +" vec3 light_pos = vec3(-0.8,1,-0.6);\n" +" normal = local_normal.xyz;\n"//normalize(ModelViewMatrix * local_normal).xyz;\n" +"\n" +" lightDir = normalize(light_pos);//gl_LightSource[0].position.xyz));\n" +"// lightDir = normalize(vec3(gl_LightSource[0].position));\n" +" \n" +" vec4 axis = vec4(1,1,1,0);\n" +" vec4 localcoord = quatRotate3( position.xyz*instance_scale,q);\n" +" vec4 vertexPos = ProjectionMatrix * ModelViewMatrix *(instance_position+localcoord);\n" +"\n" +" gl_Position = vertexPos;\n" +" \n" +" fragment.color = instance_color;\n" +" vert.texcoord = uvcoords;\n" +"}\n" +; + + +static const char* fragmentShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"in Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"in Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"uniform sampler2D Diffuse;\n" +"\n" +"in vec3 lightDir,normal,ambient;\n" +"\n" +"out vec4 color;\n" +"\n" +"void main_textured(void)\n" +"{\n" +" color = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 texel = fragment.color*texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +" vec3 ct,cf;\n" +" float intensity,at,af;\n" +" intensity = max(dot(lightDir,normalize(normal)),.2);\n" +" cf = intensity*vec3(1.0,1.0,1.0)+ambient;" +" af = 1.0;\n" +" \n" +" ct = texel.rgb;\n" +" at = texel.a;\n" +" \n" +" color = vec4(ct * cf, at * af); \n" +"}\n" +; + + +// Load the shader from the source text +void gltLoadShaderSrc(const char *szShaderSrc, GLuint shader) +{ + GLchar *fsStringPtr[1]; + + fsStringPtr[0] = (GLchar *)szShaderSrc; + glShaderSource(shader, 1, (const GLchar **)fsStringPtr, NULL); +} + + +//////////////////////////////////////////////////////////////// +// Load the shader from the specified file. Returns false if the +// shader could not be loaded +bool gltLoadShaderFile(const char *szFile, GLuint shader) +{ + GLint shaderLength = 0; + FILE *fp; + + // Open the shader file + fp = fopen(szFile, "r"); + if(fp != NULL) + { + // See how long the file is + while (fgetc(fp) != EOF) + shaderLength++; + + // Allocate a block of memory to send in the shader + assert(shaderLength < MAX_SHADER_LENGTH); // make me bigger! + if(shaderLength > MAX_SHADER_LENGTH) + { + fclose(fp); + return false; + } + + // Go back to beginning of file + rewind(fp); + + // Read the whole file in + if (shaderText != NULL) + fread(shaderText, 1, shaderLength, fp); + + // Make sure it is null terminated and close the file + shaderText[shaderLength] = '\0'; + fclose(fp); + } + else + return false; + + // printf(shaderText); + // Load the string + gltLoadShaderSrc((const char *)shaderText, shader); + + return true; +} + + +///////////////////////////////////////////////////////////////// +// Load a pair of shaders, compile, and link together. Specify the complete +// file path for each shader. Note, there is no support for +// just loading say a vertex program... you have to do both. +GLuint gltLoadShaderPair(const char *szVertexProg, const char *szFragmentProg, bool loadFromFile) +{ + // Temporary Shader objects + GLuint hVertexShader; + GLuint hFragmentShader; + GLuint hReturn = 0; + GLint testVal; + + // Create shader objects + hVertexShader = glCreateShader(GL_VERTEX_SHADER); + hFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (loadFromFile) + { + + if(gltLoadShaderFile(szVertexProg, hVertexShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + if(gltLoadShaderFile(szFragmentProg, hFragmentShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + } else + { + gltLoadShaderSrc(vertexShader, hVertexShader); + gltLoadShaderSrc(fragmentShader, hFragmentShader); + } + // Compile them + glCompileShader(hVertexShader); + glCompileShader(hFragmentShader); + + // Check for errors + glGetShaderiv(hVertexShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hVertexShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + glGetShaderiv(hFragmentShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hFragmentShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + // Link them - assuming it works... + hReturn = glCreateProgram(); + glAttachShader(hReturn, hVertexShader); + glAttachShader(hReturn, hFragmentShader); + + glLinkProgram(hReturn); + + // These are no longer needed + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + + // Make sure link worked too + glGetProgramiv(hReturn, GL_LINK_STATUS, &testVal); + if(testVal == GL_FALSE) + { + glDeleteProgram(hReturn); + return (GLuint)NULL; + } + + return hReturn; +} + +///position xyz, unused w, normal, uv +static const GLfloat cube_vertices[] = +{ + -1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 0,0,//0 + 1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 1,0,//1 + 1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 1,1,//2 + -1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 0,1 ,//3 + + -1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 0,0,//4 + 1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 1,0,//5 + 1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 1,1,//6 + -1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 0,1,//7 + + -1.0f, -1.0f, -1.0f, 1.0f, -1,0,0, 0,0, + -1.0f, 1.0f, -1.0f, 1.0f, -1,0,0, 1,0, + -1.0f, 1.0f, 1.0f, 1.0f, -1,0,0, 1,1, + -1.0f, -1.0f, 1.0f, 1.0f, -1,0,0, 0,1, + + 1.0f, -1.0f, -1.0f, 1.0f, 1,0,0, 0,0, + 1.0f, 1.0f, -1.0f, 1.0f, 1,0,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 1,0,0, 1,1, + 1.0f, -1.0f, 1.0f, 1.0f, 1,0,0, 0,1, + + -1.0f, -1.0f, -1.0f, 1.0f, 0,-1,0, 0,0, + -1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,0, + 1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,1, + 1.0f,-1.0f, -1.0f, 1.0f, 0,-1,0, 0,1, + + -1.0f, 1.0f, -1.0f, 1.0f, 0,1,0, 0,0, + -1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,1, + 1.0f,1.0f, -1.0f, 1.0f, 0,1,0, 0,1, +}; + +static const int cube_indices[]= +{ + 0,1,2,0,2,3,//ground face + 4,5,6,4,6,7,//top face + 8,9,10,8,10,11, + 12,13,14,12,14,15, + 16,17,18,16,18,19, + 20,21,22,20,22,23 +}; + +int m_mouseOldX = -1; +int m_mouseOldY = -1; +int m_mouseButtons = 0; + + +void mouseFunc(int button, int state, int x, int y) +{ + if (state == 0) + { + m_mouseButtons |= 1<0) + { + g_device= btOpenCLUtils::getDevice(g_cxMainContext,0); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(g_device,clInfo); + btOpenCLUtils::printDeviceInfo(g_device); + // create a command-queue + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, g_device, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + //normally you would create and execute kernels using this command queue + + } + + +} + +int NUM_OBJECTS = NUM_OBJECTS_X*NUM_OBJECTS_Y*NUM_OBJECTS_Z; +int POSITION_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*4); +int ORIENTATION_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*4); +int COLOR_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*4); +int SCALE_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*3); + +int VBOsize =0; + + +GLfloat* instance_positions_ptr = 0; +GLfloat* instance_quaternion_ptr = 0; +GLfloat* instance_colors_ptr = 0; +GLfloat* instance_scale_ptr= 0; + + +void DeleteShaders() +{ + glDeleteVertexArrays(1, &cube_vao); + glDeleteBuffers(1,&index_vbo); + glDeleteBuffers(1,&cube_vbo); + glDeleteProgram(instancingShader); +} + + +void InitShaders() +{ + + btOverlappingPairCache* overlappingPairCache=0; + int maxObjects = btMax(256,NUM_OBJECTS); +#ifdef USE_NEW + int maxPairsSmallProxy = 32; + + sBroadphase = new btGridBroadphaseCl(overlappingPairCache,btVector3(4.f, 4.f, 4.f), 128, 128, 128,maxObjects, maxObjects, maxPairsSmallProxy, 100.f, 128, + g_cxMainContext ,g_device,g_cqCommandQue, g_deviceCL); +#else + sBroadphase = new btGpu3DGridBroadphase(btVector3(2.f, 2.f, 2.f), 32, 32, 32,maxObjects, maxObjects, 64, 100.f, 64); +#endif + + + +// sBroadphase = new bt3dGridBroadphaseOCL(overlappingPairCache,btVector3(10.f, 10.f, 10.f), 32, 32, 32,NUM_OBJECTS, NUM_OBJECTS, 64, 100.f, 16, +// g_cxMainContext ,g_device,g_cqCommandQue); + + + + bool loadFromFile = false; + instancingShader = gltLoadShaderPair("instancing.vs","instancing.fs", loadFromFile); + + glLinkProgram(instancingShader); + glUseProgram(instancingShader); + angle_loc = glGetUniformLocation(instancingShader, "angle"); + ModelViewMatrix = glGetUniformLocation(instancingShader, "ModelViewMatrix"); + ProjectionMatrix = glGetUniformLocation(instancingShader, "ProjectionMatrix"); + uniform_texture_diffuse = glGetUniformLocation(instancingShader, "Diffuse"); + + GLuint offset = 0; + + + glGenBuffers(1, &cube_vbo); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + + instance_positions_ptr = (GLfloat*)new float[NUM_OBJECTS*4]; + instance_quaternion_ptr = (GLfloat*)new float[NUM_OBJECTS*4]; + instance_colors_ptr = (GLfloat*)new float[NUM_OBJECTS*4]; + instance_scale_ptr = (GLfloat*)new float[NUM_OBJECTS*3]; + + + + int index=0; + for (int i=0;icreateProxy(aabbMin,aabbMax,shapeType,myptr,1,1,0,0);//m_dispatcher); + proxyArray.push_back(proxy); + + instance_quaternion_ptr[index*4]=0; + instance_quaternion_ptr[index*4+1]=0; + instance_quaternion_ptr[index*4+2]=0; + instance_quaternion_ptr[index*4+3]=1; + + instance_colors_ptr[index*4]=jregisterRigidBody(gShapeIndex,mass,&instance_positions_ptr[index*4],&instance_quaternion_ptr[index*4],writeToGpu); + + index++; + } + } + } + + float posZero[4] = {0,-NUM_OBJECTS_Y/2-1,0,0}; + float ornZero[4] = {0,0,0,1}; + + //register a 'plane' + if (narrowphaseAndSolver) + narrowphaseAndSolver->registerRigidBody(-1, 0.f, posZero,ornZero,false); + + + + if (narrowphaseAndSolver) + narrowphaseAndSolver->writeAllBodiesToGpu(); + + + int size = sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE+SCALE_BUFFER_SIZE; + VBOsize = size; + + char* bla = (char*)malloc(size); + int szc = sizeof(cube_vertices); + memcpy(bla,&cube_vertices[0],szc); + memcpy(bla+sizeof(cube_vertices),instance_positions_ptr,POSITION_BUFFER_SIZE); + memcpy(bla+sizeof(cube_vertices)+POSITION_BUFFER_SIZE,instance_quaternion_ptr,ORIENTATION_BUFFER_SIZE); + memcpy(bla+sizeof(cube_vertices)+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE,instance_colors_ptr, COLOR_BUFFER_SIZE); + memcpy(bla+sizeof(cube_vertices)+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE,instance_scale_ptr, SCALE_BUFFER_SIZE); + + glBufferData(GL_ARRAY_BUFFER, size, bla, GL_DYNAMIC_DRAW);//GL_STATIC_DRAW); + + ///initialize parts of the buffer +#ifdef _USE_SUB_DATA + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(cube_vertices)+ 16384, bla);//cube_vertices); +#endif + + char* dest= (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_WRITE_ONLY);//GL_WRITE_ONLY + memcpy(dest,cube_vertices,sizeof(cube_vertices)); + //memcpy(dest+sizeof(cube_vertices),instance_colors,sizeof(instance_colors)); + glUnmapBuffer( GL_ARRAY_BUFFER); + + + + writeTransforms(); + + /* + glBufferSubData(GL_ARRAY_BUFFER, sizeof(cube_vertices) + sizeof(instance_colors), POSITION_BUFFER_SIZE, instance_positions_ptr); + glBufferSubData(GL_ARRAY_BUFFER, sizeof(cube_vertices) + sizeof(instance_colors)+POSITION_BUFFER_SIZE,ORIENTATION_BUFFER_SIZE , instance_quaternion_ptr); + */ + + glGenVertexArrays(1, &cube_vao); + glBindVertexArray(cube_vao); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glBindVertexArray(0); + + glGenBuffers(1, &index_vbo); + int indexBufferSize = sizeof(cube_indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_vbo); + + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSize, NULL, GL_STATIC_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,0,indexBufferSize,cube_indices); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + +} + + + +void updateCamera() +{ + + + + btVector3 m_cameraUp(0,1,0); + int m_forwardAxis=2; + + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + btScalar rele = m_ele * btScalar(0.01745329251994329547);// rads per deg + btScalar razi = m_azi * btScalar(0.01745329251994329547);// rads per deg + + + btQuaternion rot(m_cameraUp,razi); + + + btVector3 eyePos(0,0,0); + eyePos[m_forwardAxis] = -m_cameraDistance; + + btVector3 forward(eyePos[0],eyePos[1],eyePos[2]); + if (forward.length2() < SIMD_EPSILON) + { + forward.setValue(1.f,0.f,0.f); + } + btVector3 right = m_cameraUp.cross(forward); + btQuaternion roll(right,-rele); + + eyePos = btMatrix3x3(rot) * btMatrix3x3(roll) * eyePos; + + m_cameraPosition[0] = eyePos.getX(); + m_cameraPosition[1] = eyePos.getY(); + m_cameraPosition[2] = eyePos.getZ(); + m_cameraPosition += m_cameraTargetPosition; + + + float m_frustumZNear=1; + float m_frustumZFar=1000; + + if (m_glutScreenWidth == 0 && m_glutScreenHeight == 0) + return; + + float aspect; + btVector3 extents; + + if (m_glutScreenWidth > m_glutScreenHeight) + { + aspect = m_glutScreenWidth / (float)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + } else + { + aspect = m_glutScreenHeight / (float)m_glutScreenWidth; + extents.setValue(1.0f, aspect*1.f,0); + } + + + if (m_ortho) + { + // reset matrix + glLoadIdentity(); + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + glOrtho(lower.getX(), upper.getX(), lower.getY(), upper.getY(),-1000,1000); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } else + { + if (m_glutScreenWidth > m_glutScreenHeight) + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } else + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(m_cameraPosition[0], m_cameraPosition[1], m_cameraPosition[2], + m_cameraTargetPosition[0], m_cameraTargetPosition[1], m_cameraTargetPosition[2], + m_cameraUp.getX(),m_cameraUp.getY(),m_cameraUp.getZ()); + } + +} + + + +void myinit() +{ + GLint err = glGetError(); + + // GLfloat light_ambient[] = { btScalar(0.2), btScalar(0.2), btScalar(0.2), btScalar(1.0) }; + GLfloat light_ambient[] = { btScalar(1.0), btScalar(1.2), btScalar(0.2), btScalar(1.0) }; + + GLfloat light_diffuse[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0) }; + GLfloat light_specular[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0 )}; + /* light_position is NOT default value */ + GLfloat light_position0[] = { btScalar(10000.0), btScalar(10000.0), btScalar(10000.0), btScalar(0.0 )}; + GLfloat light_position1[] = { btScalar(-1.0), btScalar(-10.0), btScalar(-1.0), btScalar(0.0) }; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + + glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + + // glShadeModel(GL_FLAT);//GL_SMOOTH); + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glClearColor(float(0.7),float(0.7),float(0.7),float(0)); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + + static bool m_textureenabled = true; + static bool m_textureinitialized = false; + + + if(m_textureenabled) + { + if(!m_textureinitialized) + { + glActiveTexture(GL_TEXTURE0); + + GLubyte* image=new GLubyte[256*256*3]; + for(int y=0;y<256;++y) + { + const int t=y>>5; + GLubyte* pi=image+y*256*3; + for(int x=0;x<256;++x) + { + if (x<2||y<2||x>253||y>253) + { + pi[0]=0; + pi[1]=0; + pi[2]=0; + } else + { + pi[0]=255; + pi[1]=255; + pi[2]=255; + } + + /* + const int s=x>>5; + const GLubyte b=180; + GLubyte c=b+((s+t&1)&1)*(255-b); + pi[0]=c; + pi[1]=c; + pi[2]=c; + */ + + pi+=3; + } + } + + glGenTextures(1,(GLuint*)&m_texturehandle); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + gluBuild2DMipmaps(GL_TEXTURE_2D,3,256,256,GL_RGB,GL_UNSIGNED_BYTE,image); + delete[] image; + m_textureinitialized=true; + } + // glMatrixMode(GL_TEXTURE); + // glLoadIdentity(); + // glMatrixMode(GL_MODELVIEW); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + + } else + { + glDisable(GL_TEXTURE_2D); + } + + glEnable(GL_COLOR_MATERIAL); + + err = glGetError(); + assert(err==GL_NO_ERROR); + + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); +} + +//#pragma optimize( "g", off ) + + + +void writeTransforms() +{ + + + glFlush(); + char* bla = (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + + float* positions = (float*)(bla+sizeof(cube_vertices)); + float* orientations = (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE); + float* colors= (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE); + float* scaling= (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE); + + // positions[0]+=0.001f; + + static int offset=0; + //offset++; + + static btVector3 axis(1,0,0); + sAngle += 0.01f; + int index=0; + btQuaternion orn(axis,sAngle); + for (int i=0;igetCLBUffer(); + BT_PROFILE("clEnqueueAcquireGLObjects"); + ciErrNum = clEnqueueAcquireGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, NULL); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } else + { + + BT_PROFILE("glMapBuffer and clEnqueueWriteBuffer"); + + blocking= CL_TRUE; + hostPtr= (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + if (!clBuffer) + { + clBuffer = clCreateBuffer(g_cxMainContext, CL_MEM_READ_WRITE, VBOsize, 0, &ciErrNum); + } + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + ciErrNum = clEnqueueWriteBuffer ( g_cqCommandQue, + clBuffer, + blocking, + 0, + VBOsize, + hostPtr,0,0,0 + ); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } + + + + oclCHECKERROR(ciErrNum, CL_SUCCESS); + if (runOpenCLKernels) + { + +#ifdef USE_NEW + gFpIO.m_numObjects = NUM_OBJECTS; + gFpIO.m_positionOffset = (sizeof(cube_vertices) )/4; + gFpIO.m_clObjectsBuffer = clBuffer; + gFpIO.m_dAABB = sBroadphase->m_dAABB; + + + { + BT_PROFILE("setupGpuAabbs"); + setupGpuAabbsSimple(gFpIO); + } + { + BT_PROFILE("calculateOverlappingPairs"); + sBroadphase->calculateOverlappingPairs(0, NUM_OBJECTS); + } + gFpIO.m_dAllOverlappingPairs = sBroadphase->m_dAllOverlappingPairs; + gFpIO.m_numOverlap = sBroadphase->m_numPrefixSum; + //printf("gFpIO.m_numOverlap = %d\n",gFpIO.m_numOverlap ); + if (gFpIO.m_numOverlap>=0 && gFpIO.m_numOverlapgetBodiesGpu(), narrowphaseAndSolver->getBodyInertiasGpu()); + } + if (gFpIO.m_numOverlap) + { + BT_PROFILE("computeContactsAndSolver"); + if (narrowphaseAndSolver) + narrowphaseAndSolver->computeContactsAndSolver(gFpIO.m_dAllOverlappingPairs,gFpIO.m_numOverlap); + } + + { + BT_PROFILE("copyBodyVelocities"); + if (narrowphaseAndSolver) + copyBodyVelocities(gFpIO, gLinVelMem, gAngVelMem, narrowphaseAndSolver->getBodiesGpu(), narrowphaseAndSolver->getBodyInertiasGpu()); + } + } else + { + printf("error, gFpIO.m_numOverlap = %d\n",gFpIO.m_numOverlap); + btAssert(0); + } + +#else + +#endif + { + BT_PROFILE("integrateTransforms"); + + if (runOpenCLKernels) + { + int numObjects = NUM_OBJECTS; + int offset = (sizeof(cube_vertices) )/4; + + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 2, sizeof(cl_mem), (void*)&clBuffer ); + + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 3, sizeof(cl_mem), (void*)&gLinVelMem); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 4, sizeof(cl_mem), (void*)&gAngVelMem); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 5, sizeof(cl_mem), (void*)&gBodyTimes); + + + + + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((NUM_OBJECTS + (workGroupSize)) / workGroupSize); + + if (workGroupSize>numWorkItems) + workGroupSize=numWorkItems; + + ciErrNum = clEnqueueNDRangeKernel(g_cqCommandQue, g_integrateTransformsKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + } + + + } + + if (USE_GL_CL_INTEROP) + { + BT_PROFILE("clEnqueueReleaseGLObjects"); + ciErrNum = clEnqueueReleaseGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, 0); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } + else + { + BT_PROFILE("clEnqueueReadBuffer clReleaseMemObject and glUnmapBuffer"); + ciErrNum = clEnqueueReadBuffer ( g_cqCommandQue, + clBuffer, + blocking, + 0, + VBOsize, + hostPtr,0,0,0); + + //clReleaseMemObject(clBuffer); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + glUnmapBuffer( GL_ARRAY_BUFFER); + glFlush(); + } + + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + + if (runOpenCLKernels) + { + BT_PROFILE("clFinish"); + clFinish(g_cqCommandQue); + } + + + + + } +} + + +//#pragma optimize( "g", on ) + + +void RenderScene(void) +{ + BT_PROFILE("GlutDisplayFunc"); + + +#if 0 + float modelview[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + // get the current modelview matrix + glGetFloatv(GL_MODELVIEW_MATRIX , modelview); + float projection[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + glGetFloatv(GL_PROJECTION_MATRIX, projection); +#endif + + myinit(); + + updateCamera(); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //render coordinate system + glBegin(GL_LINES); + glColor3f(1,0,0); + glVertex3f(0,0,0); + glVertex3f(1,0,0); + glColor3f(0,1,0); + glVertex3f(0,0,0); + glVertex3f(0,1,0); + glColor3f(0,0,1); + glVertex3f(0,0,0); + glVertex3f(0,0,1); + glEnd(); + + //do a finish, to make sure timings are clean + // glFinish(); + + float start = gStopwatch.getTimeMilliseconds(); + + // glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + + //updatePos(); + + simulationLoop(); + + //useCPU = true; + + float stop = gStopwatch.getTimeMilliseconds(); + gStopwatch.reset(); + + if (0)//printStats) + { + printf("updatePos=%f ms on ",stop-start); + + if (useCPU) + { + printf("CPU \n"); + } else + { + printf("OpenCL "); + if (runOpenCLKernels) + printf("running the kernels"); + else + printf("without running the kernels"); + printf("\n"); + } + } + + glBindVertexArray(cube_vao); + + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 9*sizeof(float), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices))); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE)); + int uvoffset = 7*sizeof(float); + int normaloffset = 4*sizeof(float); + + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)uvoffset); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)normaloffset); + glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE)); + glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE)); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + glEnableVertexAttribArray(6); + + glVertexAttribDivisor(0, 0); + glVertexAttribDivisor(1, 1); + glVertexAttribDivisor(2, 1); + glVertexAttribDivisor(3, 0); + glVertexAttribDivisor(4, 0); + glVertexAttribDivisor(5, 1); + glVertexAttribDivisor(6, 1); + + glUseProgram(instancingShader); + glUniform1f(angle_loc, 0); + GLfloat pm[16]; + glGetFloatv(GL_PROJECTION_MATRIX, pm); + glUniformMatrix4fv(ProjectionMatrix, 1, false, &pm[0]); + + GLfloat mvm[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mvm); + glUniformMatrix4fv(ModelViewMatrix, 1, false, &mvm[0]); + + glUniform1i(uniform_texture_diffuse, 0); + + glFlush(); + int numInstances = NUM_OBJECTS; + int indexCount = sizeof(cube_indices)/sizeof(int); + int indexOffset = 0; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_vbo); + { + BT_PROFILE("glDrawElementsInstanced"); + glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void*)indexOffset, numInstances); + } + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + glutSwapBuffers(); + glutPostRedisplay(); + + GLint err = glGetError(); + assert(err==GL_NO_ERROR); +} + +extern int numPairsOut; +void mainloop(void) +{ + CProfileManager::Reset(); + RenderScene(); + CProfileManager::Increment_Frame_Counter(); + + if (printStats && runOpenCLKernels) + { + static int count = 10; + count--; + if (count<0) + { + CProfileManager::dumpAll(); + printf("total broadphase pairs= %d\n", gFpIO.m_numOverlap); + printf("numPairsOut (culled) = %d\n", numPairsOut); + + printStats = false; + } + } +} + + +void ChangeSize(int w, int h) +{ + m_glutScreenWidth = w; + m_glutScreenHeight = h; + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + delete g_interopBuffer; + clReleaseKernel(g_integrateTransformsKernel); + releaseFindPairs(fpio); + DeleteCL(); + DeleteShaders(); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + + // Set Viewport to window dimensions + glViewport(0, 0, w, h); + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + InitCL(); + InitShaders(); + + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); + g_integrateTransformsKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, interopKernelString, "interopKernel" ); + initFindPairs(...); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + +} + +void Keyboard(unsigned char key, int x, int y) +{ + switch (key) + { + case 27: + done = true; + break; + case 'O': + case 'o': + { + m_ortho = !m_ortho; + break; + } + case 'c': + case 'C': + { + useCPU = !useCPU; + if (useCPU) + printf("using CPU\n"); + else + printf("using OpenCL\n"); + break; + } + case 's': + case 'S': + { + printStats = !printStats; + break; + } + case 'k': + case 'K': + { + runOpenCLKernels=!runOpenCLKernels; + break; + } + case 'q': + case 'Q': + exit(0); + default: + break; + } +} + +// Cleanup +void ShutdownRC(void) +{ + glDeleteBuffers(1, &cube_vbo); + glDeleteVertexArrays(1, &cube_vao); +} + +#include "CommandlineArgs.h" + +void Usage() +{ + printf("\nprogram.exe [--preferred_gpu=] [--batch_gpu=<0,1>] [--preferred_platform=] [--enable_interop=<0 or 1>] [--x_dim=] [--y_dim=] [--z_dim=] [--x_gap=] [--y_gap=] [--z_gap=]\n"); + printf("\n"); + printf("preferred_gpu : the index used for OpenCL, in case multiple OpenCL-capable GPU are available. This is ignored if interop is enabled"); + printf("preferred_platform : the platform index used for OpenCL, in case multiple OpenCL-capable platforms are available. This is ignored if interop is enabled"); + printf("enable_interop : Use OpenGL/OpenCL interoperability, avoiding memory copy between GPU and main memory"); + printf("batch_gpu : Use GPU to created solver batches. Set to zero to disable to improve compatibility with many GPUs"); + + +} + +int main(int argc, char* argv[]) +{ + + CommandLineArgs args(argc,argv); + + if (args.CheckCmdLineFlag("help")) + { + Usage(); + return 0; + } + + args.GetCmdLineArgument("x_dim", NUM_OBJECTS_X); + args.GetCmdLineArgument("y_dim", NUM_OBJECTS_Y); + args.GetCmdLineArgument("z_dim", NUM_OBJECTS_Z); + + args.GetCmdLineArgument("x_gap", X_GAP); + args.GetCmdLineArgument("y_gap", Y_GAP); + args.GetCmdLineArgument("z_gap", Z_GAP); + + args.GetCmdLineArgument("enable_interop", USE_GL_CL_INTEROP); + args.GetCmdLineArgument("preferred_gpu", preferredGPU); + args.GetCmdLineArgument("preferred_platform", preferredPlatform); + args.GetCmdLineArgument("batch_gpu", gpuBatchContacts); + + + + + printf("Dimensions (%d,%d,%d) with gap (%f,%f,%f), using interop=%d, gpu %d, cl platform %d, gpuBatchContacts %d \n",NUM_OBJECTS_X,NUM_OBJECTS_Y,NUM_OBJECTS_Z,X_GAP,Y_GAP,Z_GAP,USE_GL_CL_INTEROP,preferredGPU, preferredPlatform,gpuBatchContacts); + + + + + { + NUM_OBJECTS = NUM_OBJECTS_X*NUM_OBJECTS_Y*NUM_OBJECTS_Z; + POSITION_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*4); + ORIENTATION_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*4); + COLOR_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*4); + SCALE_BUFFER_SIZE = (NUM_OBJECTS*sizeof(float)*3); + } + + srand(0); + // printf("vertexShader = \n%s\n",vertexShader); + // printf("fragmentShader = \n%s\n",fragmentShader); + + glutInit(&argc, argv); + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); + + + glutInitWindowSize(m_glutScreenWidth, m_glutScreenHeight); + char buf[1024]; + if (USE_GL_CL_INTEROP) + { + sprintf(buf,"GPU rigid body pipeline using OpenCL - OpenGL interop, simulates %d cubes on the GPU (use c to toggle CPU/CL)", NUM_OBJECTS); + } else + { + sprintf(buf,"GPU rigid body pipeline, simulates %d cubes on the GPU (use c to toggle CPU/CL)", NUM_OBJECTS); + } + + glutCreateWindow(buf); + + glutReshapeFunc(ChangeSize); + + glutMouseFunc(mouseFunc); + glutMotionFunc(mouseMotionFunc); + + glutKeyboardFunc(Keyboard); + glutDisplayFunc(mainloop); + + GLenum err = glewInit(); + if (GLEW_OK != err) + { + /* Problem: glewInit failed, something is seriously wrong. */ + fprintf(stderr, "Error: %s\n", glewGetErrorString(err)); + } + + //ChangeSize(m_glutScreenWidth,m_glutScreenHeight); + + + InitCL(preferredGPU, preferredPlatform); + + +#define CUSTOM_CL_INITIALIZATION +#ifdef CUSTOM_CL_INITIALIZATION + g_deviceCL = new adl::DeviceCL(); + g_deviceCL->m_deviceIdx = g_device; + g_deviceCL->m_context = g_cxMainContext; + g_deviceCL->m_commandQueue = g_cqCommandQue; + g_deviceCL->m_kernelManager = new adl::KernelManager; + +#else + DeviceUtils::Config cfg; + cfg.m_type = DeviceUtils::Config::DEVICE_CPU; + g_deviceCL = DeviceUtils::allocate( TYPE_CL, cfg ); +#endif + + int size = NUM_OBJECTS; + adl::Buffer linvelBuf( g_deviceCL, size ); + adl::Buffer angvelBuf( g_deviceCL, size ); + adl::Buffer bodyTimes(g_deviceCL,size); + + gLinVelMem = (cl_mem)linvelBuf.m_ptr; + gAngVelMem = (cl_mem)angvelBuf.m_ptr; + gBodyTimes = (cl_mem)bodyTimes.m_ptr; + + btVector3* linVelHost= new btVector3[size]; + btVector3* angVelHost = new btVector3[size]; + float* bodyTimesHost = new float[size]; + + { + int index=0; + for (int i=0;i verts; + int numVertices = (sizeof(cube_vertices) )/(9*sizeof(GLfloat)); + + for (int i=0;iregisterShape(s_convexHeightField); + + InitShaders(); + + if (USE_GL_CL_INTEROP) + { + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); + } + + + cl_program prog = btOpenCLUtils::compileCLProgramFromString(g_cxMainContext,g_device,interopKernelString,0,"",INTEROPKERNEL_SRC_PATH); + g_integrateTransformsKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, g_device,interopKernelString, "integrateTransformsKernel" ,0,prog); + + + initFindPairs(gFpIO, g_cxMainContext, g_device, g_cqCommandQue, NUM_OBJECTS); + + + + + + glutMainLoop(); + ShutdownRC(); + + return 0; +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/premake4.lua new file mode 100644 index 000000000..d2fbbc877 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline/premake4.lua @@ -0,0 +1,5 @@ + + include "AMD" +-- include "Intel" + include "NVIDIA" + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/AMD/premake4.lua new file mode 100644 index 000000000..8abbebb18 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/AMD/premake4.lua @@ -0,0 +1,64 @@ + + hasCL = findOpenCL_AMD() + + if (hasCL) then + + project "OpenCL_gpu_rigidbody_pipeline2_AMD" + + initOpenCL_AMD() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + + initOpenGL() + initGlew() + + includedirs { + "../../primitives", + "../../../../../src" + } + + files { + "../main.cpp", + "../CLPhysicsDemo.cpp", + "../CLPhysicsDemo.h", + "../GLInstancingRenderer.cpp", + "../GLInstancingRenderer.h", + "../GlutRenderer.cpp", + "../GlutRenderer.h", + "../Win32OpenGLRenderManager.cpp", + "../Win32OpenGLRenderManager.h", + "../../gpu_rigidbody_pipeline/btConvexUtility.cpp", + "../../gpu_rigidbody_pipeline/btConvexUtility.h", + "../../gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.cpp", + "../../gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.cpp", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.h", + "../../../../../src/LinearMath/btConvexHullComputer.cpp", + "../../../../../src/LinearMath/btConvexHullComputer.h", + "../../broadphase_benchmark/findPairsOpenCL.cpp", + "../../broadphase_benchmark/findPairsOpenCL.h", + "../../broadphase_benchmark/btGridBroadphaseCL.cpp", + "../../broadphase_benchmark/btGridBroadphaseCL.h", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h", + "../../../../../src/LinearMath/btAlignedAllocator.cpp", + "../../../../../src/LinearMath/btQuickprof.cpp", + "../../../../../src/LinearMath/btQuickprof.h", + "../../../../../src/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp", + "../../../../../src/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.cpp new file mode 100644 index 000000000..363f57525 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.cpp @@ -0,0 +1,529 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "OpenGLInclude.h" + +#include "CLPhysicsDemo.h" +#include "LinearMath/btAlignedObjectArray.h" +#include "DemoSettings.h" +#include "../basic_initialize/btOpenCLUtils.h" +#include "../opengl_interop/btOpenCLGLInteropBuffer.h" +#include "../broadphase_benchmark/findPairsOpenCL.h" +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuaternion.h" +#include "LinearMath/btMatrix3x3.h" +#include "../../opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h" +#include "../../opencl/gpu_rigidbody_pipeline/btConvexUtility.h" +#include "../../dynamics/basic_demo/ConvexHeightFieldShape.h" +#include "../broadphase_benchmark/btGridBroadphaseCl.h" +#include "LinearMath/btQuickprof.h" + + +#define MSTRINGIFY(A) #A +static char* interopKernelString = +#include "../broadphase_benchmark/integrateKernel.cl" + +#define INTEROPKERNEL_SRC_PATH "../../opencl/broadphase_benchmark/integrateKernel.cl" + +cl_kernel g_integrateTransformsKernel; + + + +bool runOpenCLKernels = true; + + +btGpuNarrowphaseAndSolver* narrowphaseAndSolver = 0; +ConvexHeightField* s_convexHeightField = 0 ; +btOpenCLGLInteropBuffer* g_interopBuffer = 0; + +extern GLuint cube_vbo; +extern int VBOsize; + +cl_mem clBuffer=0; +char* hostPtr=0; +cl_bool blocking= CL_TRUE; + + + +btFindPairsIO gFpIO; + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; +cl_device_id g_device; + +cl_mem gLinVelMem=0; +cl_mem gAngVelMem=0; +cl_mem gBodyTimes=0; + +#include + +adl::DeviceCL* g_deviceCL=0; + +struct btAABBHost //keep this in sync with btAABBCL! +{ + float fx; + float fy; + float fz; + unsigned int uw; +}; + +struct InternalData +{ + adl::Buffer* m_linVelBuf; + adl::Buffer* m_angVelBuf; + adl::Buffer* m_bodyTimes; + bool m_useInterop; + btGridBroadphaseCl* m_Broadphase; + + adl::Buffer* m_localShapeAABB; + + btVector3* m_linVelHost; + btVector3* m_angVelHost; + float* m_bodyTimesHost; + + InternalData():m_linVelBuf(0),m_angVelBuf(0),m_bodyTimes(0),m_useInterop(0),m_Broadphase(0) + { + m_linVelHost= new btVector3[MAX_CONVEX_BODIES_CL]; + m_angVelHost = new btVector3[MAX_CONVEX_BODIES_CL]; + m_bodyTimesHost = new float[MAX_CONVEX_BODIES_CL]; + } + ~InternalData() + { + delete[] m_linVelHost; + delete[] m_angVelHost; + delete[] m_bodyTimesHost; + + } +}; + + +void InitCL(int preferredDeviceIndex, int preferredPlatformIndex, bool useInterop) +{ + void* glCtx=0; + void* glDC = 0; + +#ifdef _WIN32 + glCtx = wglGetCurrentContext(); +#else //!_WIN32 + GLXContext glCtx = glXGetCurrentContext(); +#endif //!_WIN32 + glDC = wglGetCurrentDC(); + + int ciErrNum = 0; +#ifdef CL_PLATFORM_INTEL + cl_device_type deviceType = CL_DEVICE_TYPE_ALL; +#else + cl_device_type deviceType = CL_DEVICE_TYPE_GPU; +#endif + + + + if (useInterop) + { + g_cxMainContext = btOpenCLUtils::createContextFromType(deviceType, &ciErrNum, glCtx, glDC); + } else + { + g_cxMainContext = btOpenCLUtils::createContextFromType(deviceType, &ciErrNum, 0,0,preferredDeviceIndex, preferredPlatformIndex); + } + + + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + int numDev = btOpenCLUtils::getNumDevices(g_cxMainContext); + + if (numDev>0) + { + g_device= btOpenCLUtils::getDevice(g_cxMainContext,0); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(g_device,clInfo); + btOpenCLUtils::printDeviceInfo(g_device); + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, g_device, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + +} + + + + +CLPhysicsDemo::CLPhysicsDemo(Win32OpenGLWindow* renderer) +{ + m_numCollisionShapes=0; + m_numPhysicsInstances=0; + + m_data = new InternalData; +} + +CLPhysicsDemo::~CLPhysicsDemo() +{ + +} + + +void CLPhysicsDemo::writeBodiesToGpu() +{ + if (narrowphaseAndSolver) + narrowphaseAndSolver->writeAllBodiesToGpu(); +} + +int CLPhysicsDemo::registerCollisionShape(const float* vertices, int strideInBytes, int numVertices, const float* scaling) +{ + btAlignedObjectArray verts; + + unsigned char* vts = (unsigned char*) vertices; + for (int i=0;iregisterShape(s_convexHeightField); + + if (shapeIndex>=0) + { + btAABBHost aabbMin, aabbMax; + aabbMin.fx = s_convexHeightField->m_aabb.m_min.x; + aabbMin.fy = s_convexHeightField->m_aabb.m_min.y; + aabbMin.fz= s_convexHeightField->m_aabb.m_min.z; + aabbMin.uw = shapeIndex; + + aabbMax.fx = s_convexHeightField->m_aabb.m_max.x; + aabbMax.fy = s_convexHeightField->m_aabb.m_max.y; + aabbMax.fz= s_convexHeightField->m_aabb.m_max.z; + aabbMax.uw = shapeIndex; + + m_data->m_localShapeAABB->write(&aabbMin,1,shapeIndex*2); + m_data->m_localShapeAABB->write(&aabbMax,1,shapeIndex*2+1); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } + + m_numCollisionShapes++; + delete[] eqn; + return shapeIndex; +} + +int CLPhysicsDemo::registerPhysicsInstance(float mass, const float* position, const float* orientation, int collisionShapeIndex, void* userPointer) +{ + btVector3 aabbMin(position[0],position[0],position[0]); + btVector3 aabbMax = aabbMin; + aabbMin -= btVector3(1.f,1.f,1.f); + aabbMax += btVector3(1.f,1.f,1.f); + + if (collisionShapeIndex>=0) + { + btBroadphaseProxy* proxy = m_data->m_Broadphase->createProxy(aabbMin,aabbMax,collisionShapeIndex,userPointer,1,1,0,0);//m_dispatcher); + } + + bool writeToGpu = false; + int bodyIndex = -1; + + if (narrowphaseAndSolver) + bodyIndex = narrowphaseAndSolver->registerRigidBody(collisionShapeIndex,mass,position,orientation,writeToGpu); + + m_numPhysicsInstances++; + return bodyIndex; +} + + + +void CLPhysicsDemo::init(int preferredDevice, int preferredPlatform, bool useInterop) +{ + + InitCL(-1,-1,useInterop); + +#define CUSTOM_CL_INITIALIZATION +#ifdef CUSTOM_CL_INITIALIZATION + g_deviceCL = new adl::DeviceCL(); + g_deviceCL->m_deviceIdx = g_device; + g_deviceCL->m_context = g_cxMainContext; + g_deviceCL->m_commandQueue = g_cqCommandQue; + g_deviceCL->m_kernelManager = new adl::KernelManager; + +#else + DeviceUtils::Config cfg; + cfg.m_type = DeviceUtils::Config::DEVICE_CPU; + g_deviceCL = DeviceUtils::allocate( TYPE_CL, cfg ); +#endif + + //adl::Solver::allocate(g_deviceCL->allocate( + m_data->m_linVelBuf = new adl::Buffer(g_deviceCL,MAX_CONVEX_BODIES_CL); + m_data->m_angVelBuf = new adl::Buffer(g_deviceCL,MAX_CONVEX_BODIES_CL); + m_data->m_bodyTimes = new adl::Buffer(g_deviceCL,MAX_CONVEX_BODIES_CL); + + m_data->m_localShapeAABB = new adl::Buffer(g_deviceCL,MAX_CONVEX_SHAPES_CL); + + gLinVelMem = (cl_mem)m_data->m_linVelBuf->m_ptr; + gAngVelMem = (cl_mem)m_data->m_angVelBuf->m_ptr; + gBodyTimes = (cl_mem)m_data->m_bodyTimes->m_ptr; + + + + + narrowphaseAndSolver = new btGpuNarrowphaseAndSolver(g_deviceCL); + + + + int maxObjects = btMax(256,MAX_CONVEX_BODIES_CL); + int maxPairsSmallProxy = 32; + btOverlappingPairCache* overlappingPairCache=0; + + m_data->m_Broadphase = new btGridBroadphaseCl(overlappingPairCache,btVector3(4.f, 4.f, 4.f), 128, 128, 128,maxObjects, maxObjects, maxPairsSmallProxy, 100.f, 128, + g_cxMainContext ,g_device,g_cqCommandQue, g_deviceCL); + + + + cl_program prog = btOpenCLUtils::compileCLProgramFromString(g_cxMainContext,g_device,interopKernelString,0,"",INTEROPKERNEL_SRC_PATH); + g_integrateTransformsKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, g_device,interopKernelString, "integrateTransformsKernel" ,0,prog); + + + initFindPairs(gFpIO, g_cxMainContext, g_device, g_cqCommandQue, MAX_CONVEX_BODIES_CL); + + + + +} + + + +void CLPhysicsDemo::writeVelocitiesToGpu() +{ + m_data->m_linVelBuf->write(m_data->m_linVelHost,MAX_CONVEX_BODIES_CL); + m_data->m_angVelBuf->write(m_data->m_angVelHost,MAX_CONVEX_BODIES_CL); + m_data->m_bodyTimes->write(m_data->m_bodyTimesHost,MAX_CONVEX_BODIES_CL); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); +} + + +void CLPhysicsDemo::setupInterop() +{ + m_data->m_useInterop = true; + + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); +} + +void CLPhysicsDemo::cleanup() +{ + delete narrowphaseAndSolver; + + delete m_data->m_linVelBuf; + delete m_data->m_angVelBuf; + delete m_data->m_bodyTimes; + delete m_data->m_localShapeAABB; + + delete m_data->m_Broadphase; + delete m_data; + + delete g_deviceCL->m_kernelManager; + delete g_deviceCL; + + m_data=0; + g_deviceCL=0; + delete g_interopBuffer; + delete s_convexHeightField; +} + + + + + +void CLPhysicsDemo::stepSimulation() +{ + BT_PROFILE("simulationLoop"); + + { + BT_PROFILE("glFinish"); + glFinish(); + } + cl_int ciErrNum = CL_SUCCESS; + + + if(m_data->m_useInterop) + { + clBuffer = g_interopBuffer->getCLBUffer(); + BT_PROFILE("clEnqueueAcquireGLObjects"); + ciErrNum = clEnqueueAcquireGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, NULL); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } else + { + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + + BT_PROFILE("glMapBuffer and clEnqueueWriteBuffer"); + + blocking= CL_TRUE; + hostPtr= (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + if (!clBuffer) + { + clBuffer = clCreateBuffer(g_cxMainContext, CL_MEM_READ_WRITE, VBOsize, 0, &ciErrNum); + } + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + ciErrNum = clEnqueueWriteBuffer ( g_cqCommandQue, + clBuffer, + blocking, + 0, + VBOsize, + hostPtr,0,0,0 + ); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } + + + + oclCHECKERROR(ciErrNum, CL_SUCCESS); + if (runOpenCLKernels && m_numPhysicsInstances) + { + + gFpIO.m_numObjects = m_numPhysicsInstances; + gFpIO.m_positionOffset = SHAPE_VERTEX_BUFFER_SIZE/4; + gFpIO.m_clObjectsBuffer = clBuffer; + gFpIO.m_dAABB = m_data->m_Broadphase->m_dAABB; + gFpIO.m_dlocalShapeAABB = (cl_mem)m_data->m_localShapeAABB->m_ptr; + gFpIO.m_numOverlap = 0; + { + BT_PROFILE("setupGpuAabbs"); + setupGpuAabbsFull(gFpIO,narrowphaseAndSolver->getBodiesGpu() ); + } + if (1) + { + BT_PROFILE("calculateOverlappingPairs"); + m_data->m_Broadphase->calculateOverlappingPairs(0, m_numPhysicsInstances); + gFpIO.m_dAllOverlappingPairs = m_data->m_Broadphase->m_dAllOverlappingPairs; + gFpIO.m_numOverlap = m_data->m_Broadphase->m_numPrefixSum; + } + + //printf("gFpIO.m_numOverlap = %d\n",gFpIO.m_numOverlap ); + if (gFpIO.m_numOverlap>=0 && gFpIO.m_numOverlapgetBodiesGpu(), narrowphaseAndSolver->getBodyInertiasGpu()); + } + if (gFpIO.m_numOverlap) + { + BT_PROFILE("computeContactsAndSolver"); + if (narrowphaseAndSolver) + narrowphaseAndSolver->computeContactsAndSolver(gFpIO.m_dAllOverlappingPairs,gFpIO.m_numOverlap); + } + + { + BT_PROFILE("copyBodyVelocities"); + if (narrowphaseAndSolver) + copyBodyVelocities(gFpIO, gLinVelMem, gAngVelMem, narrowphaseAndSolver->getBodiesGpu(), narrowphaseAndSolver->getBodyInertiasGpu()); + } + } + + } else + { + printf("error, gFpIO.m_numOverlap = %d\n",gFpIO.m_numOverlap); + btAssert(0); + } + + + { + BT_PROFILE("integrateTransforms"); + + if (runOpenCLKernels) + { + int numObjects = m_numPhysicsInstances; + int offset = SHAPE_VERTEX_BUFFER_SIZE/4; + + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 2, sizeof(cl_mem), (void*)&clBuffer ); + + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 3, sizeof(cl_mem), (void*)&gLinVelMem); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 4, sizeof(cl_mem), (void*)&gAngVelMem); + ciErrNum = clSetKernelArg(g_integrateTransformsKernel, 5, sizeof(cl_mem), (void*)&gBodyTimes); + + + + + size_t workGroupSize = 64; + size_t numWorkItems = workGroupSize*((m_numPhysicsInstances + (workGroupSize)) / workGroupSize); + + if (workGroupSize>numWorkItems) + workGroupSize=numWorkItems; + + ciErrNum = clEnqueueNDRangeKernel(g_cqCommandQue, g_integrateTransformsKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + } + + + } + + if(m_data->m_useInterop) + { + BT_PROFILE("clEnqueueReleaseGLObjects"); + ciErrNum = clEnqueueReleaseGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, 0); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + } + else + { + BT_PROFILE("clEnqueueReadBuffer clReleaseMemObject and glUnmapBuffer"); + ciErrNum = clEnqueueReadBuffer ( g_cqCommandQue, + clBuffer, + blocking, + 0, + VBOsize, + hostPtr,0,0,0); + + //clReleaseMemObject(clBuffer); + adl::DeviceUtils::waitForCompletion( g_deviceCL ); + glUnmapBuffer( GL_ARRAY_BUFFER); + glFlush(); + } + + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + + if (runOpenCLKernels) + { + BT_PROFILE("clFinish"); + clFinish(g_cqCommandQue); + } + + +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.h new file mode 100644 index 000000000..0ed2e7392 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/CLPhysicsDemo.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef CL_PHYSICS_DEMO_H +#define CL_PHYSICS_DEMO_H + +class Win32OpenGLWindow; + +struct CLPhysicsDemo +{ + Win32OpenGLWindow* m_renderer; + + int m_numCollisionShapes; + + int m_numPhysicsInstances; + + struct InternalData* m_data; + + CLPhysicsDemo(Win32OpenGLWindow* renderer); + + virtual ~CLPhysicsDemo(); + + //btOpenCLGLInteropBuffer* m_interopBuffer; + + void init(int preferredDevice, int preferredPlatform, bool useInterop); + + void setupInterop(); + + int registerCollisionShape(const float* vertices, int strideInBytes, int numVertices, const float* scaling); + + int registerPhysicsInstance(float mass, const float* position, const float* orientation, int collisionShapeIndex, void* userPointer); + + void writeVelocitiesToGpu(); + void writeBodiesToGpu(); + + void cleanup(); + + void stepSimulation(); +}; + +#endif//CL_PHYSICS_DEMO_H \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/DemoSettings.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/DemoSettings.h new file mode 100644 index 000000000..5b55e001d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/DemoSettings.h @@ -0,0 +1,24 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef DEMO_SETTINGS_H +#define DEMO_SETTINGS_H + +#define SHAPE_VERTEX_BUFFER_SIZE 1024*1024 + +#define SHAPE_BUFFER_SIZE (SHAPE_VERTEX_BUFFER_SIZE) + + +#endif //DEMO_SETTINGS_H \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.cpp new file mode 100644 index 000000000..9e7525362 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.cpp @@ -0,0 +1,861 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#include "OpenGLInclude.h" +#include "GLInstancingRenderer.h" + +#include +#include "DemoSettings.h" +#include +#include +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuaternion.h" +#include "LinearMath/btQuickprof.h" +#include "LinearMath/btMatrix3x3.h" + +#include "../../opencl/gpu_rigidbody_pipeline/btGpuNarrowphaseAndSolver.h"//for MAX_CONVEX_BODIES_CL + +struct btGraphicsInstance +{ + GLuint m_cube_vao; + GLuint m_index_vbo; + int m_numIndices; + int m_numVertices; + + int m_numGraphicsInstances; + + int m_instanceOffset; + int m_vertexArrayOffset; + + btGraphicsInstance() :m_cube_vao(-1),m_index_vbo(-1),m_numIndices(-1),m_numVertices(-1),m_numGraphicsInstances(0),m_instanceOffset(0),m_vertexArrayOffset(0) + { + } + +}; + + + +bool m_ortho = false; +int m_glutScreenWidth = 1024; +int m_glutScreenHeight = 768; + + + +extern int gShapeIndex; + + +btVector3 m_cameraPosition(0,0,0);//will be overridden by a position computed from azi/ele +btVector3 m_cameraTargetPosition(30,-5,-20); +btScalar m_cameraDistance = 95; +btVector3 m_cameraUp(0,1,0); +float m_azi=95.f; +float m_ele=15.f; + + + + +int VBOsize =0; + + + +struct InternalDataRenderer +{ + GLfloat* m_instance_positions_ptr; + GLfloat* m_instance_quaternion_ptr; + GLfloat* m_instance_colors_ptr; + GLfloat* m_instance_scale_ptr; + + InternalDataRenderer() :m_instance_positions_ptr (0),m_instance_quaternion_ptr(0),m_instance_colors_ptr(0),m_instance_scale_ptr(0) + { + } + +}; + +static GLuint instancingShader; // The instancing renderer + +GLuint cube_vbo; + +static GLuint m_texturehandle; + +static bool done = false; +static GLint angle_loc = 0; +static GLint ModelViewMatrix; +static GLint ProjectionMatrix; + + + +GLInstancingRenderer::GLInstancingRenderer() +{ + + m_data = new InternalDataRenderer; + + m_data->m_instance_positions_ptr = (GLfloat*)new float[MAX_CONVEX_BODIES_CL*4]; + m_data->m_instance_quaternion_ptr = (GLfloat*)new float[MAX_CONVEX_BODIES_CL*4]; + m_data->m_instance_colors_ptr = (GLfloat*)new float[MAX_CONVEX_BODIES_CL*4]; + m_data->m_instance_scale_ptr = (GLfloat*)new float[MAX_CONVEX_BODIES_CL*3]; + +} + +GLInstancingRenderer::~GLInstancingRenderer() +{ + delete m_data; +} + + +static GLint uniform_texture_diffuse = 0; + +//used for dynamic loading from disk (default switched off) +#define MAX_SHADER_LENGTH 8192 +static GLubyte shaderText[MAX_SHADER_LENGTH]; + +static const char* vertexShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"\n" +"\n" +"layout (location = 0) in vec4 position;\n" +"layout (location = 1) in vec4 instance_position;\n" +"layout (location = 2) in vec4 instance_quaternion;\n" +"layout (location = 3) in vec2 uvcoords;\n" +"layout (location = 4) in vec3 vertexnormal;\n" +"layout (location = 5) in vec4 instance_color;\n" +"layout (location = 6) in vec3 instance_scale;\n" +"\n" +"\n" +"uniform float angle = 0.0;\n" +"uniform mat4 ModelViewMatrix;\n" +"uniform mat4 ProjectionMatrix;\n" +"\n" +"out Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"out Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"\n" +"vec4 quatMul ( in vec4 q1, in vec4 q2 )\n" +"{\n" +" vec3 im = q1.w * q2.xyz + q1.xyz * q2.w + cross ( q1.xyz, q2.xyz );\n" +" vec4 dt = q1 * q2;\n" +" float re = dot ( dt, vec4 ( -1.0, -1.0, -1.0, 1.0 ) );\n" +" return vec4 ( im, re );\n" +"}\n" +"\n" +"vec4 quatFromAxisAngle(vec4 axis, in float angle)\n" +"{\n" +" float cah = cos(angle*0.5);\n" +" float sah = sin(angle*0.5);\n" +" float d = inversesqrt(dot(axis,axis));\n" +" vec4 q = vec4(axis.x*sah*d,axis.y*sah*d,axis.z*sah*d,cah);\n" +" return q;\n" +"}\n" +"//\n" +"// vector rotation via quaternion\n" +"//\n" +"vec4 quatRotate3 ( in vec3 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, vec4 ( p, 0.0 ) );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"vec4 quatRotate ( in vec4 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, p );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"\n" +"out vec3 lightDir,normal,ambient;\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 q = instance_quaternion;\n" +" ambient = vec3(0.3,.3,0.3);\n" +" \n" +" \n" +" vec4 local_normal = (quatRotate3( vertexnormal,q));\n" +" vec3 light_pos = vec3(-0.8,1,-0.6);\n" +" normal = local_normal.xyz;\n"//normalize(ModelViewMatrix * local_normal).xyz;\n" +"\n" +" lightDir = normalize(light_pos);//gl_LightSource[0].position.xyz));\n" +"// lightDir = normalize(vec3(gl_LightSource[0].position));\n" +" \n" +" vec4 axis = vec4(1,1,1,0);\n" +" vec4 localcoord = quatRotate3( position.xyz*instance_scale,q);\n" +" vec4 vertexPos = ProjectionMatrix * ModelViewMatrix *(instance_position+localcoord);\n" +"\n" +" gl_Position = vertexPos;\n" +" \n" +" fragment.color = instance_color;\n" +" vert.texcoord = uvcoords;\n" +"}\n" +; + + +static const char* fragmentShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"in Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"in Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"uniform sampler2D Diffuse;\n" +"\n" +"in vec3 lightDir,normal,ambient;\n" +"\n" +"out vec4 color;\n" +"\n" +"void main_textured(void)\n" +"{\n" +" color = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 texel = fragment.color*texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +" vec3 ct,cf;\n" +" float intensity,at,af;\n" +" intensity = max(dot(lightDir,normalize(normal)),.2);\n" +" cf = intensity*vec3(1.0,1.0,1.0)+ambient;" +" af = 1.0;\n" +" \n" +" ct = texel.rgb;\n" +" at = texel.a;\n" +" \n" +" color = vec4(ct * cf, at * af); \n" +"}\n" +; + + +// Load the shader from the source text +void gltLoadShaderSrc(const char *szShaderSrc, GLuint shader) +{ + GLchar *fsStringPtr[1]; + + fsStringPtr[0] = (GLchar *)szShaderSrc; + glShaderSource(shader, 1, (const GLchar **)fsStringPtr, NULL); +} + + +GLuint gltLoadShaderPair(const char *szVertexProg, const char *szFragmentProg) +{ + // Temporary Shader objects + GLuint hVertexShader; + GLuint hFragmentShader; + GLuint hReturn = 0; + GLint testVal; + + // Create shader objects + hVertexShader = glCreateShader(GL_VERTEX_SHADER); + hFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + gltLoadShaderSrc(vertexShader, hVertexShader); + gltLoadShaderSrc(fragmentShader, hFragmentShader); + + // Compile them + glCompileShader(hVertexShader); + glCompileShader(hFragmentShader); + + // Check for errors + glGetShaderiv(hVertexShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hVertexShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + glGetShaderiv(hFragmentShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hFragmentShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + // Link them - assuming it works... + hReturn = glCreateProgram(); + glAttachShader(hReturn, hVertexShader); + glAttachShader(hReturn, hFragmentShader); + + glLinkProgram(hReturn); + + // These are no longer needed + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + + // Make sure link worked too + glGetProgramiv(hReturn, GL_LINK_STATUS, &testVal); + if(testVal == GL_FALSE) + { + glDeleteProgram(hReturn); + return (GLuint)NULL; + } + + return hReturn; +} + + +void GLInstancingRenderer::writeTransforms() +{ + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + + char* orgBase = (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE); + + int totalNumInstances= 0; + + for (int k=0;km_numGraphicsInstances; + } + + + + for (int k=0;km_numGraphicsInstances;i++) + { + + int srcIndex=i+gfxObj->m_instanceOffset; + + positions[srcIndex*4] = m_data->m_instance_positions_ptr[srcIndex*4]; + positions[srcIndex*4+1] = m_data->m_instance_positions_ptr[srcIndex*4+1]; + positions[srcIndex*4+2] = m_data->m_instance_positions_ptr[srcIndex*4+2]; + positions[srcIndex*4+3] = m_data->m_instance_positions_ptr[srcIndex*4+3]; + + orientations[srcIndex*4]=m_data->m_instance_quaternion_ptr[srcIndex*4]; + orientations[srcIndex*4+1]=m_data->m_instance_quaternion_ptr[srcIndex*4+1]; + orientations[srcIndex*4+2]=m_data->m_instance_quaternion_ptr[srcIndex*4+2]; + orientations[srcIndex*4+3]=m_data->m_instance_quaternion_ptr[srcIndex*4+3]; + + colors[srcIndex*4]=m_data->m_instance_colors_ptr[srcIndex*4]; + colors[srcIndex*4+1]=m_data->m_instance_colors_ptr[srcIndex*4+1]; + colors[srcIndex*4+2]=m_data->m_instance_colors_ptr[srcIndex*4+2]; + colors[srcIndex*4+3]=m_data->m_instance_colors_ptr[srcIndex*4+3]; + + scaling[srcIndex*3]=m_data->m_instance_scale_ptr[srcIndex*3]; + scaling[srcIndex*3+1]=m_data->m_instance_scale_ptr[srcIndex*3+1]; + scaling[srcIndex*3+2]=m_data->m_instance_scale_ptr[srcIndex*3+2]; + + } + } + + glUnmapBuffer( GL_ARRAY_BUFFER); + //if this glFinish is removed, the animation is not always working/blocks + //@todo: figure out why + glFlush(); +} + +int GLInstancingRenderer::registerGraphicsInstance(int shapeIndex, const float* position, const float* quaternion, const float* color, const float* scaling) +{ + btGraphicsInstance* gfxObj = m_graphicsInstances[shapeIndex]; + + int index = gfxObj->m_numGraphicsInstances + gfxObj->m_instanceOffset; + + + + m_data->m_instance_positions_ptr[index*4]=position[0]; + m_data->m_instance_positions_ptr[index*4+1]=position[1]; + m_data->m_instance_positions_ptr[index*4+2]=position[2]; + m_data->m_instance_positions_ptr[index*4+3]=1; + + m_data->m_instance_quaternion_ptr[index*4]=quaternion[0]; + m_data->m_instance_quaternion_ptr[index*4+1]=quaternion[1]; + m_data->m_instance_quaternion_ptr[index*4+2]=quaternion[2]; + m_data->m_instance_quaternion_ptr[index*4+3]=quaternion[3]; + + m_data->m_instance_colors_ptr[index*4]=color[0]; + m_data->m_instance_colors_ptr[index*4+1]=color[1]; + m_data->m_instance_colors_ptr[index*4+2]=color[2]; + m_data->m_instance_colors_ptr[index*4+3]=color[3]; + + m_data->m_instance_scale_ptr[index*3] = scaling[0]; + m_data->m_instance_scale_ptr[index*3+1] = scaling[1]; + m_data->m_instance_scale_ptr[index*3+2] = scaling[2]; + + gfxObj->m_numGraphicsInstances++; + return gfxObj->m_numGraphicsInstances; +} + + +int GLInstancingRenderer::registerShape(const float* vertices, int numvertices, const int* indices, int numIndices) +{ + btGraphicsInstance* gfxObj = new btGraphicsInstance; + + if (m_graphicsInstances.size()) + { + btGraphicsInstance* prevObj = m_graphicsInstances[m_graphicsInstances.size()-1]; + gfxObj->m_instanceOffset = prevObj->m_instanceOffset + prevObj->m_numGraphicsInstances; + gfxObj->m_vertexArrayOffset = prevObj->m_vertexArrayOffset + prevObj->m_numVertices; + } else + { + gfxObj->m_instanceOffset = 0; + } + + m_graphicsInstances.push_back(gfxObj); + gfxObj->m_numIndices = numIndices; + gfxObj->m_numVertices = numvertices; + + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + char* dest= (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_WRITE_ONLY);//GL_WRITE_ONLY + int vertexStrideInBytes = 9*sizeof(float); + int sz = numvertices*vertexStrideInBytes; + memcpy(dest+vertexStrideInBytes*gfxObj->m_vertexArrayOffset,vertices,sz); + glUnmapBuffer( GL_ARRAY_BUFFER); + + glGenBuffers(1, &gfxObj->m_index_vbo); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gfxObj->m_index_vbo); + int indexBufferSizeInBytes = gfxObj->m_numIndices*sizeof(int); + + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeInBytes, NULL, GL_STATIC_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,0,indexBufferSizeInBytes,indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glGenVertexArrays(1, &gfxObj->m_cube_vao); + glBindVertexArray(gfxObj->m_cube_vao); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + + return m_graphicsInstances.size()-1; +} + + + + +void GLInstancingRenderer::InitShaders() +{ + + int POSITION_BUFFER_SIZE = (MAX_CONVEX_BODIES_CL*sizeof(float)*4); + int ORIENTATION_BUFFER_SIZE = (MAX_CONVEX_BODIES_CL*sizeof(float)*4); + int COLOR_BUFFER_SIZE = (MAX_CONVEX_BODIES_CL*sizeof(float)*4); + int SCALE_BUFFER_SIZE = (MAX_CONVEX_BODIES_CL*sizeof(float)*3); + + + instancingShader = gltLoadShaderPair(vertexShader,fragmentShader); + + glLinkProgram(instancingShader); + glUseProgram(instancingShader); + angle_loc = glGetUniformLocation(instancingShader, "angle"); + ModelViewMatrix = glGetUniformLocation(instancingShader, "ModelViewMatrix"); + ProjectionMatrix = glGetUniformLocation(instancingShader, "ProjectionMatrix"); + uniform_texture_diffuse = glGetUniformLocation(instancingShader, "Diffuse"); + + GLuint offset = 0; + + + glGenBuffers(1, &cube_vbo); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + + + int size = SHAPE_BUFFER_SIZE + POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE+SCALE_BUFFER_SIZE; + VBOsize = size; + + glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW);//GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + +} + + +void myinit() +{ + GLint err = glGetError(); + + // GLfloat light_ambient[] = { btScalar(0.2), btScalar(0.2), btScalar(0.2), btScalar(1.0) }; + GLfloat light_ambient[] = { btScalar(1.0), btScalar(1.2), btScalar(0.2), btScalar(1.0) }; + + GLfloat light_diffuse[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0) }; + GLfloat light_specular[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0 )}; + /* light_position is NOT default value */ + GLfloat light_position0[] = { btScalar(10000.0), btScalar(10000.0), btScalar(10000.0), btScalar(0.0 )}; + GLfloat light_position1[] = { btScalar(-1.0), btScalar(-10.0), btScalar(-1.0), btScalar(0.0) }; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + + glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + + // glShadeModel(GL_FLAT);//GL_SMOOTH); + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glClearColor(float(0.7),float(0.7),float(0.7),float(0)); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + + static bool m_textureenabled = true; + static bool m_textureinitialized = false; + + + if(m_textureenabled) + { + if(!m_textureinitialized) + { + glActiveTexture(GL_TEXTURE0); + + GLubyte* image=new GLubyte[256*256*3]; + for(int y=0;y<256;++y) + { + const int t=y>>5; + GLubyte* pi=image+y*256*3; + for(int x=0;x<256;++x) + { + if (x<2||y<2||x>253||y>253) + { + pi[0]=0; + pi[1]=0; + pi[2]=0; + } else + { + pi[0]=255; + pi[1]=255; + pi[2]=255; + } + + /* + const int s=x>>5; + const GLubyte b=180; + GLubyte c=b+((s+t&1)&1)*(255-b); + pi[0]=c; + pi[1]=c; + pi[2]=c; + */ + + pi+=3; + } + } + + glGenTextures(1,(GLuint*)&m_texturehandle); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + gluBuild2DMipmaps(GL_TEXTURE_2D,3,256,256,GL_RGB,GL_UNSIGNED_BYTE,image); + delete[] image; + m_textureinitialized=true; + } + // glMatrixMode(GL_TEXTURE); + // glLoadIdentity(); + // glMatrixMode(GL_MODELVIEW); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + + } else + { + glDisable(GL_TEXTURE_2D); + } + + glEnable(GL_COLOR_MATERIAL); + + err = glGetError(); + assert(err==GL_NO_ERROR); + + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); +} + +void updateCamera() +{ + + + + btVector3 m_cameraUp(0,1,0); + int m_forwardAxis=2; + + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + + //m_azi+=0.0f; + + btScalar rele = m_ele * btScalar(0.01745329251994329547);// rads per deg + btScalar razi = m_azi * btScalar(0.01745329251994329547);// rads per deg + + + btQuaternion rot(m_cameraUp,razi); + + + btVector3 eyePos(0,0,0); + eyePos[m_forwardAxis] = -m_cameraDistance; + + btVector3 forward(eyePos[0],eyePos[1],eyePos[2]); + if (forward.length2() < SIMD_EPSILON) + { + forward.setValue(1.f,0.f,0.f); + } + btVector3 right = m_cameraUp.cross(forward); + btQuaternion roll(right,-rele); + + eyePos = btMatrix3x3(rot) * btMatrix3x3(roll) * eyePos; + + m_cameraPosition[0] = eyePos.getX(); + m_cameraPosition[1] = eyePos.getY(); + m_cameraPosition[2] = eyePos.getZ(); + m_cameraPosition += m_cameraTargetPosition; + + + float m_frustumZNear=1; + float m_frustumZFar=1000; + + if (m_glutScreenWidth == 0 && m_glutScreenHeight == 0) + return; + + float aspect; + btVector3 extents; + + if (m_glutScreenWidth > m_glutScreenHeight) + { + aspect = m_glutScreenWidth / (float)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + } else + { + aspect = m_glutScreenHeight / (float)m_glutScreenWidth; + extents.setValue(1.0f, aspect*1.f,0); + } + + + if (m_ortho) + { + // reset matrix + glLoadIdentity(); + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + glOrtho(lower.getX(), upper.getX(), lower.getY(), upper.getY(),-1000,1000); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } else + { + if (m_glutScreenWidth > m_glutScreenHeight) + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } else + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(m_cameraPosition[0], m_cameraPosition[1], m_cameraPosition[2], + m_cameraTargetPosition[0], m_cameraTargetPosition[1], m_cameraTargetPosition[2], + m_cameraUp.getX(),m_cameraUp.getY(),m_cameraUp.getZ()); + } + +} + + +void GLInstancingRenderer::RenderScene(void) +{ + BT_PROFILE("GlutDisplayFunc"); + + myinit(); + + updateCamera(); + + //render coordinate system + glBegin(GL_LINES); + glColor3f(1,0,0); + glVertex3f(0,0,0); + glVertex3f(1,0,0); + glColor3f(0,1,0); + glVertex3f(0,0,0); + glVertex3f(0,1,0); + glColor3f(0,0,1); + glVertex3f(0,0,0); + glVertex3f(0,0,1); + glEnd(); + + //do a finish, to make sure timings are clean + // glFinish(); + + + + // glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + + //updatePos(); + +// simulationLoop(); + + //useCPU = true; + + int totalNumInstances = 0; + + for (int i=0;im_numGraphicsInstances; + } + + int curOffset = 0; + + for (int i=0;im_instanceOffset*4*sizeof(float); + + int POSITION_BUFFER_SIZE = (totalNumInstances*sizeof(float)*4); + int ORIENTATION_BUFFER_SIZE = (totalNumInstances*sizeof(float)*4); + int COLOR_BUFFER_SIZE = (totalNumInstances*sizeof(float)*4); + int SCALE_BUFFER_SIZE = (totalNumInstances*sizeof(float)*3); + + glBindVertexArray(gfxObj->m_cube_vao); + + + int vertexStride = 9*sizeof(float); + int vertexBase = gfxObj->m_vertexArrayOffset*vertexStride; + + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid*)vertexBase); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(curOffset*4*sizeof(float)+SHAPE_BUFFER_SIZE)); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(curOffset*4*sizeof(float)+SHAPE_BUFFER_SIZE+POSITION_BUFFER_SIZE)); + int uvoffset = 7*sizeof(float)+vertexBase; + int normaloffset = 4*sizeof(float)+vertexBase; + + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)uvoffset); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)normaloffset); + glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(curOffset*4*sizeof(float)+SHAPE_BUFFER_SIZE+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE)); + glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(curOffset*3*sizeof(float)+SHAPE_BUFFER_SIZE+POSITION_BUFFER_SIZE+ORIENTATION_BUFFER_SIZE+COLOR_BUFFER_SIZE)); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + glEnableVertexAttribArray(6); + + glVertexAttribDivisor(0, 0); + glVertexAttribDivisor(1, 1); + glVertexAttribDivisor(2, 1); + glVertexAttribDivisor(3, 0); + glVertexAttribDivisor(4, 0); + glVertexAttribDivisor(5, 1); + glVertexAttribDivisor(6, 1); + + glUseProgram(instancingShader); + glUniform1f(angle_loc, 0); + GLfloat pm[16]; + glGetFloatv(GL_PROJECTION_MATRIX, pm); + glUniformMatrix4fv(ProjectionMatrix, 1, false, &pm[0]); + + GLfloat mvm[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mvm); + glUniformMatrix4fv(ModelViewMatrix, 1, false, &mvm[0]); + + glUniform1i(uniform_texture_diffuse, 0); + + glFlush(); + + if (gfxObj->m_numGraphicsInstances) + { + int indexCount = gfxObj->m_numIndices; + int indexOffset = 0; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gfxObj->m_index_vbo); + { + BT_PROFILE("glDrawElementsInstanced"); + glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void*)indexOffset, gfxObj->m_numGraphicsInstances); + } + } + curOffset+= gfxObj->m_numGraphicsInstances; + } + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + + GLint err = glGetError(); + assert(err==GL_NO_ERROR); +} + + +void GLInstancingRenderer::CleanupShaders() +{ + + delete []m_data->m_instance_positions_ptr; + delete []m_data->m_instance_quaternion_ptr; + delete []m_data->m_instance_colors_ptr; + delete []m_data->m_instance_scale_ptr; +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.h new file mode 100644 index 000000000..b5924385b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GLInstancingRenderer.h @@ -0,0 +1,45 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef GL_INSTANCING_RENDERER_H +#define GL_INSTANCING_RENDERER_H + +#include "LinearMath/btAlignedObjectArray.h" + +class GLInstancingRenderer +{ + + btAlignedObjectArray m_graphicsInstances; + + struct InternalDataRenderer* m_data; + +public: + GLInstancingRenderer(); + virtual ~GLInstancingRenderer(); + + void InitShaders(); + void RenderScene(void); + void CleanupShaders(); + + ///vertices must be in the format x,y,z, nx,ny,nz, u,v + int registerShape(const float* vertices, int numvertices, const int* indices, int numIndices); + + ///position x,y,z, quaternion x,y,z,w, color r,g,b,a, scaling x,y,z + int registerGraphicsInstance(int shapeIndex, const float* position, const float* quaternion, const float* color, const float* scaling); + + void writeTransforms(); +}; + +#endif //GL_INSTANCING_RENDERER_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.cpp new file mode 100644 index 000000000..3cb02c278 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.cpp @@ -0,0 +1,107 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + + +#include +#include "GlutRenderer.h" +#include + + +GlutRenderer* GlutRenderer::gDemoApplication; + + + +void GlutRenderer::runMainLoop() +{ + glutMainLoop(); + +} + + +static void glutKeyboardCallback(unsigned char key, int x, int y) { GlutRenderer::gDemoApplication->keyboardCallback(key,x,y); } +static void glutKeyboardUpCallback(unsigned char key, int x, int y){ GlutRenderer::gDemoApplication->keyboardUpCallback(key,x,y);} +static void glutSpecialKeyboardCallback(int key, int x, int y){ GlutRenderer::gDemoApplication->specialKeyboard(key,x,y);} +static void glutSpecialKeyboardUpCallback(int key, int x, int y){ GlutRenderer::gDemoApplication->specialKeyboardUp(key,x,y);} +static void glutReshapeCallback(int w, int h){ GlutRenderer::gDemoApplication->resize(w,h);} +static void glutIdleCallback(){ glutPostRedisplay (); } +static void glutMouseFuncCallback(int button, int state, int x, int y){ GlutRenderer::gDemoApplication->mouseFunc(button,state,x,y);} +static void glutMotionFuncCallback(int x,int y){ GlutRenderer::gDemoApplication->mouseMotionFunc(x,y);} +static void glutDisplayCallback(void){ GlutRenderer::gDemoApplication->displayCallback();} + + +void GlutRenderer::resize(int width, int height) +{ + m_glutScreenWidth = width; + m_glutScreenHeight = height; +} + +void GlutRenderer::mouseFunc(int button, int state, int x, int y) +{ +} +void GlutRenderer::mouseMotionFunc(int x,int y) +{ +} + +void GlutRenderer::renderScene() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glutSwapBuffers(); + glutPostRedisplay(); + + GLint err = glGetError(); + assert(err==GL_NO_ERROR); +} + +void GlutRenderer::displayCallback() +{ + updateScene(); + + renderScene(); +} + +GlutRenderer::GlutRenderer(int argc, char* argv[]) +{ + glutInit(&argc, argv); + gDemoApplication = this; +} + +void GlutRenderer::initGraphics(int width, int height) +{ + m_glutScreenWidth = width; + m_glutScreenHeight = height; + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); + + glutInitWindowSize(m_glutScreenWidth, m_glutScreenHeight); + glutCreateWindow("GPU rigid body pipeline2"); + glutKeyboardFunc(glutKeyboardCallback); + glutKeyboardUpFunc(glutKeyboardUpCallback); + glutSpecialFunc(glutSpecialKeyboardCallback); + glutSpecialUpFunc(glutSpecialKeyboardUpCallback); + glutReshapeFunc(glutReshapeCallback); + glutIdleFunc(glutIdleCallback); + glutMouseFunc(glutMouseFuncCallback); + glutPassiveMotionFunc(glutMotionFuncCallback); + glutMotionFunc(glutMotionFuncCallback); + glutDisplayFunc( glutDisplayCallback ); + + GLenum err = glewInit(); + if (GLEW_OK != err) + { + printf("Error: %s\n", glewGetErrorString(err)); + } + + glClearColor(0.6f,0.6f,1.f,1.f); +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.h new file mode 100644 index 000000000..3596ad490 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/GlutRenderer.h @@ -0,0 +1,59 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +#ifndef GLUT_RENDERER_H +#define GLUT_RENDERER_H + +#include "btGlutInclude.h" +#include "LinearMath/btVector3.h" + +struct GlutRenderer +{ + static GlutRenderer* gDemoApplication; + int m_glutScreenWidth; + int m_glutScreenHeight; + + btVector3 m_cameraPosition; + btVector3 m_cameraTargetPosition; + btScalar m_cameraDistance; + btVector3 m_cameraUp; + float m_azimuth; + float m_elevation; + + + GlutRenderer(int argc, char* argv[]); + + virtual void initGraphics(int width, int height); + virtual void cleanup() {} + + void runMainLoop(); + + virtual void updateScene(){}; + + virtual void renderScene(); + + virtual void keyboardCallback(unsigned char key, int x, int y) {}; + virtual void keyboardUpCallback(unsigned char key, int x, int y) {} + virtual void specialKeyboard(int key, int x, int y){} + virtual void specialKeyboardUp(int key, int x, int y){} + virtual void resize(int w, int h); + virtual void mouseFunc(int button, int state, int x, int y); + virtual void mouseMotionFunc(int x,int y); + virtual void displayCallback(); + + +}; + +#endif //GLUT_RENDERER_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/NVIDIA/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/NVIDIA/premake4.lua new file mode 100644 index 000000000..8ccc57e5d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/NVIDIA/premake4.lua @@ -0,0 +1,64 @@ + + hasCL = findOpenCL_NVIDIA() + + if (hasCL) then + + project "OpenCL_gpu_rigidbody_pipeline2_NVIDIA" + + initOpenCL_NVIDIA() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + initOpenGL() + initGlew() + + + includedirs { + "../../primitives", + "../../../bullet2" + } + + files { + "../main.cpp", + "../CLPhysicsDemo.cpp", + "../CLPhysicsDemo.h", + "../GLInstancingRenderer.cpp", + "../GLInstancingRenderer.h", + "../GlutRenderer.cpp", + "../GlutRenderer.h", + "../Win32OpenGLRenderManager.cpp", + "../Win32OpenGLRenderManager.h", + "../../gpu_rigidbody_pipeline/btConvexUtility.cpp", + "../../gpu_rigidbody_pipeline/btConvexUtility.h", + "../../gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.cpp", + "../../gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.cpp", + "../../../dynamics/basic_demo/ConvexHeightFieldShape.h", + "../../../bullet2/LinearMath/btConvexHullComputer.cpp", + "../../../bullet2/LinearMath/btConvexHullComputer.h", + "../../broadphase_benchmark/findPairsOpenCL.cpp", + "../../broadphase_benchmark/findPairsOpenCL.h", + "../../broadphase_benchmark/btGridBroadphaseCL.cpp", + "../../broadphase_benchmark/btGridBroadphaseCL.h", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.cpp", + "../../3dGridBroadphase/Shared/bt3dGridBroadphaseOCL.h", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.cpp", + "../../3dGridBroadphase/Shared/btGpu3DGridBroadphase.h", + "../../../bullet2/LinearMath/btAlignedAllocator.cpp", + "../../../bullet2/LinearMath/btQuickprof.cpp", + "../../../bullet2/LinearMath/btQuickprof.h", + "../../../bullet2/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp", + "../../../bullet2/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp", + "../../../bullet2/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/OpenGLInclude.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/OpenGLInclude.h new file mode 100644 index 000000000..2b3060bd3 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/OpenGLInclude.h @@ -0,0 +1,41 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + + +#ifndef __OPENGL_INCLUDE_H +#define __OPENGL_INCLUDE_H + +#include + +//think different +#if defined(__APPLE__) && !defined (VMDMESA) +#include +#include +#include +#else + + +#ifdef _WINDOWS +#include +#include +#include +#else +#include + +#endif //_WINDOWS +#endif //APPLE + +#endif //__OPENGL_INCLUDE_H + diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/ShapeData.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/ShapeData.h new file mode 100644 index 000000000..e77affe61 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/ShapeData.h @@ -0,0 +1,210 @@ +#ifndef SHAPE_DATA_H +#define SHAPE_DATA_H + +static float barrel_vertices[] = { +0.0f,-0.5f,0.0f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.282362f,-0.5f,-0.205148f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.349018f,-0.5f,0.0f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.107853f,-0.5f,-0.331936f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +-0.107853f,-0.5f,-0.331936f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.107853f,-0.5f,-0.331936f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +-0.282362f,-0.5f,-0.205148f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +-0.349018f,-0.5f,0.0f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +-0.282362f,-0.5f,0.205148f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +-0.107853f,-0.5f,0.331936f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.107853f,-0.5f,0.331936f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.282362f,-0.5f,0.205148f, 1.0f, 0.0f,-1.0f,0.0f, 0.5f, 0.5f, +0.0f,0.5f,0.0f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.349018f,0.5f,0.0f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.282362f,0.5f,-0.205148f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.107853f,0.5f,-0.331936f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.107853f,0.5f,-0.331936f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +-0.107853f,0.5f,-0.331936f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +-0.282362f,0.5f,-0.205148f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +-0.349018f,0.5f,0.0f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +-0.282362f,0.5f,0.205148f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +-0.107853f,0.5f,0.331936f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.107853f,0.5f,0.331936f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.282362f,0.5f,0.205148f, 1.0f, 0.0f,1.0f,0.0f, 0.5f, 0.5f, +0.349018f,-0.5f,0.0f, 1.0f, 0.957307f,-0.289072f,0.0f, 0.5f, 0.5f, +0.404509f,0.0f,-0.293893f, 1.0f, 0.809017f,0.0f,-0.587785f, 0.5f, 0.5f, +0.5f,0.0f,0.0f, 1.0f, 1.0f,0.0f,0.0f, 0.5f, 0.5f, +0.282362f,-0.5f,-0.205148f, 1.0f, 0.774478f,-0.289072f,-0.562691f, 0.5f, 0.5f, +0.154508f,0.0f,-0.475528f, 1.0f, 0.309017f,0.0f,-0.951057f, 0.5f, 0.5f, +0.107853f,-0.5f,-0.331936f, 1.0f, 0.295824f,-0.289072f,-0.910453f, 0.5f, 0.5f, +0.107853f,-0.5f,-0.331936f, 1.0f, 0.295824f,-0.289072f,-0.910453f, 0.5f, 0.5f, +-0.154509f,0.0f,-0.475528f, 1.0f, -0.309017f,0.0f,-0.951057f, 0.5f, 0.5f, +0.154508f,0.0f,-0.475528f, 1.0f, 0.309017f,0.0f,-0.951057f, 0.5f, 0.5f, +-0.107853f,-0.5f,-0.331936f, 1.0f, -0.295824f,-0.289072f,-0.910453f, 0.5f, 0.5f, +-0.404509f,0.0f,-0.293893f, 1.0f, -0.809017f,0.0f,-0.587785f, 0.5f, 0.5f, +-0.282362f,-0.5f,-0.205148f, 1.0f, -0.774478f,-0.289072f,-0.562691f, 0.5f, 0.5f, +-0.5f,0.0f,0.0f, 1.0f, -1.0f,0.0f,0.0f, 0.5f, 0.5f, +-0.349018f,-0.5f,0.0f, 1.0f, -0.957307f,-0.289072f,0.0f, 0.5f, 0.5f, +-0.404508f,0.0f,0.293893f, 1.0f, -0.809017f,0.0f,0.587785f, 0.5f, 0.5f, +-0.282362f,-0.5f,0.205148f, 1.0f, -0.774478f,-0.289072f,0.562691f, 0.5f, 0.5f, +-0.154509f,0.0f,0.475528f, 1.0f, -0.309017f,0.0f,0.951056f, 0.5f, 0.5f, +-0.107853f,-0.5f,0.331936f, 1.0f, -0.295824f,-0.289072f,0.910453f, 0.5f, 0.5f, +0.154509f,0.0f,0.475528f, 1.0f, 0.309017f,0.0f,0.951056f, 0.5f, 0.5f, +0.107853f,-0.5f,0.331936f, 1.0f, 0.295824f,-0.289072f,0.910453f, 0.5f, 0.5f, +0.404509f,0.0f,0.293892f, 1.0f, 0.809017f,0.0f,0.587785f, 0.5f, 0.5f, +0.282362f,-0.5f,0.205148f, 1.0f, 0.774478f,-0.289072f,0.562691f, 0.5f, 0.5f, +0.282362f,0.5f,-0.205148f, 1.0f, 0.774478f,0.289072f,-0.562691f, 0.5f, 0.5f, +0.349018f,0.5f,0.0f, 1.0f, 0.957307f,0.289072f,0.0f, 0.5f, 0.5f, +0.107853f,0.5f,-0.331936f, 1.0f, 0.295824f,0.289072f,-0.910453f, 0.5f, 0.5f, +-0.107853f,0.5f,-0.331936f, 1.0f, -0.295824f,0.289072f,-0.910453f, 0.5f, 0.5f, +0.107853f,0.5f,-0.331936f, 1.0f, 0.295824f,0.289072f,-0.910453f, 0.5f, 0.5f, +-0.282362f,0.5f,-0.205148f, 1.0f, -0.774478f,0.289072f,-0.562691f, 0.5f, 0.5f, +-0.349018f,0.5f,0.0f, 1.0f, -0.957307f,0.289072f,0.0f, 0.5f, 0.5f, +-0.282362f,0.5f,0.205148f, 1.0f, -0.774478f,0.289072f,0.562691f, 0.5f, 0.5f, +-0.107853f,0.5f,0.331936f, 1.0f, -0.295824f,0.289072f,0.910453f, 0.5f, 0.5f, +0.107853f,0.5f,0.331936f, 1.0f, 0.295824f,0.289072f,0.910453f, 0.5f, 0.5f, +0.282362f,0.5f,0.205148f, 1.0f, 0.774478f,0.289072f,0.562691f, 0.5f, 0.5f +}; + + + +static int barrel_indices[] = { +0,1,2, +0,3,1, +0,4,5, +0,6,4, +0,7,6, +0,8,7, +0,9,8, +0,10,9, +0,11,10, +0,2,11, +12,13,14, +12,14,15, +12,16,17, +12,17,18, +12,18,19, +12,19,20, +12,20,21, +12,21,22, +12,22,23, +12,23,13, +24,25,26, +24,27,25, +27,28,25, +27,29,28, +30,31,32, +30,33,31, +33,34,31, +33,35,34, +35,36,34, +35,37,36, +37,38,36, +37,39,38, +39,40,38, +39,41,40, +41,42,40, +41,43,42, +43,44,42, +43,45,44, +45,26,44, +45,24,26, +26,46,47, +26,25,46, +25,48,46, +25,28,48, +32,49,50, +32,31,49, +31,51,49, +31,34,51, +34,52,51, +34,36,52, +36,53,52, +36,38,53, +38,54,53, +38,40,54, +40,55,54, +40,42,55, +42,56,55, +42,44,56, +44,47,56, +44,26,47, +}; + + +///position xyz, unused w, normal, uv +static const float cube_vertices[] = +{ + -0.5f, -0.5f, 0.5f, 0.0f, 0,0,1, 0,0,//0 + 0.5f, -0.5f, 0.5f, 0.0f, 0,0,1, 1,0,//1 + 0.5f, 0.5f, 0.5f, 0.0f, 0,0,1, 1,1,//2 + -0.5f, 0.5f, 0.5f, 0.0f, 0,0,1, 0,1 ,//3 + + -0.5f, -0.5f, -0.5f, 0.5f, 0,0,-1, 0,0,//4 + 0.5f, -0.5f, -0.5f, 0.5f, 0,0,-1, 1,0,//5 + 0.5f, 0.5f, -0.5f, 0.5f, 0,0,-1, 1,1,//6 + -0.5f, 0.5f, -0.5f, 0.5f, 0,0,-1, 0,1,//7 + + -0.5f, -0.5f, -0.5f, 0.5f, -1,0,0, 0,0, + -0.5f, 0.5f, -0.5f, 0.5f, -1,0,0, 1,0, + -0.5f, 0.5f, 0.5f, 0.5f, -1,0,0, 1,1, + -0.5f, -0.5f, 0.5f, 0.5f, -1,0,0, 0,1, + + 0.5f, -0.5f, -0.5f, 0.5f, 1,0,0, 0,0, + 0.5f, 0.5f, -0.5f, 0.5f, 1,0,0, 1,0, + 0.5f, 0.5f, 0.5f, 0.5f, 1,0,0, 1,1, + 0.5f, -0.5f, 0.5f, 0.5f, 1,0,0, 0,1, + + -0.5f, -0.5f, -0.5f, 0.5f, 0,-1,0, 0,0, + -0.5f, -0.5f, 0.5f, 0.5f, 0,-1,0, 1,0, + 0.5f, -0.5f, 0.5f, 0.5f, 0,-1,0, 1,1, + 0.5f,-0.5f, -0.5f, 0.5f, 0,-1,0, 0,1, + + -0.5f, 0.5f, -0.5f, 0.5f, 0,1,0, 0,0, + -0.5f, 0.5f, 0.5f, 0.5f, 0,1,0, 1,0, + 0.5f, 0.5f, 0.5f, 0.5f, 0,1,0, 1,1, + 0.5f,0.5f, -0.5f, 0.5f, 0,1,0, 0,1, +}; + + +///position xyz, unused w, normal, uv +static const float cube_vertices2[] = +{ + -1.5f, -0.5f, 0.5f, 0.0f, 0,0,1, 0,0,//0 + 1.5f, -0.5f, 0.5f, 0.0f, 0,0,1, 1,0,//1 + 1.5f, 0.5f, 0.5f, 0.0f, 0,0,1, 1,1,//2 + -1.5f, 0.5f, 0.5f, 0.0f, 0,0,1, 0,1 ,//3 + + -1.5f, -0.5f, -0.5f, 0.5f, 0,0,-1, 0,0,//4 + 1.5f, -0.5f, -0.5f, 0.5f, 0,0,-1, 1,0,//5 + 1.5f, 0.5f, -0.5f, 0.5f, 0,0,-1, 1,1,//6 + -1.5f, 0.5f, -0.5f, 0.5f, 0,0,-1, 0,1,//7 + + -1.5f, -0.5f, -0.5f, 0.5f, -1,0,0, 0,0, + -1.5f, 0.5f, -0.5f, 0.5f, -1,0,0, 1,0, + -1.5f, 0.5f, 0.5f, 0.5f, -1,0,0, 1,1, + -1.5f, -0.5f, 0.5f, 0.5f, -1,0,0, 0,1, + + 1.5f, -0.5f, -0.5f, 0.5f, 1,0,0, 0,0, + 1.5f, 0.5f, -0.5f, 0.5f, 1,0,0, 1,0, + 1.5f, 0.5f, 0.5f, 0.5f, 1,0,0, 1,1, + 1.5f, -0.5f, 0.5f, 0.5f, 1,0,0, 0,1, + + -1.5f, -0.5f, -0.5f, 0.5f, 0,-1,0, 0,0, + -1.5f, -0.5f, 0.5f, 0.5f, 0,-1,0, 1,0, + 1.5f, -0.5f, 0.5f, 0.5f, 0,-1,0, 1,1, + 1.5f, -0.5f, -0.5f, 0.5f, 0,-1,0, 0,1, + + -1.5f, 0.5f, -0.5f, 0.5f, 0,1,0, 0,0, + -1.5f, 0.5f, 0.5f, 0.5f, 0,1,0, 1,0, + 1.5f, 0.5f, 0.5f, 0.5f, 0,1,0, 1,1, + 1.5f, 0.5f, -0.5f, 0.5f, 0,1,0, 0,1, +}; + + +static const int cube_indices[]= +{ + 0,1,2,0,2,3,//ground face + 4,5,6,4,6,7,//top face + 8,9,10,8,10,11, + 12,13,14,12,14,15, + 16,17,18,16,18,19, + 20,21,22,20,22,23 +}; + +#endif //SHAPE_DATA_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.cpp new file mode 100644 index 000000000..c3b9f250a --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.cpp @@ -0,0 +1,465 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + + +#include "Win32OpenGLRenderManager.h" + +#include +#include + +static InternalData2* sData = 0; + +struct InternalData2 +{ + HWND m_hWnd;; + int m_width; + int m_height; + HDC m_hDC; + HGLRC m_hRC; + bool m_OpenGLInitialized; + int m_oldScreenWidth; + int m_oldHeight; + int m_oldBitsPerPel; + bool m_quit; + + + InternalData2() + { + m_hWnd = 0; + m_width = 0; + m_height = 0; + m_hDC = 0; + m_hRC = 0; + m_OpenGLInitialized = false; + m_oldScreenWidth = 0; + m_oldHeight = 0; + m_oldBitsPerPel = 0; + m_quit = false; + } +}; + + +void Win32OpenGLWindow::enableOpenGL() +{ + + + + PIXELFORMATDESCRIPTOR pfd; + int format; + + // get the device context (DC) + m_data->m_hDC = GetDC( m_data->m_hWnd ); + + // set the pixel format for the DC + ZeroMemory( &pfd, sizeof( pfd ) ); + pfd.nSize = sizeof( pfd ); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + pfd.cDepthBits = 16; + pfd.cStencilBits = 1; + pfd.iLayerType = PFD_MAIN_PLANE; + format = ChoosePixelFormat( m_data->m_hDC, &pfd ); + SetPixelFormat( m_data->m_hDC, format, &pfd ); + + // create and enable the render context (RC) + m_data->m_hRC = wglCreateContext( m_data->m_hDC ); + wglMakeCurrent( m_data->m_hDC, m_data->m_hRC ); + m_data->m_OpenGLInitialized = true; + + +} + + +void Win32OpenGLWindow::disableOpenGL() +{ + m_data->m_OpenGLInitialized = false; + + wglMakeCurrent( NULL, NULL ); + wglDeleteContext( m_data->m_hRC ); + ReleaseDC( m_data->m_hWnd, m_data->m_hDC ); +} + +void Win32OpenGLWindow::pumpMessage() +{ + MSG msg; + // check for messages + if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + + // handle or dispatch messages + if ( msg.message == WM_QUIT ) + { + m_data->m_quit = TRUE; + } + else + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + +// gDemoApplication->displayCallback(); + + + }; +} + + + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + BeginPaint(hWnd, &ps); + EndPaint(hWnd, &ps); + } + return 0; + + case WM_ERASEBKGND: + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_KEYDOWN: + { + switch ( wParam ) + { + case 'Q': + case VK_ESCAPE: + { + PostQuitMessage(0); + } + return 0; + } + break; + } + + case WM_SIZE: // Size Action Has Taken Place + + switch (wParam) // Evaluate Size Action + { + case SIZE_MINIMIZED: // Was Window Minimized? + return 0; // Return + + case SIZE_MAXIMIZED: // Was Window Maximized? + + sData->m_width = LOWORD (lParam); + sData->m_height = HIWORD (lParam); + //if (sOpenGLInitialized) + //{ + // //gDemoApplication->reshape(sWidth,sHeight); + //} + glViewport(0, 0, sData->m_width, sData->m_height); + return 0; // Return + + case SIZE_RESTORED: // Was Window Restored? + sData->m_width = LOWORD (lParam); + sData->m_height = HIWORD (lParam); + //if (sOpenGLInitialized) + //{ + // gDemoApplication->reshape(sWidth,sHeight); + //} + glViewport(0, 0, sData->m_width, sData->m_height); + return 0; // Return + } + break; + + default:{ + + } + }; + + return DefWindowProc(hWnd, message, wParam, lParam); +} + + + + +void Win32OpenGLWindow::init(int width,int height, bool fullscreen,int colorBitsPerPixel, void* windowHandle) +{ + // get handle to exe file + HINSTANCE hInstance = GetModuleHandle(0); + + // create the window if we need to and we do not use the null device + if (!windowHandle) + { + const char* ClassName = "DeviceWin32"; + + // Register Class + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon( NULL, IDI_APPLICATION ); //(HICON)LoadImage(hInstance, "bullet_ico.ico", IMAGE_ICON, 0,0, LR_LOADTRANSPARENT);//LR_LOADFROMFILE); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = ClassName; + wcex.hIconSm = 0; + + // if there is an icon, load it + wcex.hIcon = (HICON)LoadImage(hInstance, "irrlicht.ico", IMAGE_ICON, 0,0, LR_LOADFROMFILE); + + RegisterClassEx(&wcex); + + // calculate client size + + RECT clientSize; + clientSize.top = 0; + clientSize.left = 0; + clientSize.right = width; + clientSize.bottom = height; + + DWORD style = WS_POPUP; + + if (!fullscreen) + style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + + AdjustWindowRect(&clientSize, style, FALSE); + + m_data->m_width = clientSize.right - clientSize.left; + m_data->m_height = clientSize.bottom - clientSize.top; + + int windowLeft = (GetSystemMetrics(SM_CXSCREEN) - m_data->m_width) / 2; + int windowTop = (GetSystemMetrics(SM_CYSCREEN) - m_data->m_height) / 2; + + if (fullscreen) + { + windowLeft = 0; + windowTop = 0; + } + + // create window + + m_data->m_hWnd = CreateWindow( ClassName, "", style, windowLeft, windowTop, + m_data->m_width, m_data->m_height, NULL, NULL, hInstance, NULL); + + ShowWindow(m_data->m_hWnd, SW_SHOW); + UpdateWindow(m_data->m_hWnd); + + MoveWindow(m_data->m_hWnd, windowLeft, windowTop, m_data->m_width, m_data->m_height, TRUE); + } + else if (windowHandle) + { + // attach external window + m_data->m_hWnd = static_cast(windowHandle); + RECT r; + GetWindowRect(m_data->m_hWnd, &r); + m_data->m_width = r.right - r.left; + m_data->m_height = r.bottom - r.top; + //sFullScreen = false; + //sExternalWindow = true; + } + + + if (fullscreen) + { + DEVMODE dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + // use default values from current setting + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm); + m_data->m_oldScreenWidth = dm.dmPelsWidth; + m_data->m_oldHeight = dm.dmPelsHeight; + m_data->m_oldBitsPerPel = dm.dmBitsPerPel; + + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + if (colorBitsPerPixel) + { + dm.dmBitsPerPel = colorBitsPerPixel; + } + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + + LONG res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN); + if (res != DISP_CHANGE_SUCCESSFUL) + { // try again without forcing display frequency + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN); + } + + } + + //VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, this); + enableOpenGL(); + + + const wchar_t* text= L"OpenCL rigid body demo"; + + DWORD dwResult; + +#ifdef _WIN64 + SetWindowTextW(m_data->m_hWnd, text); +#else + SendMessageTimeoutW(m_data->m_hWnd, WM_SETTEXT, 0, + reinterpret_cast(text), + SMTO_ABORTIFHUNG, 2000, &dwResult); +#endif + + +} + + +void Win32OpenGLWindow::switchFullScreen(bool fullscreen,int width,int height,int colorBitsPerPixel) +{ + LONG res; + DEVMODE dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + // use default values from current setting + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm); + + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + + if (fullscreen && !m_data->m_oldScreenWidth) + { + m_data->m_oldScreenWidth = dm.dmPelsWidth; + m_data->m_oldHeight = dm.dmPelsHeight; + m_data->m_oldBitsPerPel = dm.dmBitsPerPel; + + if (width && height) + { + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + } else + { + dm.dmPelsWidth = m_data->m_width; + dm.dmPelsHeight = m_data->m_height; + } + if (colorBitsPerPixel) + { + dm.dmBitsPerPel = colorBitsPerPixel; + } + } else + { + if (m_data->m_oldScreenWidth) + { + dm.dmPelsWidth = m_data->m_oldScreenWidth; + dm.dmPelsHeight= m_data->m_oldHeight; + dm.dmBitsPerPel = m_data->m_oldBitsPerPel; + } + } + + if (fullscreen) + { + res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN); + } else + { + res = ChangeDisplaySettings(&dm, 0); + } +} + + + +Win32OpenGLWindow::Win32OpenGLWindow() +{ + m_data = new InternalData2(); + sData = m_data; +} + +Win32OpenGLWindow::~Win32OpenGLWindow() +{ + delete m_data; + sData = 0; +} + +void Win32OpenGLWindow::init() +{ + init(640,480,false); +} + + +void Win32OpenGLWindow::exit() +{ + disableOpenGL(); + DestroyWindow(this->m_data->m_hWnd); +} + + + + + +void Win32OpenGLWindow::startRendering() +{ + pumpMessage(); + + //glClearColor(1.f,0.f,0.f,1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //clear buffers + + //glCullFace(GL_BACK); + //glFrontFace(GL_CCW); + glEnable(GL_DEPTH_TEST); + + + float aspect; + //btVector3 extents; + + if (m_data->m_width > m_data->m_height) + { + aspect = (float)m_data->m_width / (float)m_data->m_height; + //extents.setValue(aspect * 1.0f, 1.0f,0); + } else + { + aspect = (float)m_data->m_height / (float)m_data->m_width; + //extents.setValue(1.0f, aspect*1.f,0); + } + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + if (m_data->m_width > m_data->m_height) + { + glFrustum (-aspect, aspect, -1.0, 1.0, 1.0, 10000.0); + } else + { + glFrustum (-1.0, 1.0, -aspect, aspect, 1.0, 10000.0); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + +} + + +void Win32OpenGLWindow::renderAllObjects() +{ +} + +void Win32OpenGLWindow::endRendering() +{ + SwapBuffers( m_data->m_hDC ); +} + +float Win32OpenGLWindow::getTimeInSeconds() +{ + return 0.f; +} + +void Win32OpenGLWindow::setDebugMessage(int x,int y,const char* message) +{ +} + +bool Win32OpenGLWindow::requestedExit() +{ + return m_data->m_quit; +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.h b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.h new file mode 100644 index 000000000..861c6f5b6 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/Win32OpenGLRenderManager.h @@ -0,0 +1,70 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + + +#ifndef _WIN32_OPENGL_RENDER_MANAGER_H +#define _WIN32_OPENGL_RENDER_MANAGER_H + + +#define RM_DECLARE_HANDLE(name) typedef struct name##__ { int unused; } *name + +RM_DECLARE_HANDLE(RenderObjectHandle); + +struct InternalData2; + +class Win32OpenGLWindow +{ + protected: + + struct InternalData2* m_data; + + void enableOpenGL(); + + void disableOpenGL(); + + void pumpMessage(); + + + +public: + + Win32OpenGLWindow(); + + virtual ~Win32OpenGLWindow(); + + virtual void init(); //default implementation uses default settings for width/height/fullscreen + + void init(int width,int height, bool fullscreen=false, int colorBitsPerPixel=0, void* windowHandle=0); + + void switchFullScreen(bool fullscreen,int width=0,int height=0,int colorBitsPerPixel=0); + + virtual void exit(); + + + virtual void startRendering(); + + virtual void renderAllObjects(); + + virtual void endRendering(); + + virtual float getTimeInSeconds(); + + virtual void setDebugMessage(int x,int y,const char* message); + + virtual bool requestedExit(); + +}; + +#endif //_WIN32_OPENGL_RENDER_MANAGER_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/main.cpp new file mode 100644 index 000000000..b4de1789f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/main.cpp @@ -0,0 +1,224 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Erwin Coumans + +// +//#include "vld.h" +#include + +#include "GLInstancingRenderer.h" + + +#include "GLInstancingRenderer.h" +#include "../opengl_interop/btOpenCLGLInteropBuffer.h" +#include "Win32OpenGLRenderManager.h" +#include "CLPhysicsDemo.h" +#include "../broadphase_benchmark/btGridBroadphaseCl.h" +#include "../../opencl/gpu_rigidbody_pipeline/btGpuNarrowPhaseAndSolver.h" +#include "ShapeData.h" +#include "LinearMath/btQuickprof.h" + +int NUM_OBJECTS_X = 32; +int NUM_OBJECTS_Y = 24; +int NUM_OBJECTS_Z = 32; + + +float X_GAP = 2.f; +float Y_GAP = 2.f; +float Z_GAP = 2.f; + +extern int numPairsOut; + + +void createScene(GLInstancingRenderer& renderer,CLPhysicsDemo& physicsSim) +{ + int strideInBytes = sizeof(float)*9; + + int barrelShapeIndex = -1; + int cubeShapeIndex = -1; + + float position[4]={0,0,0,0}; + float orn[4] = {0,0,0,1}; + float color[4] = {1,1,1,1}; + int index=0; +#if 1 + { + int numVertices = sizeof(barrel_vertices)/strideInBytes; + int numIndices = sizeof(barrel_indices)/sizeof(int); + barrelShapeIndex = renderer.registerShape(&barrel_vertices[0],numVertices,barrel_indices,numIndices); + } + + + float barrelScaling[4] = {2,2,2,1}; + + + int barrelCollisionShapeIndex = physicsSim.registerCollisionShape(&barrel_vertices[0],strideInBytes, sizeof(barrel_vertices)/strideInBytes,&barrelScaling[0]); + + + + for (int i=0;iinit(1024,768); + GLenum err = glewInit(); + window->startRendering(); + window->endRendering(); + + GLInstancingRenderer render; + + + + + + CLPhysicsDemo demo(window); + + bool useInterop = true; + demo.init(-1,-1,useInterop); + + render.InitShaders(); + + if (useInterop) + demo.setupInterop(); + + createScene(render, demo); + + + printf("num objects = %d\n", NUM_OBJECTS_X*NUM_OBJECTS_Y*NUM_OBJECTS_Z); + + + render.writeTransforms(); + + + while (!window->requestedExit()) + { + CProfileManager::Reset(); + + demo.stepSimulation(); + + + window->startRendering(); + render.RenderScene(); + window->endRendering(); + + CProfileManager::Increment_Frame_Counter(); + + static bool printStats = true; + + if (printStats) + { + static int count = 10; + count--; + if (count<0) + { + CProfileManager::dumpAll(); + //printf("total broadphase pairs= %d\n", gFpIO.m_numOverlap); + printf("numPairsOut (culled) = %d\n", numPairsOut); + printStats = false; + } + } + + } + + + demo.cleanup(); + + render.CleanupShaders(); + window->exit(); + delete window; + + + + return 0; +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/premake4.lua new file mode 100644 index 000000000..e78f276dc --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/gpu_rigidbody_pipeline2/premake4.lua @@ -0,0 +1,5 @@ + +include "AMD" +-- include "Intel" +-- include "NVIDIA" + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/integration/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/integration/AMD/premake4.lua new file mode 100644 index 000000000..a8b029e81 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/integration/AMD/premake4.lua @@ -0,0 +1,34 @@ + + hasCL = findOpenCL_AMD() + + if (hasCL) then + + project "OpenCL_integration_AMD" + + initOpenCL_AMD() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + initOpenGL() + initGlut() + initGlew() + + includedirs { + "../../../rendering/BulletMath", + "../../primitives" + } + + files { + "../main.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/integration/Intel/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/integration/Intel/premake4.lua new file mode 100644 index 000000000..27c08660d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/integration/Intel/premake4.lua @@ -0,0 +1,36 @@ + + hasCL = findOpenCL_Intel() + + if (hasCL) then + + project "OpenCL_integration_Intel" + + initOpenCL_Intel() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + + initOpenGL() + initGlut() + initGlew() + + + includedirs { + "../../../rendering/BulletMath", + "../../primitives" + } + + files { + "../main.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/integration/NVIDIA/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/integration/NVIDIA/premake4.lua new file mode 100644 index 000000000..b6b5272f1 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/integration/NVIDIA/premake4.lua @@ -0,0 +1,35 @@ + + hasCL = findOpenCL_NVIDIA() + + if (hasCL) then + + project "OpenCL_integration_NVIDIA" + + initOpenCL_NVIDIA() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + initOpenGL() + initGlut() + initGlew() + + + includedirs { + "../../../rendering/BulletMath", + "../../primitives" + } + + files { + "../main.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h", + "../../opengl_interop/btOpenCLGLInteropBuffer.cpp", + "../../opengl_interop/btOpenCLGLInteropBuffer.h", + "../../opengl_interop/btStopwatch.cpp", + "../../opengl_interop/btStopwatch.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/integration/integrateKernel.cl b/Extras/RigidBodyGpuPipeline/opencl/integration/integrateKernel.cl new file mode 100644 index 000000000..fbb16be97 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/integration/integrateKernel.cl @@ -0,0 +1,73 @@ +MSTRINGIFY( + +float4 quatMult(float4 q1, float4 q2) +{ + float4 q; + q.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; + q.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z; + q.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x; + q.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z; + return q; +} + +float4 quatNorm(float4 q) +{ + float len = native_sqrt(dot(q, q)); + if(len > 0.f) + { + q *= 1.f / len; + } + else + { + q.x = q.y = q.z = 0.f; + q.w = 1.f; + } + return q; +} + + + +__kernel void + interopKernel( const int startOffset, const int numNodes, __global float4 *g_vertexBuffer, + __global float4 *linVel, + __global float4 *pAngVel) +{ + int nodeID = get_global_id(0); + float timeStep = 0.0166666; + + float BT_GPU_ANGULAR_MOTION_THRESHOLD = (0.25f * 3.14159254); + + if( nodeID < numNodes ) + { + g_vertexBuffer[nodeID + startOffset/4] += linVel[nodeID]*timeStep; + + // g_vertexBuffer[nodeID + startOffset/4+numNodes] += angVel[nodeID]; + + float4 axis; + float4 angvel = pAngVel[nodeID]; + float fAngle = native_sqrt(dot(angvel, angvel)); + //limit the angular motion + if(fAngle*timeStep > BT_GPU_ANGULAR_MOTION_THRESHOLD) + { + fAngle = BT_GPU_ANGULAR_MOTION_THRESHOLD / timeStep; + } + if(fAngle < 0.001f) + { + // use Taylor's expansions of sync function + axis = angvel * (0.5f*timeStep-(timeStep*timeStep*timeStep)*0.020833333333f * fAngle * fAngle); + } + else + { + // sync(fAngle) = sin(c*fAngle)/t + axis = angvel * ( native_sin(0.5f * fAngle * timeStep) / fAngle); + } + float4 dorn = axis; + dorn.w = native_cos(fAngle * timeStep * 0.5f); + float4 orn0 = g_vertexBuffer[nodeID + startOffset/4+numNodes]; + float4 predictedOrn = quatMult(dorn, orn0); + predictedOrn = quatNorm(predictedOrn); + g_vertexBuffer[nodeID + startOffset/4+numNodes]=predictedOrn; + } +} + +); \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/integration/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/integration/main.cpp new file mode 100644 index 000000000..26e44b6bf --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/integration/main.cpp @@ -0,0 +1,1106 @@ + +//starts crashing when more than 32700 objects on my Geforce 260, unless _USE_SUB_DATA is defined (still unstable though) +//runs fine with fewer objects + +#define NUM_OBJECTS_X 327 +#define NUM_OBJECTS_Y 20 +#define NUM_OBJECTS_Z 20 +//#define NUM_OBJECTS_Z 20 + +//#define _USE_SUB_DATA + +//#define NUM_OBJECTS_X 100 +//#define NUM_OBJECTS_Y 100 +//#define NUM_OBJECTS_Z 100 + +///RECREATE_CL_AND_SHADERS_ON_RESIZE will delete and re-create OpenCL and GLSL shaders/buffers at each resize +//#define RECREATE_CL_AND_SHADERS_ON_RESIZE + +/// +/// OpenCL - OpenGL interop example. Updating transforms of many cubes on GPU, without going through main memory/using the PCIe bus +/// Create all OpenGL resources AFTER create OpenCL context! +/// + + +#include +#include + +#include "btGlutInclude.h" +#include "../opengl_interop/btStopwatch.h" + + +#include "btVector3.h" +#include "btQuaternion.h" +#include "btMatrix3x3.h" +static float angle(0); + +#include + +#ifdef _WIN32 +#include +#endif + +//OpenCL stuff +#include "../basic_initialize/btOpenCLUtils.h" +#include "../opengl_interop/btOpenCLGLInteropBuffer.h" + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; +cl_device_id g_device; +static const size_t workGroupSize = 128; +cl_mem gLinVelMem; +cl_mem gAngVelMem; + + +btOpenCLGLInteropBuffer* g_interopBuffer = 0; +cl_kernel g_interopKernel; + +////for Adl +#include + +adl::DeviceCL* g_deviceCL=0; + + + +bool useCPU = false; +bool printStats = false; +bool runOpenCLKernels = true; + +#define MSTRINGIFY(A) #A +static char* interopKernelString = +#include "integrateKernel.cl" + +btStopwatch gStopwatch; +int m_glutScreenWidth = 640; +int m_glutScreenHeight= 480; + +bool m_ortho = false; + +static GLuint instancingShader; // The instancing renderer +static GLuint cube_vao; +static GLuint cube_vbo; +static GLuint index_vbo; +static GLuint m_texturehandle; + +static bool done = false; +static GLint angle_loc = 0; +static GLint ModelViewMatrix; +static GLint ProjectionMatrix; + + +static GLint uniform_texture_diffuse = 0; + +//used for dynamic loading from disk (default switched off) +#define MAX_SHADER_LENGTH 8192 +static GLubyte shaderText[MAX_SHADER_LENGTH]; + +static const char* vertexShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"\n" +"\n" +"layout (location = 0) in vec4 position;\n" +"layout (location = 1) in vec4 instance_position;\n" +"layout (location = 2) in vec4 instance_quaternion;\n" +"layout (location = 3) in vec2 uvcoords;\n" +"layout (location = 4) in vec3 vertexnormal;\n" +"\n" +"\n" +"uniform float angle = 0.0;\n" +"uniform mat4 ModelViewMatrix;\n" +"uniform mat4 ProjectionMatrix;\n" +"\n" +"out Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"out Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"\n" +"vec4 quatMul ( in vec4 q1, in vec4 q2 )\n" +"{\n" +" vec3 im = q1.w * q2.xyz + q1.xyz * q2.w + cross ( q1.xyz, q2.xyz );\n" +" vec4 dt = q1 * q2;\n" +" float re = dot ( dt, vec4 ( -1.0, -1.0, -1.0, 1.0 ) );\n" +" return vec4 ( im, re );\n" +"}\n" +"\n" +"vec4 quatFromAxisAngle(vec4 axis, in float angle)\n" +"{\n" +" float cah = cos(angle*0.5);\n" +" float sah = sin(angle*0.5);\n" +" float d = inversesqrt(dot(axis,axis));\n" +" vec4 q = vec4(axis.x*sah*d,axis.y*sah*d,axis.z*sah*d,cah);\n" +" return q;\n" +"}\n" +"//\n" +"// vector rotation via quaternion\n" +"//\n" +"vec4 quatRotate3 ( in vec3 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, vec4 ( p, 0.0 ) );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"vec4 quatRotate ( in vec4 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, p );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"\n" +"out vec3 lightDir,normal,ambient;\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 q = instance_quaternion;\n" +" ambient = vec3(0.2,0.2,0.2);\n" +" \n" +" \n" +" vec4 local_normal = (quatRotate3( vertexnormal,q));\n" +" vec3 light_pos = vec3(1000,1000,1000);\n" +" normal = normalize(ModelViewMatrix * local_normal).xyz;\n" +"\n" +" lightDir = normalize(light_pos);//gl_LightSource[0].position.xyz));\n" +"// lightDir = normalize(vec3(gl_LightSource[0].position));\n" +" \n" +" vec4 axis = vec4(1,1,1,0);\n" +" vec4 localcoord = quatRotate3( position.xyz,q);\n" +" vec4 vertexPos = ProjectionMatrix * ModelViewMatrix *(instance_position+localcoord);\n" +"\n" +" gl_Position = vertexPos;\n" +" \n" +"// fragment.color = instance_color;\n" +" vert.texcoord = uvcoords;\n" +"}\n" +; + + +static const char* fragmentShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"in Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"in Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"uniform sampler2D Diffuse;\n" +"\n" +"in vec3 lightDir,normal,ambient;\n" +"\n" +"out vec4 color;\n" +"\n" +"void main_textured(void)\n" +"{\n" +" color = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 texel = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +" vec3 ct,cf;\n" +" float intensity,at,af;\n" +" intensity = max(dot(lightDir,normalize(normal)),0.0);\n" +" cf = intensity*vec3(1.0,1.0,1.0);//intensity * (gl_FrontMaterial.diffuse).rgb+ambient;//gl_FrontMaterial.ambient.rgb;\n" +" af = 1.0;\n" +" \n" +" ct = texel.rgb;\n" +" at = texel.a;\n" +" \n" +" color = vec4(ct * cf, at * af); \n" +"}\n" +; + + +// Load the shader from the source text +void gltLoadShaderSrc(const char *szShaderSrc, GLuint shader) +{ + GLchar *fsStringPtr[1]; + + fsStringPtr[0] = (GLchar *)szShaderSrc; + glShaderSource(shader, 1, (const GLchar **)fsStringPtr, NULL); +} + + +//////////////////////////////////////////////////////////////// +// Load the shader from the specified file. Returns false if the +// shader could not be loaded +bool gltLoadShaderFile(const char *szFile, GLuint shader) +{ + GLint shaderLength = 0; + FILE *fp; + + // Open the shader file + fp = fopen(szFile, "r"); + if(fp != NULL) + { + // See how long the file is + while (fgetc(fp) != EOF) + shaderLength++; + + // Allocate a block of memory to send in the shader + assert(shaderLength < MAX_SHADER_LENGTH); // make me bigger! + if(shaderLength > MAX_SHADER_LENGTH) + { + fclose(fp); + return false; + } + + // Go back to beginning of file + rewind(fp); + + // Read the whole file in + if (shaderText != NULL) + fread(shaderText, 1, shaderLength, fp); + + // Make sure it is null terminated and close the file + shaderText[shaderLength] = '\0'; + fclose(fp); + } + else + return false; + + // printf(shaderText); + // Load the string + gltLoadShaderSrc((const char *)shaderText, shader); + + return true; +} + + +///////////////////////////////////////////////////////////////// +// Load a pair of shaders, compile, and link together. Specify the complete +// file path for each shader. Note, there is no support for +// just loading say a vertex program... you have to do both. +GLuint gltLoadShaderPair(const char *szVertexProg, const char *szFragmentProg, bool loadFromFile) +{ + // Temporary Shader objects + GLuint hVertexShader; + GLuint hFragmentShader; + GLuint hReturn = 0; + GLint testVal; + + // Create shader objects + hVertexShader = glCreateShader(GL_VERTEX_SHADER); + hFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (loadFromFile) + { + + if(gltLoadShaderFile(szVertexProg, hVertexShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + if(gltLoadShaderFile(szFragmentProg, hFragmentShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + } else + { + gltLoadShaderSrc(vertexShader, hVertexShader); + gltLoadShaderSrc(fragmentShader, hFragmentShader); + } + // Compile them + glCompileShader(hVertexShader); + glCompileShader(hFragmentShader); + + // Check for errors + glGetShaderiv(hVertexShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hVertexShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + glGetShaderiv(hFragmentShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hFragmentShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + // Link them - assuming it works... + hReturn = glCreateProgram(); + glAttachShader(hReturn, hVertexShader); + glAttachShader(hReturn, hFragmentShader); + + glLinkProgram(hReturn); + + // These are no longer needed + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + + // Make sure link worked too + glGetProgramiv(hReturn, GL_LINK_STATUS, &testVal); + if(testVal == GL_FALSE) + { + glDeleteProgram(hReturn); + return (GLuint)NULL; + } + + return hReturn; +} + +///position xyz, unused w, normal, uv +static const GLfloat cube_vertices[] = +{ + -1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 0,0,//0 + 1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 1,0,//1 + 1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 1,1,//2 + -1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 0,1 ,//3 + + -1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 0,0,//4 + 1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 1,0,//5 + 1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 1,1,//6 + -1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 0,1,//7 + + -1.0f, -1.0f, -1.0f, 1.0f, -1,0,0, 0,0, + -1.0f, 1.0f, -1.0f, 1.0f, -1,0,0, 1,0, + -1.0f, 1.0f, 1.0f, 1.0f, -1,0,0, 1,1, + -1.0f, -1.0f, 1.0f, 1.0f, -1,0,0, 0,1, + + 1.0f, -1.0f, -1.0f, 1.0f, 1,0,0, 0,0, + 1.0f, 1.0f, -1.0f, 1.0f, 1,0,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 1,0,0, 1,1, + 1.0f, -1.0f, 1.0f, 1.0f, 1,0,0, 0,1, + + -1.0f, -1.0f, -1.0f, 1.0f, 0,-1,0, 0,0, + -1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,0, + 1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,1, + 1.0f,-1.0f, -1.0f, 1.0f, 0,-1,0, 0,1, + + -1.0f, 1.0f, -1.0f, 1.0f, 0,1,0, 0,0, + -1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,1, + 1.0f,1.0f, -1.0f, 1.0f, 0,1,0, 0,1, +}; + +static const int cube_indices[]= +{ + 0,1,2,0,2,3,//ground face + 4,5,6,4,6,7,//top face + 8,9,10,8,10,11, + 12,13,14,12,14,15, + 16,17,18,16,18,19, + 20,21,22,20,22,23 +}; + + + + + +void DeleteCL() +{ + clReleaseContext(g_cxMainContext); + clReleaseCommandQueue(g_cqCommandQue); +} + +void InitCL() +{ + void* glCtx=0; + void* glDC = 0; + +#ifdef _WIN32 + glCtx = wglGetCurrentContext(); +#else //!_WIN32 + GLXContext glCtx = glXGetCurrentContext(); +#endif //!_WIN32 + glDC = wglGetCurrentDC(); + + int ciErrNum = 0; + cl_device_type deviceType = CL_DEVICE_TYPE_ALL;//GPU; + g_cxMainContext = btOpenCLUtils::createContextFromType(deviceType, &ciErrNum, glCtx, glDC); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + int numDev = btOpenCLUtils::getNumDevices(g_cxMainContext); + + if (numDev>0) + { + g_device= btOpenCLUtils::getDevice(g_cxMainContext,0); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(g_device,clInfo); + btOpenCLUtils::printDeviceInfo(g_device); + // create a command-queue + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, g_device, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + //normally you would create and execute kernels using this command queue + + } + + +} + +#define NUM_OBJECTS (NUM_OBJECTS_X*NUM_OBJECTS_Y*NUM_OBJECTS_Z) +#define POSITION_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) +#define ORIENTATION_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) + + +GLfloat* instance_positions_ptr = 0; +GLfloat* instance_quaternion_ptr = 0; + +void DeleteShaders() +{ + glDeleteVertexArrays(1, &cube_vao); + glDeleteBuffers(1,&index_vbo); + glDeleteBuffers(1,&cube_vbo); + glDeleteProgram(instancingShader); +} + +void writeTransforms() +{ + + + glFlush(); + char* bla = (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + + float* positions = (float*)(bla+sizeof(cube_vertices)); + float* orientations = (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE); + // positions[0]+=0.001f; + + static int offset=0; + //offset++; + + static btVector3 axis(1,0,0); + angle += 0.01f; + int index=0; + btQuaternion orn(axis,angle); + for (int i=0;i m_glutScreenHeight) + { + aspect = m_glutScreenWidth / (float)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + } else + { + aspect = m_glutScreenHeight / (float)m_glutScreenWidth; + extents.setValue(1.0f, aspect*1.f,0); + } + + + if (m_ortho) + { + // reset matrix + glLoadIdentity(); + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + glOrtho(lower.getX(), upper.getX(), lower.getY(), upper.getY(),-1000,1000); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } else + { + if (m_glutScreenWidth > m_glutScreenHeight) + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } else + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(m_cameraPosition[0], m_cameraPosition[1], m_cameraPosition[2], + m_cameraTargetPosition[0], m_cameraTargetPosition[1], m_cameraTargetPosition[2], + m_cameraUp.getX(),m_cameraUp.getY(),m_cameraUp.getZ()); + } + +} + + + +void myinit() +{ + + + // GLfloat light_ambient[] = { btScalar(0.2), btScalar(0.2), btScalar(0.2), btScalar(1.0) }; + GLfloat light_ambient[] = { btScalar(1.0), btScalar(1.2), btScalar(0.2), btScalar(1.0) }; + + GLfloat light_diffuse[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0) }; + GLfloat light_specular[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0 )}; + /* light_position is NOT default value */ + GLfloat light_position0[] = { btScalar(1000.0), btScalar(1000.0), btScalar(1000.0), btScalar(0.0 )}; + GLfloat light_position1[] = { btScalar(-1.0), btScalar(-10.0), btScalar(-1.0), btScalar(0.0) }; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + + glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + + // glShadeModel(GL_FLAT);//GL_SMOOTH); + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glClearColor(float(0.7),float(0.7),float(0.7),float(0)); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + + static bool m_textureenabled = true; + static bool m_textureinitialized = false; + + + if(m_textureenabled) + { + if(!m_textureinitialized) + { + glActiveTexture(GL_TEXTURE0); + + GLubyte* image=new GLubyte[256*256*3]; + for(int y=0;y<256;++y) + { + const int t=y>>5; + GLubyte* pi=image+y*256*3; + for(int x=0;x<256;++x) + { + const int s=x>>5; + const GLubyte b=180; + GLubyte c=b+((s+t&1)&1)*(255-b); + pi[0]=255; + pi[1]=c; + pi[2]=c; + pi+=3; + } + } + + glGenTextures(1,(GLuint*)&m_texturehandle); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + gluBuild2DMipmaps(GL_TEXTURE_2D,3,256,256,GL_RGB,GL_UNSIGNED_BYTE,image); + delete[] image; + m_textureinitialized=true; + } + // glMatrixMode(GL_TEXTURE); + // glLoadIdentity(); + // glMatrixMode(GL_MODELVIEW); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + + } else + { + glDisable(GL_TEXTURE_2D); + } + + glEnable(GL_COLOR_MATERIAL); + + + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); +} + +//#pragma optimize( "g", off ) + +void updatePos() +{ + + + if (useCPU) + { + int index=0; + for (int i=0;igetCLBUffer(); + cl_int ciErrNum = CL_SUCCESS; + ciErrNum = clEnqueueAcquireGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, NULL); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + if (runOpenCLKernels) + { + int numObjects = NUM_OBJECTS; + int offset = (sizeof(cube_vertices) )/4; + + ciErrNum = clSetKernelArg(g_interopKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(g_interopKernel, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(g_interopKernel, 2, sizeof(cl_mem), (void*)&clBuffer ); + + ciErrNum = clSetKernelArg(g_interopKernel, 3, sizeof(cl_mem), (void*)&gLinVelMem); + ciErrNum = clSetKernelArg(g_interopKernel, 4, sizeof(cl_mem), (void*)&gAngVelMem); + + size_t numWorkItems = workGroupSize*((NUM_OBJECTS + (workGroupSize-1)) / workGroupSize); + ciErrNum = clEnqueueNDRangeKernel(g_cqCommandQue, g_interopKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + + ciErrNum = clEnqueueReleaseGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, 0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + clFinish(g_cqCommandQue); + + } + +} +//#pragma optimize( "g", on ) + +void RenderScene(void) +{ + +#if 0 + float modelview[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + // get the current modelview matrix + glGetFloatv(GL_MODELVIEW_MATRIX , modelview); + float projection[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + glGetFloatv(GL_PROJECTION_MATRIX, projection); +#endif + + myinit(); + + updateCamera(); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //render coordinate system + glBegin(GL_LINES); + glColor3f(1,0,0); + glVertex3f(0,0,0); + glVertex3f(1,0,0); + glColor3f(0,1,0); + glVertex3f(0,0,0); + glVertex3f(0,1,0); + glColor3f(0,0,1); + glVertex3f(0,0,0); + glVertex3f(0,0,1); + glEnd(); + + //do a finish, to make sure timings are clean + // glFinish(); + + float start = gStopwatch.getTimeMilliseconds(); + + // glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + updatePos(); + + float stop = gStopwatch.getTimeMilliseconds(); + gStopwatch.reset(); + + if (printStats) + { + printf("updatePos=%f ms on ",stop-start); + + if (useCPU) + { + printf("CPU \n"); + } else + { + printf("OpenCL "); + if (runOpenCLKernels) + printf("running the kernels"); + else + printf("without running the kernels"); + printf("\n"); + } + } + + glBindVertexArray(cube_vao); + + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 9*sizeof(float), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices))); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE)); + int uvoffset = 7*sizeof(float); + int normaloffset = 4*sizeof(float); + + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)uvoffset); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)normaloffset); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + + glVertexAttribDivisor(1, 1); + glVertexAttribDivisor(2, 1); + glVertexAttribDivisor(3, 0); + glVertexAttribDivisor(4, 0); + + glUseProgram(instancingShader); + glUniform1f(angle_loc, 0); + GLfloat pm[16]; + glGetFloatv(GL_PROJECTION_MATRIX, pm); + glUniformMatrix4fv(ProjectionMatrix, 1, false, &pm[0]); + + GLfloat mvm[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mvm); + glUniformMatrix4fv(ModelViewMatrix, 1, false, &mvm[0]); + + glUniform1i(uniform_texture_diffuse, 0); + + glFlush(); + int numInstances = NUM_OBJECTS; + int indexCount = sizeof(cube_indices)/sizeof(int); + int indexOffset = 0; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_vbo); + glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void*)indexOffset, numInstances); + + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + glutSwapBuffers(); + glutPostRedisplay(); + + GLint err = glGetError(); + assert(err==GL_NO_ERROR); +} + + +void ChangeSize(int w, int h) +{ + m_glutScreenWidth = w; + m_glutScreenHeight = h; + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + delete g_interopBuffer; + clReleaseKernel(g_interopKernel); + DeleteCL(); + DeleteShaders(); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + + // Set Viewport to window dimensions + glViewport(0, 0, w, h); + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + InitCL(); + InitShaders(); + + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); + g_interopKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, interopKernelString, "interopKernel" ); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + +} + +void Keyboard(unsigned char key, int x, int y) +{ + switch (key) + { + case 27: + done = true; + break; + case 'O': + case 'o': + { + m_ortho = !m_ortho; + break; + } + case 'c': + case 'C': + { + useCPU = !useCPU; + if (useCPU) + printf("using CPU\n"); + else + printf("using OpenCL\n"); + break; + } + case 's': + case 'S': + { + printStats = !printStats; + break; + } + case 'k': + case 'K': + { + runOpenCLKernels=!runOpenCLKernels; + break; + } + case 'q': + case 'Q': + exit(0); + default: + break; + } +} + +// Cleanup +void ShutdownRC(void) +{ + glDeleteBuffers(1, &cube_vbo); + glDeleteVertexArrays(1, &cube_vao); +} + +int main(int argc, char* argv[]) +{ + // printf("vertexShader = \n%s\n",vertexShader); + // printf("fragmentShader = \n%s\n",fragmentShader); + + glutInit(&argc, argv); + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); + + + glutInitWindowSize(m_glutScreenWidth, m_glutScreenHeight); + char buf[1024]; + sprintf(buf,"OpenCL - OpenGL interop, transforms %d cubes on the GPU (use c to toggle CPU/CL)", NUM_OBJECTS); + glutCreateWindow(buf); + + glutReshapeFunc(ChangeSize); + + glutKeyboardFunc(Keyboard); + glutDisplayFunc(RenderScene); + + GLenum err = glewInit(); + if (GLEW_OK != err) + { + /* Problem: glewInit failed, something is seriously wrong. */ + fprintf(stderr, "Error: %s\n", glewGetErrorString(err)); + } + + //ChangeSize(m_glutScreenWidth,m_glutScreenHeight); + + InitCL(); + + +#define CUSTOM_CL_INITIALIZATION +#ifdef CUSTOM_CL_INITIALIZATION + g_deviceCL = new adl::DeviceCL(); + g_deviceCL->m_deviceIdx = g_device; + g_deviceCL->m_context = g_cxMainContext; + g_deviceCL->m_commandQueue = g_cqCommandQue; + +#else + DeviceUtils::Config cfg; + cfg.m_type = DeviceUtils::Config::DEVICE_CPU; + g_deviceCL = DeviceUtils::allocate( TYPE_CL, cfg ); +#endif + + int size = NUM_OBJECTS; + adl::Buffer linvelBuf( g_deviceCL, size ); + adl::Buffer angvelBuf( g_deviceCL, size ); + + gLinVelMem = (cl_mem)linvelBuf.m_ptr; + gAngVelMem = (cl_mem)angvelBuf.m_ptr; + + btVector3* linVelHost= new btVector3[size]; + btVector3* angVelHost = new btVector3[size]; + + for (int i=0;i +#include +#include +#endif + +#if defined (SUNOS) || defined (__SUNOS__) +#include +#endif + +#if defined(WIN32) || defined(_WIN32) + +#define BT_USE_WINDOWS_TIMERS +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOMCX +#define NOIME + +#ifdef _XBOX + #include +#else //_XBOX + #include +#endif //_XBOX + +#include + + +#else //_WIN32 +#include +#endif //_WIN32 + +#define mymin(a,b) (a > b ? a : b) + +struct btStopwatchData +{ + +#ifdef BT_USE_WINDOWS_TIMERS + LARGE_INTEGER mClockFrequency; + DWORD mStartTick; + LONGLONG mPrevElapsedTime; + LARGE_INTEGER mStartTime; +#else +#ifdef __CELLOS_LV2__ + uint64_t mStartTime; +#else + struct timeval mStartTime; +#endif +#endif //__CELLOS_LV2__ + +}; + + +btStopwatch::btStopwatch() +{ + m_data = new btStopwatchData; +#ifdef BT_USE_WINDOWS_TIMERS + QueryPerformanceFrequency(&m_data->mClockFrequency); +#endif + reset(); +} + +btStopwatch::~btStopwatch() +{ + delete m_data; +} + +btStopwatch::btStopwatch(const btStopwatch& other) +{ + m_data = new btStopwatchData; + *m_data = *other.m_data; +} + +btStopwatch& btStopwatch::operator=(const btStopwatch& other) +{ + *m_data = *other.m_data; + return *this; +} + + + /// Resets the initial reference time. +void btStopwatch::reset() +{ +#ifdef BT_USE_WINDOWS_TIMERS + QueryPerformanceCounter(&m_data->mStartTime); + m_data->mStartTick = GetTickCount(); + m_data->mPrevElapsedTime = 0; +#else +#ifdef __CELLOS_LV2__ + + typedef uint64_t ClockSize; + ClockSize newTime; + //__asm __volatile__( "mftb %0" : "=r" (newTime) : : "memory"); + SYS_TIMEBASE_GET( newTime ); + m_data->mStartTime = newTime; +#else + gettimeofday(&m_data->mStartTime, 0); +#endif +#endif +} + +/// Returns the time in ms since the last call to reset or since +/// the btStopwatch was created. +float btStopwatch::getTimeMilliseconds() +{ + return getTimeMicroseconds()/1000.f; +} + + /// Returns the time in us since the last call to reset or since + /// the stopwatch was created. +unsigned long int btStopwatch::getTimeMicroseconds() +{ +#ifdef BT_USE_WINDOWS_TIMERS + LARGE_INTEGER currentTime; + QueryPerformanceCounter(¤tTime); + LONGLONG elapsedTime = currentTime.QuadPart - m_data->mStartTime.QuadPart; + + // Compute the number of millisecond ticks elapsed. + unsigned long msecTicks = (unsigned long)(1000 * elapsedTime / m_data->mClockFrequency.QuadPart); + + // Check for unexpected leaps in the Win32 performance counter. + // (This is caused by unexpected data across the PCI to ISA + // bridge, aka south bridge. See Microsoft KB274323.) + unsigned long elapsedTicks = GetTickCount() - m_data->mStartTick; + signed long msecOff = (signed long)(msecTicks - elapsedTicks); + if (msecOff < -100 || msecOff > 100) + { + // Adjust the starting time forwards. + LONGLONG msecAdjustment = mymin(msecOff * + m_data->mClockFrequency.QuadPart / 1000, elapsedTime - + m_data->mPrevElapsedTime); + m_data->mStartTime.QuadPart += msecAdjustment; + elapsedTime -= msecAdjustment; + } + + // Store the current elapsed time for adjustments next time. + m_data->mPrevElapsedTime = elapsedTime; + + // Convert to microseconds. + unsigned long usecTicks = (unsigned long)(1000000 * elapsedTime / + m_data->mClockFrequency.QuadPart); + + return usecTicks; +#else + +#ifdef __CELLOS_LV2__ + uint64_t freq=sys_time_get_timebase_frequency(); + double dFreq=((double) freq)/ 1000000.0; + typedef uint64_t ClockSize; + ClockSize newTime; + //__asm __volatile__( "mftb %0" : "=r" (newTime) : : "memory"); + SYS_TIMEBASE_GET( newTime ); + + return (unsigned long int)((double(newTime-m_data->mStartTime)) / dFreq); +#else + + struct timeval currentTime; + gettimeofday(¤tTime, 0); + return (currentTime.tv_sec - m_data->mStartTime.tv_sec) * 1000000 + (currentTime.tv_usec - m_data->mStartTime.tv_usec); +#endif//__CELLOS_LV2__ +#endif +} + + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btStopwatch.h b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btStopwatch.h new file mode 100644 index 000000000..19e8d3722 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/btStopwatch.h @@ -0,0 +1,45 @@ +/* +Stopwatch for timing and profiling for the Bullet Physics Library, http://bulletphysics.org +Copyright (c) 2003-2011 Erwin Coumans + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef BT_STOPWATCH_H +#define BT_STOPWATCH_H + +///The btStopwatch is a portable basic clock that measures real-time, use for profiling etc. +class btStopwatch +{ +public: + btStopwatch(); + + btStopwatch(const btStopwatch& other); + btStopwatch& operator=(const btStopwatch& other); + + ~btStopwatch(); + + /// Resets the initial reference time. + void reset(); + + /// Returns the time in ms since the last call to reset or since + /// the btStopwatch was created. + float getTimeMilliseconds(); + + /// Returns the time in us since the last call to reset or since + /// the Clock was created. + unsigned long int getTimeMicroseconds(); +private: + struct btStopwatchData* m_data; +}; + + +#endif //BT_STOPWATCH_H \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/interopKernel.cl b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/interopKernel.cl new file mode 100644 index 000000000..e65da56dc --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/interopKernel.cl @@ -0,0 +1,13 @@ +MSTRINGIFY( + +__kernel void +interopKernel( const int startOffset, const int numNodes, __global float *g_vertexBuffer) +{ + int nodeID = get_global_id(0); + if( nodeID < numNodes ) + { + g_vertexBuffer[nodeID*4 + startOffset+1] += 0.01; + } +} + +); \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/main.cpp new file mode 100644 index 000000000..9ee3921b5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/main.cpp @@ -0,0 +1,1057 @@ + +//starts crashing when more than 32700 objects on my Geforce 260, unless _USE_SUB_DATA is defined (still unstable though) +//runs fine with fewer objects + +#define NUM_OBJECTS_X 327 +#define NUM_OBJECTS_Y 10 +#define NUM_OBJECTS_Z 10 +//#define NUM_OBJECTS_Z 20 + +//#define _USE_SUB_DATA + +//#define NUM_OBJECTS_X 100 +//#define NUM_OBJECTS_Y 100 +//#define NUM_OBJECTS_Z 100 + +///RECREATE_CL_AND_SHADERS_ON_RESIZE will delete and re-create OpenCL and GLSL shaders/buffers at each resize +//#define RECREATE_CL_AND_SHADERS_ON_RESIZE + +/// +/// OpenCL - OpenGL interop example. Updating transforms of many cubes on GPU, without going through main memory/using the PCIe bus +/// Create all OpenGL resources AFTER create OpenCL context! +/// + + +#include +#include + +#include "btGlutInclude.h" +#include "btStopwatch.h" + + +#include "btVector3.h" +#include "btQuaternion.h" +#include "btMatrix3x3.h" +static float angle(0); + +#include + +#ifdef _WIN32 +#include +#endif + +//OpenCL stuff +#include "../basic_initialize/btOpenCLUtils.h" +#include "btOpenCLGLInteropBuffer.h" + +cl_context g_cxMainContext; +cl_command_queue g_cqCommandQue; +cl_device_id g_device; +static const size_t workGroupSize = 128; + + +btOpenCLGLInteropBuffer* g_interopBuffer = 0; +cl_kernel g_interopKernel; + +bool useCPU = false; +bool printStats = false; +bool runOpenCLKernels = true; + +#define MSTRINGIFY(A) #A +static char* interopKernelString = +#include "interopKernel.cl" + +btStopwatch gStopwatch; +int m_glutScreenWidth = 640; +int m_glutScreenHeight= 480; + +bool m_ortho = false; + +static GLuint instancingShader; // The instancing renderer +static GLuint cube_vao; +static GLuint cube_vbo; +static GLuint index_vbo; +static GLuint m_texturehandle; + +static bool done = false; +static GLint angle_loc = 0; +static GLint ModelViewMatrix; +static GLint ProjectionMatrix; + + +static GLint uniform_texture_diffuse = 0; + +//used for dynamic loading from disk (default switched off) +#define MAX_SHADER_LENGTH 8192 +static GLubyte shaderText[MAX_SHADER_LENGTH]; + +static const char* vertexShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"\n" +"\n" +"layout (location = 0) in vec4 position;\n" +"layout (location = 1) in vec4 instance_position;\n" +"layout (location = 2) in vec4 instance_quaternion;\n" +"layout (location = 3) in vec2 uvcoords;\n" +"layout (location = 4) in vec3 vertexnormal;\n" +"\n" +"\n" +"uniform float angle = 0.0;\n" +"uniform mat4 ModelViewMatrix;\n" +"uniform mat4 ProjectionMatrix;\n" +"\n" +"out Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"out Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"\n" +"vec4 quatMul ( in vec4 q1, in vec4 q2 )\n" +"{\n" +" vec3 im = q1.w * q2.xyz + q1.xyz * q2.w + cross ( q1.xyz, q2.xyz );\n" +" vec4 dt = q1 * q2;\n" +" float re = dot ( dt, vec4 ( -1.0, -1.0, -1.0, 1.0 ) );\n" +" return vec4 ( im, re );\n" +"}\n" +"\n" +"vec4 quatFromAxisAngle(vec4 axis, in float angle)\n" +"{\n" +" float cah = cos(angle*0.5);\n" +" float sah = sin(angle*0.5);\n" +" float d = inversesqrt(dot(axis,axis));\n" +" vec4 q = vec4(axis.x*sah*d,axis.y*sah*d,axis.z*sah*d,cah);\n" +" return q;\n" +"}\n" +"//\n" +"// vector rotation via quaternion\n" +"//\n" +"vec4 quatRotate3 ( in vec3 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, vec4 ( p, 0.0 ) );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"vec4 quatRotate ( in vec4 p, in vec4 q )\n" +"{\n" +" vec4 temp = quatMul ( q, p );\n" +" return quatMul ( temp, vec4 ( -q.x, -q.y, -q.z, q.w ) );\n" +"}\n" +"\n" +"out vec3 lightDir,normal,ambient;\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 q = instance_quaternion;\n" +" ambient = vec3(0.2,0.2,0.2);\n" +" \n" +" \n" +" vec4 local_normal = (quatRotate3( vertexnormal,q));\n" +" vec3 light_pos = vec3(1000,1000,1000);\n" +" normal = normalize(ModelViewMatrix * local_normal).xyz;\n" +"\n" +" lightDir = normalize(light_pos);//gl_LightSource[0].position.xyz));\n" +"// lightDir = normalize(vec3(gl_LightSource[0].position));\n" +" \n" +" vec4 axis = vec4(1,1,1,0);\n" +" vec4 localcoord = quatRotate3( position.xyz,q);\n" +" vec4 vertexPos = ProjectionMatrix * ModelViewMatrix *(instance_position+localcoord);\n" +"\n" +" gl_Position = vertexPos;\n" +" \n" +"// fragment.color = instance_color;\n" +" vert.texcoord = uvcoords;\n" +"}\n" +; + + +static const char* fragmentShader= \ +"#version 330\n" +"precision highp float;\n" +"\n" +"in Fragment\n" +"{\n" +" vec4 color;\n" +"} fragment;\n" +"\n" +"in Vert\n" +"{\n" +" vec2 texcoord;\n" +"} vert;\n" +"\n" +"uniform sampler2D Diffuse;\n" +"\n" +"in vec3 lightDir,normal,ambient;\n" +"\n" +"out vec4 color;\n" +"\n" +"void main_textured(void)\n" +"{\n" +" color = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 texel = texture2D(Diffuse,vert.texcoord);//fragment.color;\n" +" vec3 ct,cf;\n" +" float intensity,at,af;\n" +" intensity = max(dot(lightDir,normalize(normal)),0.0);\n" +" cf = intensity*vec3(1.0,1.0,1.0);//intensity * (gl_FrontMaterial.diffuse).rgb+ambient;//gl_FrontMaterial.ambient.rgb;\n" +" af = 1.0;\n" +" \n" +" ct = texel.rgb;\n" +" at = texel.a;\n" +" \n" +" color = vec4(ct * cf, at * af); \n" +"}\n" +; + + +// Load the shader from the source text +void gltLoadShaderSrc(const char *szShaderSrc, GLuint shader) +{ + GLchar *fsStringPtr[1]; + + fsStringPtr[0] = (GLchar *)szShaderSrc; + glShaderSource(shader, 1, (const GLchar **)fsStringPtr, NULL); +} + + +//////////////////////////////////////////////////////////////// +// Load the shader from the specified file. Returns false if the +// shader could not be loaded +bool gltLoadShaderFile(const char *szFile, GLuint shader) +{ + GLint shaderLength = 0; + FILE *fp; + + // Open the shader file + fp = fopen(szFile, "r"); + if(fp != NULL) + { + // See how long the file is + while (fgetc(fp) != EOF) + shaderLength++; + + // Allocate a block of memory to send in the shader + assert(shaderLength < MAX_SHADER_LENGTH); // make me bigger! + if(shaderLength > MAX_SHADER_LENGTH) + { + fclose(fp); + return false; + } + + // Go back to beginning of file + rewind(fp); + + // Read the whole file in + if (shaderText != NULL) + fread(shaderText, 1, shaderLength, fp); + + // Make sure it is null terminated and close the file + shaderText[shaderLength] = '\0'; + fclose(fp); + } + else + return false; + + // printf(shaderText); + // Load the string + gltLoadShaderSrc((const char *)shaderText, shader); + + return true; +} + + +///////////////////////////////////////////////////////////////// +// Load a pair of shaders, compile, and link together. Specify the complete +// file path for each shader. Note, there is no support for +// just loading say a vertex program... you have to do both. +GLuint gltLoadShaderPair(const char *szVertexProg, const char *szFragmentProg, bool loadFromFile) +{ + // Temporary Shader objects + GLuint hVertexShader; + GLuint hFragmentShader; + GLuint hReturn = 0; + GLint testVal; + + // Create shader objects + hVertexShader = glCreateShader(GL_VERTEX_SHADER); + hFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (loadFromFile) + { + + if(gltLoadShaderFile(szVertexProg, hVertexShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + if(gltLoadShaderFile(szFragmentProg, hFragmentShader) == false) + { + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + } else + { + gltLoadShaderSrc(vertexShader, hVertexShader); + gltLoadShaderSrc(fragmentShader, hFragmentShader); + } + // Compile them + glCompileShader(hVertexShader); + glCompileShader(hFragmentShader); + + // Check for errors + glGetShaderiv(hVertexShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hVertexShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + glGetShaderiv(hFragmentShader, GL_COMPILE_STATUS, &testVal); + if(testVal == GL_FALSE) + { + char temp[256] = ""; + glGetShaderInfoLog( hFragmentShader, 256, NULL, temp); + fprintf( stderr, "Compile failed:\n%s\n", temp); + assert(0); + exit(0); + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + return (GLuint)NULL; + } + + // Link them - assuming it works... + hReturn = glCreateProgram(); + glAttachShader(hReturn, hVertexShader); + glAttachShader(hReturn, hFragmentShader); + + glLinkProgram(hReturn); + + // These are no longer needed + glDeleteShader(hVertexShader); + glDeleteShader(hFragmentShader); + + // Make sure link worked too + glGetProgramiv(hReturn, GL_LINK_STATUS, &testVal); + if(testVal == GL_FALSE) + { + glDeleteProgram(hReturn); + return (GLuint)NULL; + } + + return hReturn; +} + +///position xyz, unused w, normal, uv +static const GLfloat cube_vertices[] = +{ + -1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 0,0,//0 + 1.0f, -1.0f, 1.0f, 0.0f, 0,0,1, 1,0,//1 + 1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 1,1,//2 + -1.0f, 1.0f, 1.0f, 0.0f, 0,0,1, 0,1 ,//3 + + -1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 0,0,//4 + 1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 1,0,//5 + 1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 1,1,//6 + -1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 0,1,//7 + + -1.0f, -1.0f, -1.0f, 1.0f, -1,0,0, 0,0, + -1.0f, 1.0f, -1.0f, 1.0f, -1,0,0, 1,0, + -1.0f, 1.0f, 1.0f, 1.0f, -1,0,0, 1,1, + -1.0f, -1.0f, 1.0f, 1.0f, -1,0,0, 0,1, + + 1.0f, -1.0f, -1.0f, 1.0f, 1,0,0, 0,0, + 1.0f, 1.0f, -1.0f, 1.0f, 1,0,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 1,0,0, 1,1, + 1.0f, -1.0f, 1.0f, 1.0f, 1,0,0, 0,1, + + -1.0f, -1.0f, -1.0f, 1.0f, 0,-1,0, 0,0, + -1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,0, + 1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,1, + 1.0f,-1.0f, -1.0f, 1.0f, 0,-1,0, 0,1, + + -1.0f, 1.0f, -1.0f, 1.0f, 0,1,0, 0,0, + -1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,0, + 1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,1, + 1.0f,1.0f, -1.0f, 1.0f, 0,1,0, 0,1, +}; + +static const int cube_indices[]= +{ + 0,1,2,0,2,3,//ground face + 4,5,6,4,6,7,//top face + 8,9,10,8,10,11, + 12,13,14,12,14,15, + 16,17,18,16,18,19, + 20,21,22,20,22,23 +}; + + + + + +void DeleteCL() +{ + clReleaseContext(g_cxMainContext); + clReleaseCommandQueue(g_cqCommandQue); +} + +void InitCL() +{ + void* glCtx=0; + void* glDC = 0; + +#ifdef _WIN32 + glCtx = wglGetCurrentContext(); +#else //!_WIN32 + GLXContext glCtx = glXGetCurrentContext(); +#endif //!_WIN32 + glDC = wglGetCurrentDC(); + + int ciErrNum = 0; + cl_device_type deviceType = CL_DEVICE_TYPE_ALL;//CPU; + g_cxMainContext = btOpenCLUtils::createContextFromType(deviceType, &ciErrNum, glCtx, glDC); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + + int numDev = btOpenCLUtils::getNumDevices(g_cxMainContext); + + if (numDev>0) + { + g_device= btOpenCLUtils::getDevice(g_cxMainContext,0); + btOpenCLDeviceInfo clInfo; + btOpenCLUtils::getDeviceInfo(g_device,clInfo); + btOpenCLUtils::printDeviceInfo(g_device); + // create a command-queue + g_cqCommandQue = clCreateCommandQueue(g_cxMainContext, g_device, 0, &ciErrNum); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + //normally you would create and execute kernels using this command queue + + } + + +} + +#define NUM_OBJECTS (NUM_OBJECTS_X*NUM_OBJECTS_Y*NUM_OBJECTS_Z) +#define POSITION_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) +#define ORIENTATION_BUFFER_SIZE (NUM_OBJECTS*sizeof(float)*4) + + +GLfloat* instance_positions_ptr = 0; +GLfloat* instance_quaternion_ptr = 0; + +void DeleteShaders() +{ + glDeleteVertexArrays(1, &cube_vao); + glDeleteBuffers(1,&index_vbo); + glDeleteBuffers(1,&cube_vbo); + glDeleteProgram(instancingShader); +} + +void writeTransforms() +{ + + + glFlush(); + char* bla = (char*)glMapBuffer( GL_ARRAY_BUFFER,GL_READ_WRITE);//GL_WRITE_ONLY + + float* positions = (float*)(bla+sizeof(cube_vertices)); + float* orientations = (float*)(bla+sizeof(cube_vertices) + POSITION_BUFFER_SIZE); + // positions[0]+=0.001f; + + static int offset=0; + //offset++; + + static btVector3 axis(1,0,0); + angle += 0.01f; + int index=0; + btQuaternion orn(axis,angle); + for (int i=0;i m_glutScreenHeight) + { + aspect = m_glutScreenWidth / (float)m_glutScreenHeight; + extents.setValue(aspect * 1.0f, 1.0f,0); + } else + { + aspect = m_glutScreenHeight / (float)m_glutScreenWidth; + extents.setValue(1.0f, aspect*1.f,0); + } + + + if (m_ortho) + { + // reset matrix + glLoadIdentity(); + extents *= m_cameraDistance; + btVector3 lower = m_cameraTargetPosition - extents; + btVector3 upper = m_cameraTargetPosition + extents; + glOrtho(lower.getX(), upper.getX(), lower.getY(), upper.getY(),-1000,1000); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } else + { + if (m_glutScreenWidth > m_glutScreenHeight) + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } else + { + glFrustum (-aspect * m_frustumZNear, aspect * m_frustumZNear, -m_frustumZNear, m_frustumZNear, m_frustumZNear, m_frustumZFar); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(m_cameraPosition[0], m_cameraPosition[1], m_cameraPosition[2], + m_cameraTargetPosition[0], m_cameraTargetPosition[1], m_cameraTargetPosition[2], + m_cameraUp.getX(),m_cameraUp.getY(),m_cameraUp.getZ()); + } + +} + + + +void myinit() +{ + + + // GLfloat light_ambient[] = { btScalar(0.2), btScalar(0.2), btScalar(0.2), btScalar(1.0) }; + GLfloat light_ambient[] = { btScalar(1.0), btScalar(1.2), btScalar(0.2), btScalar(1.0) }; + + GLfloat light_diffuse[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0) }; + GLfloat light_specular[] = { btScalar(1.0), btScalar(1.0), btScalar(1.0), btScalar(1.0 )}; + /* light_position is NOT default value */ + GLfloat light_position0[] = { btScalar(1000.0), btScalar(1000.0), btScalar(1000.0), btScalar(0.0 )}; + GLfloat light_position1[] = { btScalar(-1.0), btScalar(-10.0), btScalar(-1.0), btScalar(0.0) }; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + + glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + + // glShadeModel(GL_FLAT);//GL_SMOOTH); + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glClearColor(float(0.7),float(0.7),float(0.7),float(0)); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + + static bool m_textureenabled = true; + static bool m_textureinitialized = false; + + + if(m_textureenabled) + { + if(!m_textureinitialized) + { + glActiveTexture(GL_TEXTURE0); + + GLubyte* image=new GLubyte[256*256*3]; + for(int y=0;y<256;++y) + { + const int t=y>>5; + GLubyte* pi=image+y*256*3; + for(int x=0;x<256;++x) + { + const int s=x>>5; + const GLubyte b=180; + GLubyte c=b+((s+t&1)&1)*(255-b); + pi[0]=255; + pi[1]=c; + pi[2]=c; + pi+=3; + } + } + + glGenTextures(1,(GLuint*)&m_texturehandle); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + gluBuild2DMipmaps(GL_TEXTURE_2D,3,256,256,GL_RGB,GL_UNSIGNED_BYTE,image); + delete[] image; + m_textureinitialized=true; + } + // glMatrixMode(GL_TEXTURE); + // glLoadIdentity(); + // glMatrixMode(GL_MODELVIEW); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,m_texturehandle); + + } else + { + glDisable(GL_TEXTURE_2D); + } + + glEnable(GL_COLOR_MATERIAL); + + + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); +} + +//#pragma optimize( "g", off ) + +void updatePos() +{ + + + if (useCPU) + { + int index=0; + for (int i=0;igetCLBUffer(); + cl_int ciErrNum = CL_SUCCESS; + ciErrNum = clEnqueueAcquireGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, NULL); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + if (runOpenCLKernels) + { + int numObjects = NUM_OBJECTS; + int offset = (sizeof(cube_vertices) )/4; + + ciErrNum = clSetKernelArg(g_interopKernel, 0, sizeof(int), &offset); + ciErrNum = clSetKernelArg(g_interopKernel, 1, sizeof(int), &numObjects); + ciErrNum = clSetKernelArg(g_interopKernel, 2, sizeof(cl_mem), (void*)&clBuffer ); + size_t numWorkItems = workGroupSize*((NUM_OBJECTS + (workGroupSize-1)) / workGroupSize); + ciErrNum = clEnqueueNDRangeKernel(g_cqCommandQue, g_interopKernel, 1, NULL, &numWorkItems, &workGroupSize,0 ,0 ,0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + } + ciErrNum = clEnqueueReleaseGLObjects(g_cqCommandQue, 1, &clBuffer, 0, 0, 0); + oclCHECKERROR(ciErrNum, CL_SUCCESS); + clFinish(g_cqCommandQue); + + } + +} +//#pragma optimize( "g", on ) + +void RenderScene(void) +{ + +#if 0 + float modelview[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + // get the current modelview matrix + glGetFloatv(GL_MODELVIEW_MATRIX , modelview); + float projection[20]={0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; + glGetFloatv(GL_PROJECTION_MATRIX, projection); +#endif + + myinit(); + + updateCamera(); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //render coordinate system + glBegin(GL_LINES); + glColor3f(1,0,0); + glVertex3f(0,0,0); + glVertex3f(1,0,0); + glColor3f(0,1,0); + glVertex3f(0,0,0); + glVertex3f(0,1,0); + glColor3f(0,0,1); + glVertex3f(0,0,0); + glVertex3f(0,0,1); + glEnd(); + + //do a finish, to make sure timings are clean + // glFinish(); + + float start = gStopwatch.getTimeMilliseconds(); + + // glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glFlush(); + updatePos(); + + float stop = gStopwatch.getTimeMilliseconds(); + gStopwatch.reset(); + + if (printStats) + { + printf("updatePos=%f ms on ",stop-start); + + if (useCPU) + { + printf("CPU \n"); + } else + { + printf("OpenCL "); + if (runOpenCLKernels) + printf("running the kernels"); + else + printf("without running the kernels"); + printf("\n"); + } + } + + glBindVertexArray(cube_vao); + + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 9*sizeof(float), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices))); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(cube_vertices)+POSITION_BUFFER_SIZE)); + int uvoffset = 7*sizeof(float); + int normaloffset = 4*sizeof(float); + + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)uvoffset); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 9*sizeof(float), (GLvoid *)normaloffset); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + + glVertexAttribDivisor(1, 1); + glVertexAttribDivisor(2, 1); + glVertexAttribDivisor(3, 0); + glVertexAttribDivisor(4, 0); + + glUseProgram(instancingShader); + glUniform1f(angle_loc, 0); + GLfloat pm[16]; + glGetFloatv(GL_PROJECTION_MATRIX, pm); + glUniformMatrix4fv(ProjectionMatrix, 1, false, &pm[0]); + + GLfloat mvm[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mvm); + glUniformMatrix4fv(ModelViewMatrix, 1, false, &mvm[0]); + + glUniform1i(uniform_texture_diffuse, 0); + + glFlush(); + int numInstances = NUM_OBJECTS; + int indexCount = sizeof(cube_indices)/sizeof(int); + int indexOffset = 0; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_vbo); + glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void*)indexOffset, numInstances); + + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindVertexArray(0); + + glutSwapBuffers(); + glutPostRedisplay(); + + GLint err = glGetError(); + assert(err==GL_NO_ERROR); +} + + +void ChangeSize(int w, int h) +{ + m_glutScreenWidth = w; + m_glutScreenHeight = h; + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + delete g_interopBuffer; + clReleaseKernel(g_interopKernel); + DeleteCL(); + DeleteShaders(); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + + // Set Viewport to window dimensions + glViewport(0, 0, w, h); + +#ifdef RECREATE_CL_AND_SHADERS_ON_RESIZE + InitCL(); + InitShaders(); + + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); + g_interopKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, interopKernelString, "interopKernel" ); +#endif //RECREATE_CL_AND_SHADERS_ON_RESIZE + +} + +void Keyboard(unsigned char key, int x, int y) +{ + switch (key) + { + case 27: + done = true; + break; + case 'O': + case 'o': + { + m_ortho = !m_ortho; + break; + } + case 'c': + case 'C': + { + useCPU = !useCPU; + if (useCPU) + printf("using CPU\n"); + else + printf("using OpenCL\n"); + break; + } + case 's': + case 'S': + { + printStats = !printStats; + break; + } + case 'k': + case 'K': + { + runOpenCLKernels=!runOpenCLKernels; + break; + } + case 'q': + case 'Q': + exit(0); + default: + break; + } +} + +// Cleanup +void ShutdownRC(void) +{ + glDeleteBuffers(1, &cube_vbo); + glDeleteVertexArrays(1, &cube_vao); +} + +int main(int argc, char* argv[]) +{ + // printf("vertexShader = \n%s\n",vertexShader); + // printf("fragmentShader = \n%s\n",fragmentShader); + + glutInit(&argc, argv); + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); + + + glutInitWindowSize(m_glutScreenWidth, m_glutScreenHeight); + char buf[1024]; + sprintf(buf,"OpenCL - OpenGL interop, transforms %d cubes on the GPU (use c to toggle CPU/CL)", NUM_OBJECTS); + glutCreateWindow(buf); + + glutReshapeFunc(ChangeSize); + + glutKeyboardFunc(Keyboard); + glutDisplayFunc(RenderScene); + + GLenum err = glewInit(); + if (GLEW_OK != err) + { + /* Problem: glewInit failed, something is seriously wrong. */ + fprintf(stderr, "Error: %s\n", glewGetErrorString(err)); + } + + //ChangeSize(m_glutScreenWidth,m_glutScreenHeight); + + InitCL(); + + + InitShaders(); + + g_interopBuffer = new btOpenCLGLInteropBuffer(g_cxMainContext,g_cqCommandQue,cube_vbo); + clFinish(g_cqCommandQue); + + + g_interopKernel = btOpenCLUtils::compileCLKernelFromString(g_cxMainContext, g_device,interopKernelString, "interopKernel" ); + + glutMainLoop(); + ShutdownRC(); + + return 0; +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/premake4.lua new file mode 100644 index 000000000..422952b33 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/opengl_interop/premake4.lua @@ -0,0 +1,5 @@ + + include "AMD" + include "Intel" +-- include "NVIDIA" + \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.cpp b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.cpp new file mode 100644 index 000000000..fdd5ed3e9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.cpp @@ -0,0 +1,19 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#include + +//KernelManager* KernelManager::s_kManager = NULL; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.h new file mode 100644 index 000000000..5d51abe4e --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.h @@ -0,0 +1,235 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ADL_H +#define ADL_H + +#pragma warning( disable : 4996 ) +#include +#include +#include + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +namespace adl +{ + +enum DeviceType +{ + TYPE_CL = 0, + TYPE_DX11 = 1, + TYPE_HOST, +}; + + +struct Device; + +struct BufferBase +{ + enum BufferType + { + BUFFER, + + // for dx + BUFFER_CONST, + BUFFER_STAGING, + BUFFER_APPEND, + BUFFER_RAW, + BUFFER_W_COUNTER, + BUFFER_INDEX, + BUFFER_VERTEX, + + // for cl + BUFFER_ZERO_COPY, + + }; +}; + +class DeviceUtils +{ + public: + struct Config + { + enum DeviceType + { + DEVICE_GPU, + DEVICE_CPU, + }; + + // for CL + enum DeviceVendor + { + VD_AMD, + VD_INTEL, + VD_NV, + }; + + Config() : m_type(DEVICE_GPU), m_deviceIdx(0), m_vendor(VD_AMD){} + + DeviceType m_type; + int m_deviceIdx; + DeviceVendor m_vendor; + }; + + __inline + static + int getNDevices( DeviceType type ); + __inline + static Device* allocate( DeviceType type, Config& cfg ); + __inline + static void deallocate( Device* deviceData ); + __inline + static void waitForCompletion( const Device* deviceData ); +}; + +//========================== +// DeviceData +//========================== +struct Kernel; + +struct Device +{ + typedef DeviceUtils::Config Config; + + Device( DeviceType type ) : m_type( type ), m_memoryUsage(0) + { + } + + virtual void* getContext() const { return 0; } + virtual void initialize(const Config& cfg){} + virtual void release(){} + virtual void waitForCompletion() const {} + virtual void getDeviceName( char nameOut[128] ) const {} + virtual Kernel* getKernel(const char* fileName, const char* funcName, const char* option = NULL, const char* src = NULL, bool cacheKernel = true ) const { ADLASSERT(0); return 0;} + virtual unsigned int getUsedMemory() const { return m_memoryUsage; } + + DeviceType m_type; + unsigned int m_memoryUsage; +}; + +//========================== +// Buffer +//========================== + +template +struct HostBuffer; +// overload each deviceDatas +template +struct Buffer : public BufferBase +{ + __inline + Buffer(); + __inline + Buffer(const Device* device, int nElems, BufferType type = BUFFER ); + __inline + virtual ~Buffer(); + + __inline + void setRawPtr( const Device* device, T* ptr, int size, BufferType type = BUFFER ); + __inline + void allocate(const Device* device, int nElems, BufferType type = BUFFER ); + __inline + void write(T* hostSrcPtr, int nElems, int dstOffsetNElems = 0); + __inline + void read(T* hostDstPtr, int nElems, int srcOffsetNElems = 0) const; + __inline + void write(Buffer& src, int nElems); + __inline + void read(Buffer& dst, int nElems) const; +// __inline +// Buffer& operator = (const Buffer& buffer); + __inline + int getSize() const { return m_size; } + + DeviceType getType() const { ADLASSERT( m_device ); return m_device->m_type; } + + + const Device* m_device; + int m_size; + T* m_ptr; + // for DX11 + void* m_uav; + void* m_srv; + bool m_allocated; // todo. move this to a bit +}; + +class BufferUtils +{ +public: + template + __inline + static + typename Buffer* map(const Device* device, const Buffer* in, int copySize = -1); + + template + __inline + static + void unmap( Buffer* native, const Buffer* orig, int copySize = -1 ); +}; + +//========================== +// HostBuffer +//========================== +struct DeviceHost; + +template +struct HostBuffer : public Buffer +{ + __inline + HostBuffer():Buffer(){} + __inline + HostBuffer(const Device* device, int nElems, BufferType type = BUFFER ) : Buffer(device, nElems, type) {} +// HostBuffer(const Device* deviceData, T* rawPtr, int nElems); + + + __inline + T& operator[](int idx); + __inline + const T& operator[](int idx) const; + __inline + T* begin() { return m_ptr; } + + __inline + HostBuffer& operator = (const Buffer& device); +}; + +}; + +#include +#if defined(ADL_ENABLE_CL) + #include +#endif +#if defined(ADL_ENABLE_DX11) + #include +#endif + +#include +#include +#include + + +#include + +#include +#include + +#endif diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.inl new file mode 100644 index 000000000..d732e86d6 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Adl.inl @@ -0,0 +1,344 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +namespace adl +{ + +int DeviceUtils::getNDevices( DeviceType type ) +{ + switch( type ) + { +#if defined(ADL_ENABLE_CL) + case TYPE_CL: + return DeviceCL::getNDevices(); +#endif +#if defined(ADL_ENABLE_DX11) + case TYPE_DX11: + return DeviceDX11::getNDevices(); +#endif + default: + return 1; + }; +} + +Device* DeviceUtils::allocate( DeviceType type, Config& cfg ) +{ + Device* deviceData; + switch( type ) + { +#if defined(ADL_ENABLE_CL) + case TYPE_CL: + deviceData = new DeviceCL(); + break; +#endif +#if defined(ADL_ENABLE_DX11) + case TYPE_DX11: + deviceData = new DeviceDX11(); + break; +#endif + case TYPE_HOST: + deviceData = new DeviceHost(); + break; + default: + ADLASSERT( 0 ); + break; + }; + deviceData->initialize( cfg ); + return deviceData; +} + +void DeviceUtils::deallocate( Device* deviceData ) +{ + ADLASSERT( deviceData->getUsedMemory() == 0 ); + deviceData->release(); + delete deviceData; +} + +void DeviceUtils::waitForCompletion( const Device* deviceData ) +{ + deviceData->waitForCompletion(); +} + +#if defined(ADL_ENABLE_DX11) + #if defined(ADL_ENABLE_CL) + #define SELECT_DEVICEDATA( type, func ) \ + switch( type ) \ + { \ + case TYPE_CL: ((DeviceCL*)m_device)->func; break; \ + case TYPE_DX11: ((DeviceDX11*)m_device)->func; break; \ + case TYPE_HOST: ((DeviceHost*)m_device)->func; break; \ + default: ADLASSERT(0); break; \ + } + + #define SELECT_DEVICEDATA1( deviceData, func ) \ + switch( deviceData->m_type ) \ + { \ + case TYPE_CL: ((DeviceCL*)deviceData)->func; break; \ + case TYPE_DX11: ((DeviceDX11*)deviceData)->func; break; \ + case TYPE_HOST: ((DeviceHost*)deviceData)->func; break; \ + default: ADLASSERT(0); break; \ + } + #else + #define SELECT_DEVICEDATA( type, func ) \ + switch( type ) \ + { \ + case TYPE_DX11: ((DeviceDX11*)m_device)->func; break; \ + case TYPE_HOST: ((DeviceHost*)m_device)->func; break; \ + default: ADLASSERT(0); break; \ + } + + #define SELECT_DEVICEDATA1( deviceData, func ) \ + switch( deviceData->m_type ) \ + { \ + case TYPE_DX11: ((DeviceDX11*)deviceData)->func; break; \ + case TYPE_HOST: ((DeviceHost*)deviceData)->func; break; \ + default: ADLASSERT(0); break; \ + } + #endif +#else + #if defined(ADL_ENABLE_CL) + #define SELECT_DEVICEDATA( type, func ) \ + switch( type ) \ + { \ + case TYPE_CL: ((DeviceCL*)m_device)->func; break; \ + case TYPE_HOST: ((DeviceHost*)m_device)->func; break; \ + default: ADLASSERT(0); break; \ + } + + #define SELECT_DEVICEDATA1( deviceData, func ) \ + switch( deviceData->m_type ) \ + { \ + case TYPE_CL: ((DeviceCL*)deviceData)->func; break; \ + case TYPE_HOST: ((DeviceHost*)deviceData)->func; break; \ + default: ADLASSERT(0); break; \ + } + #else + #define SELECT_DEVICEDATA( type, func ) \ + switch( type ) \ + { \ + case TYPE_HOST: ((DeviceHost*)m_device)->func; break; \ + default: ADLASSERT(0); break; \ + } + + #define SELECT_DEVICEDATA1( deviceData, func ) \ + switch( deviceData->m_type ) \ + { \ + case TYPE_HOST: ((DeviceHost*)deviceData)->func; break; \ + default: ADLASSERT(0); break; \ + } + #endif +#endif + +template +Buffer::Buffer() +{ + m_device = 0; + m_size = 0; + m_ptr = 0; + + m_uav = 0; + m_srv = 0; + + m_allocated = false; +} + +template +Buffer::Buffer(const Device* deviceData, int nElems, BufferType type ) +{ + m_device = 0; + allocate( deviceData, nElems, type ); +} + +template +Buffer::~Buffer() +{ + if( m_allocated ) + { + if( m_device ) + SELECT_DEVICEDATA( m_device->m_type, deallocate( this ) ); + } + + m_device = 0; + m_ptr = 0; + m_size = 0; +} + +template +void Buffer::setRawPtr( const Device* device, T* ptr, int size, BufferType type ) +{ + ADLASSERT( m_device == 0 ); + ADLASSERT( type == BUFFER ); // todo. implement + ADLASSERT( device->m_type != TYPE_DX11 ); // todo. implement set srv, uav + + m_device = device; + m_ptr = ptr; + m_size = size; +} + +template +void Buffer::allocate(const Device* deviceData, int nElems, BufferType type ) +{ + ADLASSERT( m_device == 0 ); + m_device = deviceData; + m_size = 0; + m_ptr = 0; + + m_uav = 0; + m_srv = 0; + + SELECT_DEVICEDATA( m_device->m_type, allocate( this, nElems, type ) ); + m_allocated = true; +} + +template +void Buffer::write(T* hostPtr, int nElems, int offsetNElems) +{ + ADLASSERT( nElems+offsetNElems <= m_size ); + SELECT_DEVICEDATA( m_device->m_type, copy(this, hostPtr, nElems, offsetNElems) ); +} + +template +void Buffer::read(T* hostPtr, int nElems, int offsetNElems) const +{ + SELECT_DEVICEDATA( m_device->m_type, copy(hostPtr,this, nElems, offsetNElems) ); +} + +template +void Buffer::write(Buffer& src, int nElems) +{ + ADLASSERT( nElems <= m_size ); + SELECT_DEVICEDATA( m_device->m_type, copy(this, &src, nElems) ); +} + +template +void Buffer::read(Buffer& dst, int nElems) const +{ + SELECT_DEVICEDATA( m_device->m_type, copy(&dst, this, nElems) ); +} +/* +template +Buffer& Buffer::operator = ( const Buffer& buffer ) +{ +// ADLASSERT( buffer.m_size <= m_size ); + + SELECT_DEVICEDATA( m_device->m_type, copy(this, &buffer, min2( m_size, buffer.m_size) ) ); + + return *this; +} +*/ + +template +__inline +static +typename Buffer* BufferUtils::map(const Device* device, const Buffer* in, int copySize) +{ + Buffer* native; + ADLASSERT( device->m_type == TYPE ); + + if( in->getType() == TYPE ) + native = (Buffer*)in; + else + { + ADLASSERT( copySize <= in->getSize() ); + copySize = (copySize==-1)? in->getSize() : copySize; + + native = new Buffer( device, copySize ); + if( COPY ) + { + if( in->getType() == TYPE_HOST ) + native->write( in->m_ptr, copySize ); + else if( native->getType() == TYPE_HOST ) + { + in->read( native->m_ptr, copySize ); + DeviceUtils::waitForCompletion( in->m_device ); + } + else + { + T* tmp = new T[copySize]; + in->read( tmp, copySize ); + DeviceUtils::waitForCompletion( in->m_device ); + native->write( tmp, copySize ); + DeviceUtils::waitForCompletion( native->m_device ); + delete [] tmp; + } + } + } + return native; +} + +template +__inline +static +void BufferUtils::unmap( Buffer* native, const Buffer* orig, int copySize ) +{ + if( native != orig ) + { + if( COPY ) + { + copySize = (copySize==-1)? orig->getSize() : copySize; + ADLASSERT( copySize <= orig->getSize() ); + if( orig->getType() == TYPE_HOST ) + { + native->read( orig->m_ptr, copySize ); + DeviceUtils::waitForCompletion( native->m_device ); + } + else if( native->getType() == TYPE_HOST ) + { + Buffer* dst = (Buffer*)orig; + dst->write( native->m_ptr, copySize ); + DeviceUtils::waitForCompletion( dst->m_device ); + } + else + { + T* tmp = new T[copySize]; + native->read( tmp, copySize ); + DeviceUtils::waitForCompletion( native->m_device ); + Buffer* dst = (Buffer*)orig; + dst->write( tmp, copySize ); + DeviceUtils::waitForCompletion( dst->m_device ); + delete [] tmp; + } + } + delete native; + } +} + + +template +T& HostBuffer::operator[](int idx) +{ + return m_ptr[idx]; +} + +template +const T& HostBuffer::operator[](int idx) const +{ + return m_ptr[idx]; +} + +template +HostBuffer& HostBuffer::operator = ( const Buffer& device ) +{ + ADLASSERT( device.m_size <= m_size ); + + SELECT_DEVICEDATA1( device.m_device, copy( m_ptr, &device, device.m_size ) ); + + return *this; +} + +#undef SELECT_DEVICEDATA + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlConfig.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlConfig.h new file mode 100644 index 000000000..141c874c9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlConfig.h @@ -0,0 +1,27 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +//ADL_ENABLE_CL and ADL_ENABLE_DX11 can be set in the build system using C/C++ preprocessor defines +//#define ADL_ENABLE_CL +//#define ADL_ENABLE_DX11 + +//#define ADL_CL_FORCE_UNCACHE_KERNEL +#define ADL_CL_DUMP_MEMORY_LOG + +//load the kernels from string instead of loading them from file +#define ADL_LOAD_KERNEL_FROM_STRING +#define ADL_DUMP_DX11_ERROR diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlError.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlError.h new file mode 100644 index 000000000..6d08e95a8 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlError.h @@ -0,0 +1,80 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ADL_ERROR_H +#define ADL_ERROR_H + +#if defined(ADL_DUMP_DX11_ERROR) + #include +#endif +#ifdef _DEBUG + #include + #include + #include +#endif + + +namespace adl +{ + +#ifdef _DEBUG + #define ADLASSERT(x) if(!(x)){__debugbreak(); } +#else + #define ADLASSERT(x) if(x){} +#endif + +#ifdef _DEBUG + #define COMPILE_TIME_ASSERT(x) {int compileTimeAssertFailed[x]; compileTimeAssertFailed[0];} +#else + #define COMPILE_TIME_ASSERT(x) +#endif + +#ifdef _DEBUG + __inline + void debugPrintf(const char *fmt, ...) + { + va_list arg; + va_start(arg, fmt); +#if defined(ADL_DUMP_DX11_ERROR) + const int size = 1024*10; + char buf[size]; + vsprintf_s( buf, size, fmt, arg ); +#ifdef UNICODE + WCHAR wbuf[size]; + int sizeWide = MultiByteToWideChar(0,0,buf,-1,wbuf,0); + MultiByteToWideChar(0,0,buf,-1,wbuf,sizeWide); + +// swprintf_s( wbuf, 256, L"%s", buf ); + OutputDebugString( wbuf ); +#else + OutputDebugString( buf ); +#endif +#else + vprintf(fmt, arg); +#endif + va_end(arg); + } +#else + __inline + void debugPrintf(const char *fmt, ...) + { + } +#endif + +}; + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.h new file mode 100644 index 000000000..1a785c1be --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.h @@ -0,0 +1,142 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef ADL_KERNEL_H +#define ADL_KERNEL_H + +#include +#include +#include + +namespace adl +{ + +//========================== +// Kernel +//========================== +struct Kernel +{ + DeviceType m_type; + void* m_kernel; +}; + +//========================== +// KernelManager +//========================== +class KernelManager +{ + public: + typedef std::map KMap; + + __inline + ~KernelManager(); + + __inline +// static + Kernel* query(const Device* dd, const char* fileName, const char* funcName, const char* option = NULL, const char* src = NULL, + bool cacheKernel = true); + + public: + KMap m_map; +}; + +//========================== +// Launcher +//========================== +class Launcher +{ + public: + struct BufferInfo + { + BufferInfo(){} + template + BufferInfo(Buffer* buff, bool isReadOnly = false): m_buffer(buff), m_isReadOnly(isReadOnly){} + + void* m_buffer; + bool m_isReadOnly; + }; + + __inline + Launcher(const Device* dd, char* fileName, char* funcName, char* option = NULL); + __inline + Launcher(const Device* dd, Kernel* kernel); + __inline + void setBuffers( BufferInfo* buffInfo, int n ); + template + __inline + void setConst( Buffer& constBuff, const T& consts ); + __inline + void launch1D( int numThreads, int localSize = 64 ); + __inline + void launch2D( int numThreadsX, int numThreadsY, int localSizeX = 8, int localSizeY = 8 ); + + public: + enum + { + CONST_BUFFER_SIZE = 512, + }; + + const Device* m_deviceData; + Kernel* m_kernel; + int m_idx; + int m_idxRw; +}; + +template +class KernelBuilder +{ + public: + + __inline + KernelBuilder(): m_ptr(0){} + + __inline + void setFromFile( const Device* deviceData, const char* fileName, const char* option = NULL, bool addExtension = false, + bool cacheKernel = true); + + __inline + void setFromSrc( const Device* deviceData, const char* src, const char* option = NULL ); + + __inline + void setFromSrcCached( const Device* deviceData, const char* src, const char* fileName, const char* option ); + + + __inline + void createKernel( const char* funcName, Kernel& kernelOut ); + + __inline + ~KernelBuilder(); + // todo. implemement in kernel destructor? + __inline + static void deleteKernel( Kernel& kernel ); + + private: + enum + { + MAX_PATH_LENGTH = 260, + }; + const Device* m_deviceData; +#ifdef UNICODE + wchar_t m_path[MAX_PATH_LENGTH]; +#else + char m_path[MAX_PATH_LENGTH]; +#endif + void* m_ptr; +}; + +}; + +#endif //ADL_KERNEL_H diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.inl new file mode 100644 index 000000000..9752b8cf6 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlKernel.inl @@ -0,0 +1,223 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +#ifdef ADL_ENABLE_CL + #include +#endif +#ifdef ADL_ENABLE_DX11 + #include +#endif + +namespace adl +{ + +//========================== +// KernelManager +//========================== +Kernel* KernelManager::query(const Device* dd, const char* fileName, const char* funcName, const char* option, const char* src, + bool cacheKernel) +{ + printf("compiling kernel %s",funcName); + const int charSize = 1024*2; + KernelManager* s_kManager = this; + + char fullFineName[charSize]; + switch( dd->m_type ) + { + case TYPE_CL: +#if defined(ADL_ENABLE_CL) + sprintf_s(fullFineName,charSize,"%s.cl", fileName); + break; +#endif +#if defined(ADL_ENABLE_DX11) + case TYPE_DX11: + sprintf_s(fullFineName,charSize,"%s.hlsl", fileName); + break; +#endif + default: + ADLASSERT(0); + break; + }; + + char mapName[charSize]; + { + if( option ) + sprintf_s(mapName, charSize, "%d%s%s%s", (int)dd->getContext(), fullFineName, funcName, option); + else + sprintf_s(mapName, charSize, "%d%s%s", (int)dd->getContext(), fullFineName, funcName); + } + + std::string str(mapName); + + KMap::iterator iter = s_kManager->m_map.find( str ); + + Kernel* kernelOut; + if( iter == s_kManager->m_map.end() ) + { + kernelOut = new Kernel(); + + switch( dd->m_type ) + { +#if defined(ADL_ENABLE_CL) + case TYPE_CL: + { + KernelBuilder builder; + if( src ) + if (cacheKernel) + { + builder.setFromSrcCached( dd, src, fileName, option ); + } else + { + builder.setFromSrc( dd, src, option ); + } + else + builder.setFromFile( dd, fileName, option, true, cacheKernel ); + builder.createKernel( funcName, *kernelOut ); + } + break; +#endif +#if defined(ADL_ENABLE_DX11) + case TYPE_DX11: + { + KernelBuilder builder; + if( src ) + builder.setFromSrc( dd, src, option ); + else + builder.setFromFile( dd, fileName, option, true, cacheKernel ); + builder.createKernel( funcName, *kernelOut ); + } + break; +#endif + default: + ADLASSERT(0); + break; + }; + s_kManager->m_map.insert( KMap::value_type(str,kernelOut) ); + } + else + { + kernelOut = iter->second; + } + + printf(" ready\n"); + return kernelOut; +} + +KernelManager::~KernelManager() +{ + for(KMap::iterator iter = m_map.begin(); iter != m_map.end(); iter++) + { + Kernel* k = iter->second; + switch( k->m_type ) + { +#if defined(ADL_ENABLE_CL) + case TYPE_CL: + KernelBuilder::deleteKernel( *k ); + delete k; + break; +#endif +#if defined(ADL_ENABLE_DX11) + case TYPE_DX11: + KernelBuilder::deleteKernel( *k ); + delete k; + break; +#endif + default: + ADLASSERT(0); + break; + }; + } +} + +//========================== +// Launcher +//========================== + +#if defined(ADL_ENABLE_DX11) + #if defined(ADL_ENABLE_CL) + #define SELECT_LAUNCHER( type, func ) \ + switch( type ) \ + { \ + case TYPE_CL: LauncherCL::func; break; \ + case TYPE_DX11: LauncherDX11::func; break; \ + default: ADLASSERT(0); break; \ + }; + #else + #define SELECT_LAUNCHER( type, func ) \ + switch( type ) \ + { \ + case TYPE_DX11: LauncherDX11::func; break; \ + default: ADLASSERT(0); break; \ + }; + #endif +#else + #if defined(ADL_ENABLE_CL) + #define SELECT_LAUNCHER( type, func ) \ + switch( type ) \ + { \ + case TYPE_CL: LauncherCL::func; break; \ + default: ADLASSERT(0); break; \ + }; + #else + #define SELECT_LAUNCHER( type, func ) \ + switch( type ) \ + { \ + default: ADLASSERT(0); break; \ + }; + #endif +#endif + +Launcher::Launcher(const Device *dd, char *fileName, char *funcName, char *option) +{ + m_kernel = dd->getKernel( fileName, funcName, option ); + m_deviceData = dd; + m_idx = 0; + m_idxRw = 0; +} + +Launcher::Launcher(const Device* dd, Kernel* kernel) +{ + m_kernel = kernel; + m_deviceData = dd; + m_idx = 0; + m_idxRw = 0; +} + +void Launcher::setBuffers( BufferInfo* buffInfo, int n ) +{ + SELECT_LAUNCHER( m_deviceData->m_type, setBuffers( this, buffInfo, n ) ); +} + +template +void Launcher::setConst( Buffer& constBuff, const T& consts ) +{ + SELECT_LAUNCHER( m_deviceData->m_type, setConst( this, constBuff, consts ) ); +} + +void Launcher::launch1D( int numThreads, int localSize ) +{ + SELECT_LAUNCHER( m_deviceData->m_type, launch2D( this, numThreads, 1, localSize, 1 ) ); +} + +void Launcher::launch2D( int numThreadsX, int numThreadsY, int localSizeX, int localSizeY ) +{ + SELECT_LAUNCHER( m_deviceData->m_type, launch2D( this, numThreadsX, numThreadsY, localSizeX, localSizeY ) ); +} + +#undef SELECT_LAUNCHER + +}; \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.h new file mode 100644 index 000000000..034f044a1 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.h @@ -0,0 +1,81 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +#include + +namespace adl +{ + +struct StopwatchBase +{ + __inline + StopwatchBase(): m_device(0){} + __inline + StopwatchBase( const Device* deviceData ){ init(deviceData); } + __inline + virtual ~StopwatchBase(){} + + __inline + virtual void init( const Device* deviceData ) = 0; + __inline + virtual void start() = 0; + __inline + virtual void split() = 0; + __inline + virtual void stop() = 0; + __inline + virtual float getMs(int index=0) = 0; + __inline + virtual void getMs( float* times, int capacity ) = 0; + __inline + int getNIntervals() const{ return m_idx-1;} + + enum + { + CAPACITY = 64, + }; + + const Device* m_device; + int m_idx; +}; + +struct Stopwatch +{ + __inline + Stopwatch( const Device* deviceData = NULL ) { m_impl=0; if(deviceData) init(deviceData);} + __inline + ~Stopwatch(); + + __inline + void init( const Device* deviceData ); + __inline + void start(){if(!m_impl) init(0); m_impl->start();} + __inline + void split(){m_impl->split();} + __inline + void stop(){m_impl->stop();} + __inline + float getMs(){ return m_impl->getMs();} + __inline + void getMs( float* times, int capacity ){m_impl->getMs(times, capacity);} + __inline + int getNIntervals() const{return m_impl->getNIntervals();} + + StopwatchBase* m_impl; +}; + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.inl new file mode 100644 index 000000000..a825ec2c9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/AdlStopwatch.inl @@ -0,0 +1,59 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +namespace adl +{ + +void Stopwatch::init( const Device* deviceData ) +{ + ADLASSERT( m_impl == 0 ); + + if( deviceData ) + { + switch( deviceData->m_type ) + { +#if defined(ADL_ENABLE_CL) + case TYPE_CL: + m_impl = new StopwatchHost;//StopwatchCL + break; +#endif +#if defined(ADL_ENABLE_DX11) + case TYPE_DX11: + m_impl = new StopwatchHost;//StopwatchDX11; + break; +#endif + case TYPE_HOST: + m_impl = new StopwatchHost; + break; + default: + ADLASSERT(0); + break; + }; + } + else + { + m_impl = new StopwatchHost; + } + m_impl->init( deviceData ); +} + +Stopwatch::~Stopwatch() +{ + if( m_impl == 0 ) return; + delete m_impl; +} + +}; \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlCL.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlCL.inl new file mode 100644 index 000000000..1b603a9c4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlCL.inl @@ -0,0 +1,384 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +#pragma comment(lib,"OpenCL.lib") +#include +#include +#include + +namespace adl +{ + +struct DeviceCL : public Device +{ + typedef DeviceUtils::Config Config; + + + __inline + DeviceCL() : Device( TYPE_CL ), m_kernelManager(0){} + __inline + void* getContext() const { return m_context; } + __inline + void initialize(const Config& cfg); + __inline + void release(); + + template + __inline + void allocate(Buffer* buf, int nElems, BufferBase::BufferType type); + + template + __inline + void deallocate(Buffer* buf); + + template + __inline + void copy(Buffer* dst, const Buffer* src, int nElems,int srcOffsetNElems = 0,int dstOffsetNElems = 0); + + template + __inline + void copy(T* dst, const Buffer* src, int nElems, int srcOffsetNElems = 0); + + template + __inline + void copy(Buffer* dst, const T* src, int nElems, int dstOffsetNElems = 0); + + __inline + void waitForCompletion() const; + + __inline + void getDeviceName( char nameOut[128] ) const; + + __inline + static + int getNDevices(); + + __inline + Kernel* getKernel(const char* fileName, const char* funcName, const char* option = NULL, const char* src = NULL, bool cacheKernel = true )const; + + + enum + { + MAX_NUM_DEVICES = 6, + }; + + cl_context m_context; + cl_command_queue m_commandQueue; + + cl_device_id m_deviceIdx; + + KernelManager* m_kernelManager; +}; + +//=== +//=== + +void DeviceCL::initialize(const Config& cfg) +{ +// DeviceUtils::create( cfg, (DeviceCL*)this ); + { +// dd = new DeviceCL(); + + DeviceCL* deviceData = (DeviceCL*)this; + +// cl_device_type deviceType = (driverType == DRIVER_HARDWARE)? CL_DEVICE_TYPE_GPU:CL_DEVICE_TYPE_CPU; + cl_device_type deviceType = (cfg.m_type== Config::DEVICE_GPU)? CL_DEVICE_TYPE_GPU: CL_DEVICE_TYPE_CPU; +// int numContextQueuePairsToCreate = 1; + bool enableProfiling = false; +#ifdef _DEBUG + enableProfiling = true; +#endif + cl_int status; + + cl_platform_id platform; + { + cl_uint nPlatforms = 0; + status = clGetPlatformIDs(0, NULL, &nPlatforms); + ADLASSERT( status == CL_SUCCESS ); + + cl_platform_id pIdx[5]; + status = clGetPlatformIDs(nPlatforms, pIdx, NULL); + ADLASSERT( status == CL_SUCCESS ); + + cl_uint atiIdx = -1; + cl_uint intelIdx = -1; + cl_uint nvIdx = -1; + + for(cl_uint i=0; i0) + { + if( strcmp( buff, "NVIDIA Corporation" )==0 ) nvIdx = i; + if( strcmp( buff, "Advanced Micro Devices, Inc." )==0 ) atiIdx = i; + if( strcmp( buff, "Intel(R) Corporation" )==0 ) intelIdx = i; + } + } + + if( deviceType == CL_DEVICE_TYPE_GPU ) + { + switch( cfg.m_vendor ) + { + case DeviceUtils::Config::VD_AMD: + if( atiIdx == -1 && nvIdx != -1 ) goto USE_NV_GPU; +USE_AMD_GPU: + ADLASSERT(atiIdx != -1 ); + platform = pIdx[atiIdx]; + break; + case DeviceUtils::Config::VD_NV: + if( atiIdx != -1 && nvIdx == -1 ) goto USE_AMD_GPU; +USE_NV_GPU: + ADLASSERT(nvIdx != -1 ); + platform = pIdx[nvIdx]; + break; + default: + ADLASSERT(0); + break; + }; + } + else if( deviceType == CL_DEVICE_TYPE_CPU ) + { + switch( cfg.m_vendor ) + { + case DeviceUtils::Config::VD_AMD: + ADLASSERT(atiIdx != -1 ); + platform = pIdx[atiIdx]; + break; + case DeviceUtils::Config::VD_INTEL: + ADLASSERT(intelIdx != -1 ); + platform = pIdx[intelIdx]; + break; + default: + ADLASSERT(0); + break; + }; + } + } + + cl_uint numDevice; + status = clGetDeviceIDs( platform, deviceType, 0, NULL, &numDevice ); + +// ADLASSERT( cfg.m_deviceIdx < (int)numDevice ); + + debugPrintf("CL: %d %s Devices ", numDevice, (deviceType==CL_DEVICE_TYPE_GPU)? "GPU":"CPU"); + +// numContextQueuePairsToCreate = min( (int)numDevice, numContextQueuePairsToCreate ); +// numContextQueuePairsToCreate = ( (int)numDevice < numContextQueuePairsToCreate )? numDevice : numContextQueuePairsToCreate; + + cl_device_id deviceIds[ MAX_NUM_DEVICES ]; + + status = clGetDeviceIDs( platform, deviceType, numDevice, deviceIds, NULL ); + ADLASSERT( status == CL_SUCCESS ); + + { int i = min( (int)numDevice-1, cfg.m_deviceIdx ); + m_deviceIdx = deviceIds[i]; + deviceData->m_context = clCreateContext( NULL, 1, &deviceData->m_deviceIdx, NULL, NULL, &status ); + ADLASSERT( status == CL_SUCCESS ); + + char buff[512]; + status = clGetDeviceInfo( deviceData->m_deviceIdx, CL_DEVICE_NAME, sizeof(buff), &buff, NULL ); + ADLASSERT( status == CL_SUCCESS ); + + debugPrintf("[%s]\n", buff); + + deviceData->m_commandQueue = clCreateCommandQueue( deviceData->m_context, deviceData->m_deviceIdx, (enableProfiling)?CL_QUEUE_PROFILING_ENABLE:NULL, NULL ); + + ADLASSERT( status == CL_SUCCESS ); + + // status = clSetCommandQueueProperty( commandQueue, CL_QUEUE_PROFILING_ENABLE, CL_TRUE, 0 ); + // CLASSERT( status == CL_SUCCESS ); + + if(0) + { + cl_bool image_support; + clGetDeviceInfo(deviceData->m_deviceIdx, CL_DEVICE_IMAGE_SUPPORT, sizeof(image_support), &image_support, NULL); + debugPrintf(" CL_DEVICE_IMAGE_SUPPORT : %s\n", image_support?"Yes":"No"); + } + } + } + + m_kernelManager = new KernelManager; +} + +void DeviceCL::release() +{ + clReleaseCommandQueue( m_commandQueue ); + clReleaseContext( m_context ); + + if( m_kernelManager ) delete m_kernelManager; +} + +template +void DeviceCL::allocate(Buffer* buf, int nElems, BufferBase::BufferType type) +{ + buf->m_device = this; + buf->m_size = nElems; + buf->m_ptr = 0; + + if( type == BufferBase::BUFFER_CONST ) return; + +#if defined(ADL_CL_DUMP_MEMORY_LOG) + char deviceName[256]; + getDeviceName( deviceName ); + printf( "adlCLMemoryLog %s : %3.2fMB Allocation: %3.2fKB ", deviceName, m_memoryUsage/1024.f/1024.f, sizeof(T)*nElems/1024.f ); + fflush( stdout ); +#endif + + int sz=sizeof(T)*nElems; + + cl_int status = 0; + if( type == BufferBase::BUFFER_ZERO_COPY ) + buf->m_ptr = (T*)clCreateBuffer( m_context, CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR, sz, 0, &status ); + else if( type == BufferBase::BUFFER_RAW ) + buf->m_ptr = (T*)clCreateBuffer( m_context, CL_MEM_WRITE_ONLY, sz, 0, &status ); + else + buf->m_ptr = (T*)clCreateBuffer( m_context, CL_MEM_READ_WRITE, sz, 0, &status ); + + m_memoryUsage += buf->m_size*sizeof(T); +#if defined(ADL_CL_DUMP_MEMORY_LOG) + printf( "%s\n", (status==CL_SUCCESS)? "Succeed": "Failed" ); + fflush( stdout ); +#endif + ADLASSERT( status == CL_SUCCESS ); +} + +template +void DeviceCL::deallocate(Buffer* buf) +{ + if( buf->m_ptr ) + { + m_memoryUsage -= buf->m_size*sizeof(T); + clReleaseMemObject( (cl_mem)buf->m_ptr ); + } + buf->m_device = 0; + buf->m_size = 0; + buf->m_ptr = 0; +} + +template +void DeviceCL::copy(Buffer* dst, const Buffer* src, int nElems,int srcOffsetNElems,int dstOffsetNElems ) +{ + if( dst->m_device->m_type == TYPE_CL && src->m_device->m_type == TYPE_CL ) + { + cl_int status = 0; + status = clEnqueueCopyBuffer( m_commandQueue, (cl_mem)src->m_ptr, (cl_mem)dst->m_ptr, sizeof(T)*srcOffsetNElems, sizeof(T)*dstOffsetNElems, sizeof(T)*nElems, 0, 0, 0 ); + ADLASSERT( status == CL_SUCCESS ); + } + else if( src->m_device->m_type == TYPE_HOST ) + { + ADLASSERT( dst->getType() == TYPE_CL ); + dst->write( src->m_ptr, nElems ); + } + else if( dst->m_device->m_type == TYPE_HOST ) + { + ADLASSERT( src->getType() == TYPE_CL ); + src->read( dst->m_ptr, nElems ); + } + else + { + ADLASSERT( 0 ); + } +} + +template +void DeviceCL::copy(T* dst, const Buffer* src, int nElems, int srcOffsetNElems ) +{ + cl_int status = 0; + status = clEnqueueReadBuffer( m_commandQueue, (cl_mem)src->m_ptr, 0, sizeof(T)*srcOffsetNElems, sizeof(T)*nElems, + dst, 0,0,0 ); + ADLASSERT( status == CL_SUCCESS ); +} + +template +void DeviceCL::copy(Buffer* dst, const T* src, int nElems, int dstOffsetNElems ) +{ + cl_int status = 0; + int sz=sizeof(T)*nElems; + status = clEnqueueWriteBuffer( m_commandQueue, (cl_mem)dst->m_ptr, 0, sizeof(T)*dstOffsetNElems, sz, + src, 0,0,0 ); + ADLASSERT( status == CL_SUCCESS ); +} + +void DeviceCL::waitForCompletion() const +{ + clFinish( m_commandQueue ); +} + +int DeviceCL::getNDevices() +{ + cl_device_type deviceType = CL_DEVICE_TYPE_GPU; + cl_int status; + + cl_platform_id platform; + { + cl_uint nPlatforms = 0; + status = clGetPlatformIDs(0, NULL, &nPlatforms); + ADLASSERT( status == CL_SUCCESS ); + + cl_platform_id pIdx[5]; + status = clGetPlatformIDs(nPlatforms, pIdx, NULL); + ADLASSERT( status == CL_SUCCESS ); + + cl_uint nvIdx = -1; + cl_uint atiIdx = -1; + for(cl_uint i=0; iquery( this, fileName, funcName, option, src, cacheKernel ); +} + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlKernelUtilsCL.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlKernelUtilsCL.inl new file mode 100644 index 000000000..513478a35 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/CL/AdlKernelUtilsCL.inl @@ -0,0 +1,541 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + + + +namespace adl +{ + +struct KernelCL : public Kernel +{ + cl_kernel& getKernel() { return (cl_kernel&)m_kernel; } +}; + +static const char* strip(const char* name, const char* pattern) +{ + size_t const patlen = strlen(pattern); + size_t patcnt = 0; + const char * oriptr; + const char * patloc; + // find how many times the pattern occurs in the original string + for (oriptr = name; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen) + { + patcnt++; + } + return oriptr; +} + +static bool isFileUpToDate(const char* binaryFileName,const char* srcFileName) + +{ + bool fileUpToDate = false; + + bool binaryFileValid=false; + FILETIME modtimeBinary; + + int nameLength = (int)strlen(binaryFileName)+1; +#ifdef UNICODE + WCHAR* fName = new WCHAR[nameLength]; + MultiByteToWideChar(CP_ACP,0,binaryFileName,-1, fName, nameLength); + HANDLE binaryFileHandle = CreateFile(fName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); + delete [] fName; +#else + HANDLE binaryFileHandle = CreateFile(binaryFileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); +#endif + if (binaryFileHandle ==INVALID_HANDLE_VALUE) + { + DWORD errorCode; + errorCode = GetLastError(); + switch (errorCode) + { + case ERROR_FILE_NOT_FOUND: + { + debugPrintf("\nCached file not found %s\n", binaryFileName); + break; + } + case ERROR_PATH_NOT_FOUND: + { + debugPrintf("\nCached file path not found %s\n", binaryFileName); + break; + } + default: + { + debugPrintf("\nFailed reading cached file with errorCode = %d\n", errorCode); + } + } + } else + { + if (GetFileTime(binaryFileHandle, NULL, NULL, &modtimeBinary)==0) + { + DWORD errorCode; + errorCode = GetLastError(); + debugPrintf("\nGetFileTime errorCode = %d\n", errorCode); + } else + { + binaryFileValid = true; + } + CloseHandle(binaryFileHandle); + } + + if (binaryFileValid) + { +#ifdef UNICODE + int nameLength = (int)strlen(srcFileName)+1; + WCHAR* fName = new WCHAR[nameLength]; + MultiByteToWideChar(CP_ACP,0,srcFileName,-1, fName, nameLength); + HANDLE srcFileHandle = CreateFile(fName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); + delete [] fName; +#else + HANDLE srcFileHandle = CreateFile(srcFileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); +#endif + if (srcFileHandle!=INVALID_HANDLE_VALUE) + { + FILETIME modtimeSrc; + if (GetFileTime(srcFileHandle, NULL, NULL, &modtimeSrc)==0) + { + DWORD errorCode; + errorCode = GetLastError(); + debugPrintf("\nGetFileTime errorCode = %d\n", errorCode); + } + if ( ( modtimeSrc.dwHighDateTime < modtimeBinary.dwHighDateTime) + ||(( modtimeSrc.dwHighDateTime == modtimeBinary.dwHighDateTime)&&(modtimeSrc.dwLowDateTime <= modtimeBinary.dwLowDateTime))) + { + fileUpToDate=true; + } else + { + debugPrintf("\nCached binary file found (%s), but out-of-date\n",binaryFileName); + } + CloseHandle(srcFileHandle); + } + else + { +#ifdef _DEBUG + DWORD errorCode; + errorCode = GetLastError(); + switch (errorCode) + { + case ERROR_FILE_NOT_FOUND: + { + debugPrintf("\nSrc file not found %s\n", srcFileName); + break; + } + case ERROR_PATH_NOT_FOUND: + { + debugPrintf("\nSrc path not found %s\n", srcFileName); + break; + } + default: + { + debugPrintf("\nnSrc file reading errorCode = %d\n", errorCode); + } + } + ADLASSERT(0); +#else + //if we cannot find the source, assume it is OK in release builds + fileUpToDate = true; +#endif + } + } + + + return fileUpToDate; +} + +template<> +void KernelBuilder::setFromFile( const Device* deviceData, const char* fileName, const char* option, bool addExtension, + bool cacheKernel) +{ + m_deviceData = deviceData; + + char fileNameWithExtension[256]; + + if( addExtension ) + sprintf_s( fileNameWithExtension, "%s.cl", fileName ); + else + sprintf_s( fileNameWithExtension, "%s", fileName ); + + class File + { + public: + __inline + bool open(const char* fileNameWithExtension) + { + size_t size; + char* str; + + // Open file stream + std::fstream f(fileNameWithExtension, (std::fstream::in | std::fstream::binary)); + + // Check if we have opened file stream + if (f.is_open()) { + size_t sizeFile; + // Find the stream size + f.seekg(0, std::fstream::end); + size = sizeFile = (size_t)f.tellg(); + f.seekg(0, std::fstream::beg); + + str = new char[size + 1]; + if (!str) { + f.close(); + return NULL; + } + + // Read file + f.read(str, sizeFile); + f.close(); + str[size] = '\0'; + + m_source = str; + + delete[] str; + + return true; + } + + return false; + } + const std::string& getSource() const {return m_source;} + + private: + std::string m_source; + }; + + cl_program& program = (cl_program&)m_ptr; + cl_int status = 0; + + bool cacheBinary = cacheKernel; +#if defined(ADL_CL_FORCE_UNCACHE_KERNEL) + cacheBinary = false; +#endif + + char binaryFileName[512]; + { + char deviceName[256]; + deviceData->getDeviceName(deviceName); + char driverVersion[256]; + const DeviceCL* dd = (const DeviceCL*) deviceData; + clGetDeviceInfo(dd->m_deviceIdx, CL_DRIVER_VERSION, 256, &driverVersion, NULL); + const char* strippedFileName = strip(fileName,"\\"); + strippedFileName = strip(strippedFileName,"/"); + + sprintf_s(binaryFileName,"cache/%s.%s.%s.bin",strippedFileName, deviceName,driverVersion ); + } + + bool upToDate = isFileUpToDate(binaryFileName,fileNameWithExtension); + + if( cacheBinary && upToDate) + { + FILE* file = fopen(binaryFileName, "rb"); + + if( file ) + { + fseek( file, 0L, SEEK_END ); + size_t binarySize = ftell( file ); + + rewind( file ); + char* binary = new char[binarySize]; + fread( binary, sizeof(char), binarySize, file ); + fclose( file ); + + if (binarySize) + { + const DeviceCL* dd = (const DeviceCL*) deviceData; + program = clCreateProgramWithBinary( dd->m_context, 1, &dd->m_deviceIdx, &binarySize, (const unsigned char**)&binary, 0, &status ); + ADLASSERT( status == CL_SUCCESS ); + status = clBuildProgram( program, 1, &dd->m_deviceIdx, option, 0, 0 ); + ADLASSERT( status == CL_SUCCESS ); + if( status != CL_SUCCESS ) + { + char *build_log; + size_t ret_val_size; + clGetProgramBuildInfo(program, dd->m_deviceIdx, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size); + build_log = new char[ret_val_size+1]; + clGetProgramBuildInfo(program, dd->m_deviceIdx, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL); + + build_log[ret_val_size] = '\0'; + + debugPrintf("%s\n", build_log); + + delete build_log; + ADLASSERT(0); + } + + } + } + } + if( !m_ptr ) + { + File kernelFile; + ADLASSERT( kernelFile.open( fileNameWithExtension ) ); + const char* source = kernelFile.getSource().c_str(); + setFromSrc( m_deviceData, source, option ); + + if( cacheBinary ) + { // write to binary + size_t binarySize; + status = clGetProgramInfo( program, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &binarySize, 0 ); + ADLASSERT( status == CL_SUCCESS ); + + char* binary = new char[binarySize]; + + status = clGetProgramInfo( program, CL_PROGRAM_BINARIES, sizeof(char*), &binary, 0 ); + ADLASSERT( status == CL_SUCCESS ); + + { + FILE* file = fopen(binaryFileName, "wb"); + if (file) + { + fwrite( binary, sizeof(char), binarySize, file ); + fclose( file ); + } + } + + delete [] binary; + } + } +} + + + +template<> +void KernelBuilder::setFromSrcCached( const Device* deviceData, const char* src, const char* fileName, const char* option ) +{ + m_deviceData = deviceData; + + bool cacheBinary = true; + cl_program& program = (cl_program&)m_ptr; + cl_int status = 0; + + char binaryFileName[512]; + { + char deviceName[256]; + deviceData->getDeviceName(deviceName); + char driverVersion[256]; + const DeviceCL* dd = (const DeviceCL*) deviceData; + clGetDeviceInfo(dd->m_deviceIdx, CL_DRIVER_VERSION, 256, &driverVersion, NULL); + + const char* strippedFileName = strip(fileName,"\\"); + strippedFileName = strip(strippedFileName,"/"); + + sprintf_s(binaryFileName,"cache/%s.%s.%s.bin",strippedFileName, deviceName,driverVersion ); + } + + + char fileNameWithExtension[256]; + sprintf_s(fileNameWithExtension,"%s.cl",fileName, ".cl"); + + bool upToDate = isFileUpToDate(binaryFileName,fileNameWithExtension); + + + if( cacheBinary ) + { + + bool fileUpToDate = isFileUpToDate(binaryFileName,fileNameWithExtension); + + if( fileUpToDate) + { + FILE* file = fopen(binaryFileName, "rb"); + if (file) + { + fseek( file, 0L, SEEK_END ); + size_t binarySize = ftell( file ); + rewind( file ); + char* binary = new char[binarySize]; + fread( binary, sizeof(char), binarySize, file ); + fclose( file ); + + const DeviceCL* dd = (const DeviceCL*) deviceData; + program = clCreateProgramWithBinary( dd->m_context, 1, &dd->m_deviceIdx, &binarySize, (const unsigned char**)&binary, 0, &status ); + ADLASSERT( status == CL_SUCCESS ); + status = clBuildProgram( program, 1, &dd->m_deviceIdx, option, 0, 0 ); + ADLASSERT( status == CL_SUCCESS ); + + if( status != CL_SUCCESS ) + { + char *build_log; + size_t ret_val_size; + clGetProgramBuildInfo(program, dd->m_deviceIdx, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size); + build_log = new char[ret_val_size+1]; + clGetProgramBuildInfo(program, dd->m_deviceIdx, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL); + + build_log[ret_val_size] = '\0'; + + debugPrintf("%s\n", build_log); + + delete build_log; + ADLASSERT(0); + } + delete[] binary; + } + } + } + + + if( !m_ptr ) + { + + setFromSrc( deviceData, src, option ); + + if( cacheBinary ) + { // write to binary + cl_uint numAssociatedDevices; + status = clGetProgramInfo( program, CL_PROGRAM_NUM_DEVICES, sizeof(cl_uint), &numAssociatedDevices, 0 ); + ADLASSERT( status == CL_SUCCESS ); + if (numAssociatedDevices==1) + { + + + size_t binarySize; + status = clGetProgramInfo( program, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &binarySize, 0 ); + ADLASSERT( status == CL_SUCCESS ); + + char* binary = new char[binarySize]; + + status = clGetProgramInfo( program, CL_PROGRAM_BINARIES, sizeof(char*), &binary, 0 ); + ADLASSERT( status == CL_SUCCESS ); + + { + FILE* file = fopen(binaryFileName, "wb"); + if (file) + { + fwrite( binary, sizeof(char), binarySize, file ); + fclose( file ); + } + } + + delete [] binary; + } + } + } +} + + +template<> +void KernelBuilder::setFromSrc( const Device* deviceData, const char* src, const char* option ) +{ + ADLASSERT( deviceData->m_type == TYPE_CL ); + m_deviceData = deviceData; + const DeviceCL* dd = (const DeviceCL*) deviceData; + + cl_program& program = (cl_program&)m_ptr; + cl_int status = 0; + size_t srcSize[] = {strlen( src )}; + program = clCreateProgramWithSource( dd->m_context, 1, &src, srcSize, &status ); + ADLASSERT( status == CL_SUCCESS ); + status = clBuildProgram( program, 1, &dd->m_deviceIdx, option, NULL, NULL ); + if( status != CL_SUCCESS ) + { + char *build_log; + size_t ret_val_size; + clGetProgramBuildInfo(program, dd->m_deviceIdx, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size); + build_log = new char[ret_val_size+1]; + clGetProgramBuildInfo(program, dd->m_deviceIdx, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL); + + build_log[ret_val_size] = '\0'; + + debugPrintf("%s\n", build_log); + printf("%s\n", build_log); + + ADLASSERT(0); + delete build_log; + + } +} + +template<> +KernelBuilder::~KernelBuilder() +{ + cl_program program = (cl_program)m_ptr; + clReleaseProgram( program ); +} + +template<> +void KernelBuilder::createKernel( const char* funcName, Kernel& kernelOut ) +{ + KernelCL* clKernel = (KernelCL*)&kernelOut; + + cl_program program = (cl_program)m_ptr; + cl_int status = 0; + clKernel->getKernel() = clCreateKernel(program, funcName, &status ); + ADLASSERT( status == CL_SUCCESS ); + + kernelOut.m_type = TYPE_CL; +} + +template<> +void KernelBuilder::deleteKernel( Kernel& kernel ) +{ + KernelCL* clKernel = (KernelCL*)&kernel; + clReleaseKernel( clKernel->getKernel() ); +} + + + +class LauncherCL +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + __inline + static void setBuffers( Launcher* launcher, BufferInfo* buffInfo, int n ); + template + __inline + static void setConst( Launcher* launcher, Buffer& constBuff, const T& consts ); + __inline + static void launch2D( Launcher* launcher, int numThreadsX, int numThreadsY, int localSizeX, int localSizeY ); +}; + +void LauncherCL::setBuffers( Launcher* launcher, BufferInfo* buffInfo, int n ) +{ + KernelCL* clKernel = (KernelCL*)launcher->m_kernel; + for(int i=0; i* buff = (Buffer*)buffInfo[i].m_buffer; + cl_int status = clSetKernelArg( clKernel->getKernel(), launcher->m_idx++, sizeof(cl_mem), &buff->m_ptr ); + ADLASSERT( status == CL_SUCCESS ); + } +} + +template +void LauncherCL::setConst( Launcher* launcher, Buffer& constBuff, const T& consts ) +{ + KernelCL* clKernel = (KernelCL*)launcher->m_kernel; + int sz=sizeof(T); + cl_int status = clSetKernelArg( clKernel->getKernel(), launcher->m_idx++, sz, &consts ); + ADLASSERT( status == CL_SUCCESS ); +} + +void LauncherCL::launch2D( Launcher* launcher, int numThreadsX, int numThreadsY, int localSizeX, int localSizeY ) +{ + KernelCL* clKernel = (KernelCL*)launcher->m_kernel; + const DeviceCL* ddcl = (const DeviceCL*)launcher->m_deviceData; + size_t gRange[3] = {1,1,1}; + size_t lRange[3] = {1,1,1}; + lRange[0] = localSizeX; + lRange[1] = localSizeY; + gRange[0] = max((size_t)1, (numThreadsX/lRange[0])+(!(numThreadsX%lRange[0])?0:1)); + gRange[0] *= lRange[0]; + gRange[1] = max((size_t)1, (numThreadsY/lRange[1])+(!(numThreadsY%lRange[1])?0:1)); + gRange[1] *= lRange[1]; + + cl_int status = clEnqueueNDRangeKernel( ddcl->m_commandQueue, + clKernel->getKernel(), 2, NULL, gRange, lRange, 0,0,0 ); + ADLASSERT( status == CL_SUCCESS ); +} + + +}; \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlDX11.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlDX11.inl new file mode 100644 index 000000000..66abde98e --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlDX11.inl @@ -0,0 +1,512 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#include +#include +#include +#include +#include +#pragma comment(lib,"d3dx11.lib") +#pragma comment(lib,"d3d11.lib") +#pragma comment(lib,"DXGI.lib") + +namespace adl +{ + +#define u32 unsigned int + +struct DeviceDX11 : public Device +{ + typedef DeviceUtils::Config Config; + + + __inline + DeviceDX11() : Device( TYPE_DX11 ), m_kernelManager(0){} + __inline + void* getContext() const { return m_context; } + __inline + void initialize(const Config& cfg); + __inline + void release(); + + template + __inline + void allocate(Buffer* buf, int nElems, BufferBase::BufferType type); + + template + __inline + void deallocate(Buffer* buf); + + template + __inline + void copy(Buffer* dst, const Buffer* src, int nElems); + + template + __inline + void copy(T* dst, const Buffer* src, int nElems, int srcOffsetNElems = 0); + + template + __inline + void copy(Buffer* dst, const T* src, int nElems, int dstOffsetNElems = 0); + + __inline + void waitForCompletion() const; + + __inline + void getDeviceName( char nameOut[128] ) const; + + __inline + static + int getNDevices(); + + __inline + Kernel* getKernel(const char* fileName, const char* funcName, const char* option = NULL, const char* src = NULL, bool cacheKernel = true )const; + + + ID3D11DeviceContext* m_context; + ID3D11Device* m_device; + IDXGISwapChain* m_swapChain; + + KernelManager* m_kernelManager; +}; + +template +struct BufferDX11 : public Buffer +{ + ID3D11Buffer* getBuffer() { return (ID3D11Buffer*)m_ptr; } + ID3D11UnorderedAccessView* getUAV() { return (ID3D11UnorderedAccessView*)m_uav; } + ID3D11ShaderResourceView* getSRV() { return (ID3D11ShaderResourceView*)m_srv; } + + ID3D11Buffer** getBufferPtr() { return (ID3D11Buffer**)&m_ptr; } + ID3D11UnorderedAccessView** getUAVPtr() { return (ID3D11UnorderedAccessView**)&m_uav; } + ID3D11ShaderResourceView** getSRVPtr() { return (ID3D11ShaderResourceView**)&m_srv; } +}; + +#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } + + +void DeviceDX11::initialize(const Config& cfg) +{ + DeviceDX11* deviceData = this; + + HRESULT hr = S_OK; + UINT createDeviceFlg = 0; +#ifdef _DEBUG + createDeviceFlg |= D3D11_CREATE_DEVICE_DEBUG; +#endif + D3D_FEATURE_LEVEL fl[] = { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0 + }; + +typedef HRESULT (WINAPI * LPD3D11CREATEDEVICE)( IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, u32, D3D_FEATURE_LEVEL*, UINT, u32, ID3D11Device**, D3D_FEATURE_LEVEL*, ID3D11DeviceContext** ); + + HMODULE moduleD3D11 = 0; +#ifdef UNICODE + moduleD3D11 = LoadLibrary( L"d3d11.dll" ); +#else + moduleD3D11 = LoadLibrary( "d3d11.dll" ); +#endif + ADLASSERT( moduleD3D11 ); + + LPD3D11CREATEDEVICE _DynamicD3D11CreateDevice; + _DynamicD3D11CreateDevice = ( LPD3D11CREATEDEVICE )GetProcAddress( moduleD3D11, "D3D11CreateDevice" ); + + D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_HARDWARE; + // http://msdn.microsoft.com/en-us/library/ff476082(v=VS.85).aspx + // If you set the pAdapter parameter to a non-NULL value, you must also set the DriverType parameter to the D3D_DRIVER_TYPE_UNKNOWN value. If you set the pAdapter parameter to a non-NULL value and the DriverType parameter to the D3D_DRIVER_TYPE_HARDWARE value, D3D11CreateDevice returns an HRESULT of E_INVALIDARG. + type = D3D_DRIVER_TYPE_UNKNOWN; +/* + // Create a hardware Direct3D 11 device + hr = _DynamicD3D11CreateDevice( NULL, + type, NULL, createDeviceFlg, + fl, _countof(fl), D3D11_SDK_VERSION, &deviceData->m_device, NULL, &deviceData->m_context ); +*/ + IDXGIAdapter* adapter = NULL; + {// get adapter of the index + IDXGIFactory* factory = NULL; + int targetAdapterIdx = cfg.m_deviceIdx;//min( cfg.m_deviceIdx, getNDevices()-1 ); + CreateDXGIFactory( __uuidof(IDXGIFactory), (void**)&factory ); + + u32 i = 0; + while( factory->EnumAdapters( i, &adapter ) != DXGI_ERROR_NOT_FOUND ) + { + if( i== targetAdapterIdx ) break; + i++; + } + factory->Release(); + } + + // Create a hardware Direct3D 11 device + hr = D3D11CreateDevice( adapter, + type, + NULL, createDeviceFlg, + fl, _countof(fl), D3D11_SDK_VERSION, &deviceData->m_device, NULL, &deviceData->m_context ); + + ADLASSERT( hr == S_OK ); + + // Check if the hardware device supports Compute Shader 4.0 + D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS hwopts; + deviceData->m_device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &hwopts, sizeof(hwopts)); + + if( !hwopts.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x ) + { + SAFE_RELEASE( deviceData->m_context ); + SAFE_RELEASE( deviceData->m_device ); + + debugPrintf("DX11 GPU is not present\n"); + ADLASSERT( 0 ); + } + + m_kernelManager = new KernelManager; +} + +void DeviceDX11::release() +{ + SAFE_RELEASE( m_context ); + SAFE_RELEASE( m_device ); + + if( m_kernelManager ) delete m_kernelManager; +} + +template +void DeviceDX11::allocate(Buffer* buf, int nElems, BufferBase::BufferType type) +{ + ADLASSERT( type != BufferBase::BUFFER_ZERO_COPY ); + + DeviceDX11* deviceData = this; + buf->m_device = deviceData; + buf->m_size = nElems; + BufferDX11* dBuf = (BufferDX11*)buf; + +// if( type & BufferBase::BUFFER ) + { + HRESULT hr = S_OK; + + if( type == BufferBase::BUFFER_CONST ) + { + ADLASSERT( nElems == 1 ); + D3D11_BUFFER_DESC constant_buffer_desc; + ZeroMemory( &constant_buffer_desc, sizeof(constant_buffer_desc) ); +// constant_buffer_desc.ByteWidth = NEXTMULTIPLEOF( sizeof(T), 16 ); + constant_buffer_desc.ByteWidth = (((sizeof(T))/(16) + (((sizeof(T))%(16)==0)?0:1))*(16)); +// constant_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; +// constant_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; +// constant_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + constant_buffer_desc.Usage = D3D11_USAGE_DEFAULT; + constant_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + constant_buffer_desc.CPUAccessFlags = 0; + + hr = deviceData->m_device->CreateBuffer( &constant_buffer_desc, NULL, dBuf->getBufferPtr() ); + ADLASSERT( hr == S_OK ); + return; + } + + D3D11_BUFFER_DESC buffer_desc; + ZeroMemory(&buffer_desc, sizeof(buffer_desc)); + buffer_desc.ByteWidth = nElems * sizeof(T); + + if( type != BufferBase::BUFFER_RAW ) + { + buffer_desc.StructureByteStride = sizeof(T); +// buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + } + + if( type == BufferBase::BUFFER_STAGING ) + { + buffer_desc.Usage = D3D11_USAGE_STAGING; + buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + } + else if( type == BufferBase::BUFFER_INDEX ) + { + buffer_desc.Usage = D3D11_USAGE_DEFAULT; + buffer_desc.BindFlags = D3D11_BIND_INDEX_BUFFER; + } + else if( type == BufferBase::BUFFER_VERTEX ) + { + buffer_desc.Usage = D3D11_USAGE_DEFAULT; + buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + } + else + { + buffer_desc.Usage = D3D11_USAGE_DEFAULT; + + buffer_desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; + buffer_desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; + +// check this + if(type == BufferBase::BUFFER_RAW) + { +// buffer_desc.BindFlags |= D3D11_BIND_INDEX_BUFFER | D3D11_BIND_VERTEX_BUFFER; + buffer_desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS | D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS; // need this to be used for DispatchIndirect + } + } + hr = deviceData->m_device->CreateBuffer(&buffer_desc, NULL, dBuf->getBufferPtr()); + + ADLASSERT( hr == S_OK ); + + if( type == BufferBase::BUFFER_INDEX ) return; + + if( type == BufferBase::BUFFER || + type == BufferBase::BUFFER_RAW || + type == BufferBase::BUFFER_W_COUNTER ) + { + // Create UAVs for all CS buffers + D3D11_UNORDERED_ACCESS_VIEW_DESC uavbuffer_desc; + ZeroMemory(&uavbuffer_desc, sizeof(uavbuffer_desc)); + uavbuffer_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + + if( type == BufferBase::BUFFER_RAW ) + { + uavbuffer_desc.Format = DXGI_FORMAT_R32_TYPELESS; + uavbuffer_desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; + uavbuffer_desc.Buffer.NumElements = buffer_desc.ByteWidth / 4; + } + else + { + uavbuffer_desc.Format = DXGI_FORMAT_UNKNOWN; + uavbuffer_desc.Buffer.NumElements = nElems; + } + + if( type == BufferBase::BUFFER_W_COUNTER ) + { + uavbuffer_desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_COUNTER; + } + + hr = deviceData->m_device->CreateUnorderedAccessView(dBuf->getBuffer(), &uavbuffer_desc, dBuf->getUAVPtr()); + ADLASSERT( hr == S_OK ); + + // Create SRVs for all CS buffers + D3D11_SHADER_RESOURCE_VIEW_DESC srvbuffer_desc; + ZeroMemory(&srvbuffer_desc, sizeof(srvbuffer_desc)); + if( type == BufferBase::BUFFER_RAW ) + { + ADLASSERT( sizeof(T) <= 16 ); + srvbuffer_desc.Format = DXGI_FORMAT_R32_UINT; + srvbuffer_desc.Buffer.ElementWidth = nElems; +// if ( buffer_desc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS ) +// { +// srvbuffer_desc.Format = DXGI_FORMAT_R32_TYPELESS; +// srvbuffer_desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW; +// srvbuffer_desc.BufferEx.NumElements = buffer_desc.ByteWidth / 4; + } + else + { + srvbuffer_desc.Format = DXGI_FORMAT_UNKNOWN; + srvbuffer_desc.Buffer.ElementWidth = nElems; + } + srvbuffer_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; + + hr = deviceData->m_device->CreateShaderResourceView(dBuf->getBuffer(), &srvbuffer_desc, dBuf->getSRVPtr()); + ADLASSERT( hr == S_OK ); + } + else if( type == BufferBase::BUFFER_APPEND ) + { + D3D11_UNORDERED_ACCESS_VIEW_DESC desc; + ZeroMemory( &desc, sizeof(desc) ); + desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + desc.Buffer.FirstElement = 0; + + desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_APPEND; + + desc.Format = DXGI_FORMAT_UNKNOWN; // Format must be must be DXGI_FORMAT_UNKNOWN, when creating a View of a Structured Buffer + desc.Buffer.NumElements = buffer_desc.ByteWidth / buffer_desc.StructureByteStride; + + hr = deviceData->m_device->CreateUnorderedAccessView( dBuf->getBuffer(), &desc, dBuf->getUAVPtr() ); + ADLASSERT( hr == S_OK ); + } + } +// else +// { +// ADLASSERT(0); +// } +} + +template +void DeviceDX11::deallocate(Buffer* buf) +{ + BufferDX11* dBuf = (BufferDX11*)buf; + + if( dBuf->getBuffer() ) + { + dBuf->getBuffer()->Release(); + dBuf->m_ptr = NULL; + } + if( dBuf->getUAV() ) + { + dBuf->getUAV()->Release(); + dBuf->m_uav = NULL; + } + if( dBuf->getSRV() ) + { + dBuf->getSRV()->Release(); + dBuf->m_srv = NULL; + } + buf->m_device = 0; +} + +template +void DeviceDX11::copy(Buffer* dst, const Buffer* src, int nElems) +{ + if( dst->m_device->m_type == TYPE_DX11 || src->m_device->m_type == TYPE_DX11 ) + { + DeviceDX11* deviceData = this; + BufferDX11* dDst = (BufferDX11*)dst; + BufferDX11* dSrc = (BufferDX11*)src; + + D3D11_MAPPED_SUBRESOURCE MappedVelResource = {0}; + + D3D11_BOX destRegion; + destRegion.left = 0*sizeof(T); + destRegion.front = 0; + destRegion.top = 0; + destRegion.bottom = 1; + destRegion.back = 1; + destRegion.right = (0+nElems)*sizeof(T); + + deviceData->m_context->CopySubresourceRegion( + dDst->getBuffer(), + 0, 0, 0, 0, + dSrc->getBuffer(), + 0, + &destRegion ); + + } + else if( src->m_device->m_type == TYPE_HOST ) + { + ADLASSERT( dst->getType() == TYPE_DX11 ); + dst->write( src->m_ptr, nElems ); + } + else if( dst->m_device->m_type == TYPE_HOST ) + { + ADLASSERT( src->getType() == TYPE_DX11 ); + src->read( dst->m_ptr, nElems ); + } + else + { + ADLASSERT( 0 ); + } +} + +template +void DeviceDX11::copy(T* dst, const Buffer* src, int nElems, int srcOffsetNElems) +{ + DeviceDX11* deviceData = this; + BufferDX11* dSrc = (BufferDX11*)src; + Buffer sBuf( deviceData, nElems, BufferBase::BUFFER_STAGING ); + BufferDX11* dStagingBuf = (BufferDX11*)&sBuf; + + + ID3D11Buffer *StagingBuffer = dStagingBuf->getBuffer(); + D3D11_MAPPED_SUBRESOURCE MappedVelResource = {0}; + + D3D11_BOX destRegion; + destRegion.left = srcOffsetNElems*sizeof(T); + destRegion.front = 0; + destRegion.top = 0; + destRegion.bottom = 1; + destRegion.back = 1; + destRegion.right = (srcOffsetNElems+nElems)*sizeof(T); + + deviceData->m_context->CopySubresourceRegion( + StagingBuffer, + 0, 0, 0, 0, + dSrc->getBuffer(), + 0, + &destRegion); + + deviceData->m_context->Map(StagingBuffer, 0, D3D11_MAP_READ, 0, &MappedVelResource); + memcpy(dst, MappedVelResource.pData, nElems*sizeof(T)); + deviceData->m_context->Unmap(StagingBuffer, 0); +} + +template +void DeviceDX11::copy(Buffer* dst, const T* src, int nElems, int dstOffsetNElems) +{ + BufferDX11* dBuf = (BufferDX11*)dst; + + DeviceDX11* deviceData = this; + + D3D11_BOX destRegion; + destRegion.left = dstOffsetNElems*sizeof(T); + destRegion.front = 0; + destRegion.top = 0; + destRegion.bottom = 1; + destRegion.back = 1; + destRegion.right = (dstOffsetNElems+nElems)*sizeof(T); + deviceData->m_context->UpdateSubresource(dBuf->getBuffer(), 0, &destRegion, src, 0, 0); +} + +void DeviceDX11::waitForCompletion() const +{ + const DeviceDX11* deviceData = this; + + ID3D11Query* syncQuery; + D3D11_QUERY_DESC qDesc; + qDesc.Query = D3D11_QUERY_EVENT; + qDesc.MiscFlags = 0; + deviceData->m_device->CreateQuery( &qDesc, &syncQuery ); + deviceData->m_context->End( syncQuery ); + while( deviceData->m_context->GetData( syncQuery, 0,0,0 ) == S_FALSE ){} + syncQuery->Release(); +} + +int DeviceDX11::getNDevices() +{ + IDXGIFactory1* factory = NULL; + IDXGIAdapter1* adapter = NULL; + CreateDXGIFactory1( __uuidof(IDXGIFactory1), (void**)&factory ); + + u32 i = 0; + while( factory->EnumAdapters1( i, &adapter ) != DXGI_ERROR_NOT_FOUND ) + { + i++; + } + + factory->Release(); + return i; +} + +void DeviceDX11::getDeviceName( char nameOut[128] ) const +{ + IDXGIAdapter* adapter;// = getAdapterFromDevice( this ); + { + IDXGIDevice* pDXGIDevice; + + ADLASSERT( m_device->QueryInterface(__uuidof(IDXGIDevice), (void **)&pDXGIDevice) == S_OK ); + ADLASSERT( pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void **)&adapter) == S_OK ); + + pDXGIDevice->Release(); + } + DXGI_ADAPTER_DESC adapterDesc; + adapter->GetDesc( &adapterDesc ); + +// wcstombs( nameOut, adapterDesc.Description, 128 ); + size_t i; + wcstombs_s( &i, nameOut, 128, adapterDesc.Description, 128 ); +} + +Kernel* DeviceDX11::getKernel(const char* fileName, const char* funcName, const char* option, const char* src, bool cacheKernel ) const +{ + return m_kernelManager->query( this, fileName, funcName, option, src, cacheKernel ); +} + +#undef u32 + +#undef SAFE_RELEASE + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlKernelUtilsDX11.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlKernelUtilsDX11.inl new file mode 100644 index 000000000..d4e29999d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlKernelUtilsDX11.inl @@ -0,0 +1,348 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +namespace adl +{ + +#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } + +struct KernelDX11 : public Kernel +{ + ID3D11ComputeShader* getKernel() { return (ID3D11ComputeShader*)m_kernel; } + ID3D11ComputeShader** getKernelPtr() { return (ID3D11ComputeShader**)&m_kernel; } +}; + + +__inline +#ifdef UNICODE +HRESULT FindDXSDKShaderFileCch( __in_ecount(cchDest) WCHAR* strDestPath, + int cchDest, + __in LPCWSTR strFilename ) +#else +HRESULT FindDXSDKShaderFileCch( __in_ecount(cchDest) CHAR* strDestPath, + int cchDest, + __in LPCSTR strFilename ) +#endif +{ + if( NULL == strFilename || strFilename[0] == 0 || NULL == strDestPath || cchDest < 10 ) + return E_INVALIDARG; + + // Get the exe name, and exe path +#ifdef UNICODE + WCHAR strExePath[MAX_PATH] = +#else + CHAR strExePath[MAX_PATH] = +#endif + { + 0 + }; +#ifdef UNICODE + WCHAR strExeName[MAX_PATH] = +#else + CHAR strExeName[MAX_PATH] = +#endif + { + 0 + }; +#ifdef UNICODE + WCHAR* strLastSlash = NULL; +#else + CHAR* strLastSlash = NULL; +#endif + GetModuleFileName( NULL, strExePath, MAX_PATH ); + strExePath[MAX_PATH - 1] = 0; +#ifdef UNICODE + strLastSlash = wcsrchr( strExePath, TEXT( '\\' ) ); +#else + strLastSlash = strrchr( strExePath, TEXT( '\\' ) ); +#endif + if( strLastSlash ) + { +#ifdef UNICODE + wcscpy_s( strExeName, MAX_PATH, &strLastSlash[1] ); +#else + +#endif + // Chop the exe name from the exe path + *strLastSlash = 0; + + // Chop the .exe from the exe name +#ifdef UNICODE + strLastSlash = wcsrchr( strExeName, TEXT( '.' ) ); +#else + strLastSlash = strrchr( strExeName, TEXT( '.' ) ); +#endif + if( strLastSlash ) + *strLastSlash = 0; + } + + // Search in directories: + // .\ + // %EXE_DIR%\..\..\%EXE_NAME% +#ifdef UNICODE + wcscpy_s( strDestPath, cchDest, strFilename ); +#else + strcpy_s( strDestPath, cchDest, strFilename ); +#endif + if( GetFileAttributes( strDestPath ) != 0xFFFFFFFF ) + return S_OK; + +// swprintf_s( strDestPath, cchDest, L"%s\\..\\..\\%s\\%s", strExePath, strExeName, strFilename ); +#ifdef UNICODE + swprintf_s( strDestPath, cchDest, L"%s\\..\\%s\\%s", strExePath, strExeName, strFilename ); +#else + sprintf_s( strDestPath, cchDest, "%s\\..\\%s\\%s", strExePath, strExeName, strFilename ); +#endif + if( GetFileAttributes( strDestPath ) != 0xFFFFFFFF ) + return S_OK; + + // On failure, return the file as the path but also return an error code +#ifdef UNICODE + wcscpy_s( strDestPath, cchDest, strFilename ); +#else + strcpy_s( strDestPath, cchDest, strFilename ); +#endif + + ADLASSERT( 0 ); + + return E_FAIL; +} + + + + +template<> +void KernelBuilder::setFromFile( const Device* deviceData, const char* fileName, const char* option, bool addExtension, + bool cacheKernel) +{ + char fileNameWithExtension[256]; + + if( addExtension ) + sprintf_s( fileNameWithExtension, "%s.hlsl", fileName ); + else + sprintf_s( fileNameWithExtension, "%s", fileName ); + + m_deviceData = deviceData; + + int nameLength = (int)strlen(fileNameWithExtension)+1; +#ifdef UNICODE + WCHAR* wfileNameWithExtension = new WCHAR[nameLength]; +#else + CHAR* wfileNameWithExtension = new CHAR[nameLength]; +#endif + memset(wfileNameWithExtension,0,nameLength); +#ifdef UNICODE + MultiByteToWideChar(CP_ACP,0,fileNameWithExtension,-1, wfileNameWithExtension, nameLength); +#else + sprintf_s(wfileNameWithExtension, nameLength, "%s", fileNameWithExtension); +#endif +// swprintf_s(wfileNameWithExtension, nameLength*2, L"%s", fileNameWithExtension); + + HRESULT hr; + + // Finds the correct path for the shader file. + // This is only required for this sample to be run correctly from within the Sample Browser, + // in your own projects, these lines could be removed safely + hr = FindDXSDKShaderFileCch( m_path, MAX_PATH, wfileNameWithExtension ); + + delete [] wfileNameWithExtension; + + ADLASSERT( hr == S_OK ); +} + +template<> +void KernelBuilder::setFromSrc( const Device* deviceData, const char* src, const char* option ) +{ + m_deviceData = deviceData; + m_ptr = (void*)src; + m_path[0] = '0'; +} + +template<> +KernelBuilder::~KernelBuilder() +{ + +} + +template<> +void KernelBuilder::createKernel( const char* funcName, Kernel& kernelOut ) +{ + const DeviceDX11* deviceData = (const DeviceDX11*)m_deviceData; + KernelDX11* dxKernel = (KernelDX11*)&kernelOut; + HRESULT hr; + + DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS; +#if defined( DEBUG ) || defined( _DEBUG ) + // Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders. + // Setting this flag improves the shader debugging experience, but still allows + // the shaders to be optimized and to run exactly the way they will run in + // the release configuration of this program. + dwShaderFlags |= D3DCOMPILE_DEBUG; +#endif + + const D3D_SHADER_MACRO defines[] = + { +#ifdef USE_STRUCTURED_BUFFERS + "USE_STRUCTURED_BUFFERS", "1", +#endif + +#ifdef TEST_DOUBLE + "TEST_DOUBLE", "1", +#endif + NULL, NULL + }; + + // We generally prefer to use the higher CS shader profile when possible as CS 5.0 is better performance on 11-class hardware + LPCSTR pProfile = ( deviceData->m_device->GetFeatureLevel() >= D3D_FEATURE_LEVEL_11_0 ) ? "cs_5_0" : "cs_4_0"; + + ID3DBlob* pErrorBlob = NULL; + ID3DBlob* pBlob = NULL; + if( m_path[0] == '0' ) + { + char* src = (char*)m_ptr; + hr = D3DX11CompileFromMemory( src, strlen(src), 0, defines, NULL, funcName, pProfile, + dwShaderFlags, NULL, NULL, &pBlob, &pErrorBlob, NULL ); + } + else + { + hr = D3DX11CompileFromFile( m_path, defines, NULL, funcName, pProfile, + dwShaderFlags, NULL, NULL, &pBlob, &pErrorBlob, NULL ); + } + + if ( FAILED(hr) ) + { + debugPrintf("%s", (char*)pErrorBlob->GetBufferPointer()); + } + ADLASSERT( hr == S_OK ); + + hr = deviceData->m_device->CreateComputeShader( pBlob->GetBufferPointer(), pBlob->GetBufferSize(), NULL, + dxKernel->getKernelPtr() ); + +#if defined(DEBUG) || defined(PROFILE) + if ( kernelOut.m_kernel ) + kernelOut.m_kernel->SetPrivateData( WKPDID_D3DDebugObjectName, lstrlenA(pFunctionName), pFunctionName ); +#endif + + SAFE_RELEASE( pErrorBlob ); + SAFE_RELEASE( pBlob ); + + kernelOut.m_type = TYPE_DX11; +} + +template<> +void KernelBuilder::deleteKernel( Kernel& kernel ) +{ + KernelDX11* dxKernel = (KernelDX11*)&kernel; + + if( kernel.m_kernel ) + { + dxKernel->getKernel()->Release(); + kernel.m_kernel = NULL; + } +} + + + +class LauncherDX11 +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + __inline + static void setBuffers( Launcher* launcher, BufferInfo* buffInfo, int n ); + template + __inline + static void setConst( Launcher* launcher, Buffer& constBuff, const T& consts ); + __inline + static void launch2D( Launcher* launcher, int numThreadsX, int numThreadsY, int localSizeX, int localSizeY ); +}; + +void LauncherDX11::setBuffers( Launcher* launcher, BufferInfo* buffInfo, int n ) +{ + KernelDX11* dxKernel = (KernelDX11*)launcher->m_kernel; + const DeviceDX11* dddx = (const DeviceDX11*)launcher->m_deviceData; + + for(int i=0; i* dBuf = (BufferDX11*)buffInfo[i].m_buffer; + if( buffInfo[i].m_isReadOnly ) + { + dddx->m_context->CSSetShaderResources( launcher->m_idx++, 1, dBuf->getSRVPtr() ); + } + else + { + // todo. cannot initialize append buffer with proper counter value which is the last arg + dddx->m_context->CSSetUnorderedAccessViews( launcher->m_idxRw++, 1, dBuf->getUAVPtr(), 0 ); + } + } +} + +template +void LauncherDX11::setConst( Launcher* launcher, Buffer& constBuff, const T& consts ) +{ + KernelDX11* dxKernel = (KernelDX11*)launcher->m_kernel; + const DeviceDX11* dddx = (const DeviceDX11*)launcher->m_deviceData; + BufferDX11* dBuf = (BufferDX11*)&constBuff; +/* + D3D11_MAPPED_SUBRESOURCE MappedResource; + dddx->m_context->Map( dBuf->getBuffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ); + memcpy( MappedResource.pData, &consts, sizeof(T) ); + dddx->m_context->Unmap( dBuf->getBuffer(), 0 ); +*/ + + dddx->m_context->UpdateSubresource( dBuf->getBuffer(), 0, NULL, &consts, 0, 0 ); + + dddx->m_context->CSSetConstantBuffers( 0, 1, dBuf->getBufferPtr() ); +} + +void LauncherDX11::launch2D( Launcher* launcher, int numThreadsX, int numThreadsY, int localSizeX, int localSizeY ) +{ + KernelDX11* dxKernel = (KernelDX11*)launcher->m_kernel; + const DeviceDX11* dddx = (const DeviceDX11*)launcher->m_deviceData; + + dddx->m_context->CSSetShader( dxKernel->getKernel(), NULL, 0 ); + + int nx, ny, nz; + nx = max( 1, (numThreadsX/localSizeX)+(!(numThreadsX%localSizeX)?0:1) ); + ny = max( 1, (numThreadsY/localSizeY)+(!(numThreadsY%localSizeY)?0:1) ); + nz = 1; + + dddx->m_context->Dispatch( nx, ny, nz ); + + // set 0 to registers + { + dddx->m_context->CSSetShader( NULL, NULL, 0 ); + + if( launcher->m_idxRw ) + { + ID3D11UnorderedAccessView* aUAViewsNULL[ 16 ] = { 0 }; + dddx->m_context->CSSetUnorderedAccessViews( 0, + min( (unsigned int)launcher->m_idxRw, sizeof(aUAViewsNULL)/sizeof(*aUAViewsNULL) ), aUAViewsNULL, NULL ); + } + + if( launcher->m_idx ) + { + ID3D11ShaderResourceView* ppSRVNULL[16] = { 0 }; + dddx->m_context->CSSetShaderResources( 0, + min( (unsigned int)launcher->m_idx, sizeof(ppSRVNULL)/sizeof(*ppSRVNULL) ), ppSRVNULL ); + } + } +} + +#undef SAFE_RELEASE + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlStopwatchDX11.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlStopwatchDX11.inl new file mode 100644 index 000000000..15b79aac5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/DX11/AdlStopwatchDX11.inl @@ -0,0 +1,131 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +namespace adl +{ + +struct StopwatchDX11 : public StopwatchBase +{ + public: + __inline + StopwatchDX11() : StopwatchBase(){} + __inline + ~StopwatchDX11(); + + __inline + void init( const Device* deviceData ); + __inline + void start(); + __inline + void split(); + __inline + void stop(); + __inline + float getMs(int index=0); + __inline + void getMs( float* times, int capacity ); + + public: + ID3D11Query* m_tQuery[CAPACITY+1]; + ID3D11Query* m_fQuery; + UINT64 m_t[CAPACITY]; +}; + +void StopwatchDX11::init( const Device* deviceData ) +{ + ADLASSERT( deviceData->m_type == TYPE_DX11 ); + m_device = deviceData; + { + D3D11_QUERY_DESC qDesc; + qDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; + qDesc.MiscFlags = 0; + ((const DeviceDX11*)m_device)->m_device->CreateQuery( &qDesc, &m_fQuery ); + } + for(int i=0; im_device->CreateQuery( &qDesc, &m_tQuery[i] ); + } +} + +StopwatchDX11::~StopwatchDX11() +{ + m_fQuery->Release(); + for(int i=0; iRelease(); + } +} + +void StopwatchDX11::start() +{ + m_idx = 0; + ((const DeviceDX11*)m_device)->m_context->Begin( m_fQuery ); + ((const DeviceDX11*)m_device)->m_context->End( m_tQuery[m_idx++] ); +} + +void StopwatchDX11::split() +{ + if( m_idx < CAPACITY ) + ((const DeviceDX11*)m_device)->m_context->End( m_tQuery[m_idx++] ); +} + +void StopwatchDX11::stop() +{ + ((const DeviceDX11*)m_device)->m_context->End( m_tQuery[m_idx++] ); + ((const DeviceDX11*)m_device)->m_context->End( m_fQuery ); +} + +float StopwatchDX11::getMs(int index) +{ + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT d; +// m_deviceData->m_context->End( m_fQuery ); + while( ((const DeviceDX11*)m_device)->m_context->GetData( m_fQuery, &d,sizeof(D3D11_QUERY_DATA_TIMESTAMP_DISJOINT),0 ) == S_FALSE ) {} + + while( ((const DeviceDX11*)m_device)->m_context->GetData( m_tQuery[0], &m_t[index],sizeof(UINT64),0 ) == S_FALSE ){} + while( ((const DeviceDX11*)m_device)->m_context->GetData( m_tQuery[1], &m_t[index+1],sizeof(UINT64),0 ) == S_FALSE ){} + + ADLASSERT( d.Disjoint == false ); + + float elapsedMs = (m_t[index+1] - m_t[index])/(float)d.Frequency*1000; + return elapsedMs; + +} + +void StopwatchDX11::getMs( float* times, int capacity ) +{ + ADLASSERT( capacity <= CAPACITY ); + + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT d; + while( ((const DeviceDX11*)m_device)->m_context->GetData( m_fQuery, &d,sizeof(D3D11_QUERY_DATA_TIMESTAMP_DISJOINT),0 ) == S_FALSE ) {} + + for(int i=0; im_context->GetData( m_tQuery[i], &m_t[i],sizeof(UINT64),0 ) == S_FALSE ){} + } + + ADLASSERT( d.Disjoint == false ); + + for(int i=0; i + __inline + void allocate(Buffer* buf, int nElems, BufferBase::BufferType type); + + template + __inline + void deallocate(Buffer* buf); + + template + __inline + void copy(Buffer* dst, const Buffer* src, int nElems); + + template + __inline + void copy(T* dst, const Buffer* src, int nElems, int offsetNElems = 0); + + template + __inline + void copy(Buffer* dst, const T* src, int nElems, int offsetNElems = 0); + + __inline + void waitForCompletion() const; +}; + +void DeviceHost::initialize(const Config& cfg) +{ + +} + +void DeviceHost::release() +{ + +} + +template +void DeviceHost::allocate(Buffer* buf, int nElems, BufferBase::BufferType type) +{ + buf->m_device = this; + + if( type == BufferBase::BUFFER_CONST ) return; + + buf->m_ptr = new T[nElems]; + ADLASSERT( buf->m_ptr ); + buf->m_size = nElems; +} + +template +void DeviceHost::deallocate(Buffer* buf) +{ + if( buf->m_ptr ) delete [] buf->m_ptr; +} + +template +void DeviceHost::copy(Buffer* dst, const Buffer* src, int nElems) +{ + copy( dst, src->m_ptr, nElems ); +} + +template +void DeviceHost::copy(T* dst, const Buffer* src, int nElems, int srcOffsetNElems) +{ + ADLASSERT( src->getType() == TYPE_HOST ); + memcpy( dst, src->m_ptr+srcOffsetNElems, nElems*sizeof(T) ); +} + +template +void DeviceHost::copy(Buffer* dst, const T* src, int nElems, int dstOffsetNElems) +{ + ADLASSERT( dst->getType() == TYPE_HOST ); + memcpy( dst->m_ptr+dstOffsetNElems, src, nElems*sizeof(T) ); +} + +void DeviceHost::waitForCompletion() const +{ + +} + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Host/AdlStopwatchHost.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Host/AdlStopwatchHost.inl new file mode 100644 index 000000000..bb6eb571c --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/Adl/Host/AdlStopwatchHost.inl @@ -0,0 +1,119 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#ifdef _WIN32 + #include +#else + #include +#endif + +namespace adl +{ + +class StopwatchHost : public StopwatchBase +{ + public: + __inline + StopwatchHost(); + __inline + void init( const Device* deviceData ); + __inline + void start(); + __inline + void split(); + __inline + void stop(); + __inline + float getMs(int index=0); + __inline + void getMs( float* times, int capacity ); + + private: +#ifdef _WIN32 + LARGE_INTEGER m_frequency; + LARGE_INTEGER m_t[CAPACITY]; +#else + struct timeval mStartTime; + timeval m_t[CAPACITY]; +#endif +}; + +__inline +StopwatchHost::StopwatchHost() + : StopwatchBase() +{ +} + +__inline +void StopwatchHost::init( const Device* deviceData ) +{ + m_device = deviceData; +#ifdef _WIN32 + QueryPerformanceFrequency( &m_frequency ); +#else + gettimeofday(&mStartTime, 0); +#endif +} + +__inline +void StopwatchHost::start() +{ + m_idx = 0; +#ifdef _WIN32 + QueryPerformanceCounter(&m_t[m_idx++]); +#else + gettimeofday(&m_t[m_idx++], 0); +#endif +} + +__inline +void StopwatchHost::split() +{ +#ifdef _WIN32 + QueryPerformanceCounter(&m_t[m_idx++]); +#else + gettimeofday(&m_t[m_idx++], 0); +#endif +} + +__inline +void StopwatchHost::stop() +{ + split(); +} + +__inline +float StopwatchHost::getMs(int index) +{ +#ifdef _WIN32 + return (float)(1000*(m_t[index+1].QuadPart - m_t[index].QuadPart))/m_frequency.QuadPart; +#else + return (m_t[index+1].tv_sec - m_t[index].tv_sec) * 1000 + + (m_t[index+1].tv_usec - m_t[index].tv_usec) / 1000; +#endif +} + +__inline +void StopwatchHost::getMs(float* times, int capacity) +{ + for(int i=0; i +#include + +namespace adl +{ + +class CopyBase +{ + public: + enum Option + { + PER_WI_1, + PER_WI_2, + PER_WI_4, + }; +}; + +template +class Copy : public CopyBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct Data + { + const Device* m_device; + Kernel* m_copy1F4Kernel; + Kernel* m_copy2F4Kernel; + Kernel* m_copy4F4Kernel; + Kernel* m_copyF1Kernel; + Kernel* m_copyF2Kernel; + Buffer* m_constBuffer; + }; + + static + Data* allocate(const Device* deviceData); + + static + void deallocate(Data* data); + + static + void execute( Data* data, Buffer& dst, Buffer& src, int n, Option option = PER_WI_1); + + static + void execute( Data* data, Buffer& dst, Buffer& src, int n); + + static + void execute( Data* data, Buffer& dst, Buffer& src, int n); +}; + + +#include +#include + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/Copy.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/Copy.inl new file mode 100644 index 000000000..ee6d3f0ef --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/Copy.inl @@ -0,0 +1,151 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + + +#define PATH "..\\..\\opencl\\primitives\\AdlPrimitives\\Copy\\CopyKernels" +#define KERNEL0 "Copy1F4Kernel" +#define KERNEL1 "Copy2F4Kernel" +#define KERNEL2 "Copy4F4Kernel" +#define KERNEL3 "CopyF1Kernel" +#define KERNEL4 "CopyF2Kernel" + +#include +#include + + +template +typename Copy::Data* Copy::allocate( const Device* device ) +{ + ADLASSERT( TYPE == device->m_type ); + + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {copyKernelsCL, copyKernelsDX11}; +// ADLASSERT(0); +#else + {0,0}; +#endif + + Data* data = new Data; + data->m_device = device; + data->m_copy1F4Kernel = device->getKernel( PATH, KERNEL0, 0, src[TYPE] ); + data->m_copy2F4Kernel = device->getKernel( PATH, KERNEL1, 0, src[TYPE] ); + data->m_copy4F4Kernel = device->getKernel( PATH, KERNEL2, 0, src[TYPE] ); + data->m_copyF1Kernel = device->getKernel( PATH, KERNEL3, 0, src[TYPE] ); + data->m_copyF2Kernel = device->getKernel( PATH, KERNEL4, 0, src[TYPE] ); + data->m_constBuffer = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + + return data; +} + +template +void Copy::deallocate( Data* data ) +{ + delete data->m_constBuffer; + delete data; +} + +template +void Copy::execute( Data* data, Buffer& dst, Buffer& src, int n, Option option ) +{ + ADLASSERT( TYPE == dst.getType() ); + ADLASSERT( TYPE == src.getType() ); + + int4 constBuffer; + constBuffer.x = n; + + switch (option) + { + case PER_WI_1: + { + BufferInfo bInfo[] = { BufferInfo( &dst ), BufferInfo( &src, true ) }; + + Launcher launcher( data->m_device, data->m_copy1F4Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n/1 ); + } + break; + case PER_WI_2: + { + ADLASSERT( n%2 == 0 ); + BufferInfo bInfo[] = { BufferInfo( &dst ), BufferInfo( &src, true ) }; + + Launcher launcher( data->m_device, data->m_copy2F4Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n/2 ); + } + break; + case PER_WI_4: + { + ADLASSERT( n%4 == 0 ); + BufferInfo bInfo[] = { BufferInfo( &dst ), BufferInfo( &src, true ) }; + + Launcher launcher( data->m_device, data->m_copy4F4Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n/4 ); + } + break; + default: + ADLASSERT(0); + break; + }; +} + +template +void Copy::execute( Data* data, Buffer& dst, Buffer& src, int n ) +{ + ADLASSERT( TYPE == dst.getType() ); + ADLASSERT( TYPE == src.getType() ); + + int4 constBuffer; + constBuffer.x = n; + + BufferInfo bInfo[] = { BufferInfo( &dst ), BufferInfo( &src, true ) }; + + Launcher launcher( data->m_device, data->m_copyF2Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n/1 ); +} + +template +void Copy::execute( Data* data, Buffer& dst, Buffer& src, int n ) +{ + ADLASSERT( TYPE == dst.getType() ); + ADLASSERT( TYPE == src.getType() ); + + int4 constBuffer; + constBuffer.x = n; + + BufferInfo bInfo[] = { BufferInfo( &dst ), BufferInfo( &src, true ) }; + + Launcher launcher( data->m_device, data->m_copyF1Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n/1 ); +} + + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 +#undef KERNEL2 +#undef KERNEL3 +#undef KERNEL4 diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyHost.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyHost.inl new file mode 100644 index 000000000..2f8562a29 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyHost.inl @@ -0,0 +1,85 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +template<> +class Copy : public CopyBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct Data + { + }; + + static + Data* allocate(const Device* deviceData) + { + ADLASSERT( TYPE_HOST == deviceData->m_type ); + return 0; + } + + static + void deallocate(Data* data) + { + return; + } + + static + void execute( Data* data, Buffer& dst, Buffer& src, int n, Option option = PER_WI_1) + { + ADLASSERT( TYPE_HOST == dst.getType() ); + ADLASSERT( TYPE_HOST == src.getType() ); + + HostBuffer& dstH = (HostBuffer&)dst; + HostBuffer& srcH = (HostBuffer&)src; + + for(int i=0; i& dst, Buffer& src, int n) + { + ADLASSERT( TYPE_HOST == dst.getType() ); + ADLASSERT( TYPE_HOST == src.getType() ); + + HostBuffer& dstH = (HostBuffer&)dst; + HostBuffer& srcH = (HostBuffer&)src; + + for(int i=0; i& dst, Buffer& src, int n) + { + ADLASSERT( TYPE_HOST == dst.getType() ); + ADLASSERT( TYPE_HOST == src.getType() ); + + HostBuffer& dstH = (HostBuffer&)dst; + HostBuffer& srcH = (HostBuffer&)src; + + for(int i=0; i dst : register( u0 ); +StructuredBuffer src : register( t0 ); + +[numthreads(WG_SIZE, 1, 1)] +void Copy1F4Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < m_n ) + { + float4 a0 = src[gIdx]; + + dst[ gIdx ] = a0; + } +} + +[numthreads(WG_SIZE, 1, 1)] +void Copy2F4Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( 2*gIdx <= m_n ) + { + float4 a0 = src[gIdx*2+0]; + float4 a1 = src[gIdx*2+1]; + + dst[ gIdx*2+0 ] = a0; + dst[ gIdx*2+1 ] = a1; + } +} + +[numthreads(WG_SIZE, 1, 1)] +void Copy4F4Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( 4*gIdx <= m_n ) + { + int idx0 = gIdx*4+0; + int idx1 = gIdx*4+1; + int idx2 = gIdx*4+2; + int idx3 = gIdx*4+3; + + float4 a0 = src[idx0]; + float4 a1 = src[idx1]; + float4 a2 = src[idx2]; + float4 a3 = src[idx3]; + + dst[ idx0 ] = a0; + dst[ idx1 ] = a1; + dst[ idx2 ] = a2; + dst[ idx3 ] = a3; + } +} + +RWStructuredBuffer dstF1 : register( u0 ); +StructuredBuffer srcF1 : register( t0 ); + +[numthreads(WG_SIZE, 1, 1)] +void CopyF1Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < m_n ) + { + float a0 = srcF1[gIdx]; + + dstF1[ gIdx ] = a0; + } + +} + +RWStructuredBuffer dstF2 : register( u0 ); +StructuredBuffer srcF2 : register( t0 ); + +[numthreads(WG_SIZE, 1, 1)] +void CopyF2Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < m_n ) + { + float2 a0 = srcF2[gIdx]; + + dstF2[ gIdx ] = a0; + } +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsCL.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsCL.h new file mode 100644 index 000000000..3b6789201 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsCL.h @@ -0,0 +1,119 @@ +static const char* copyKernelsCL= \ +"/*\n" +" 2011 Takahiro Harada\n" +"*/\n" +"\n" +"#pragma OPENCL EXTENSION cl_amd_printf : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable\n" +"\n" +"typedef unsigned int u32;\n" +"#define GET_GROUP_IDX get_group_id(0)\n" +"#define GET_LOCAL_IDX get_local_id(0)\n" +"#define GET_GLOBAL_IDX get_global_id(0)\n" +"#define GET_GROUP_SIZE get_local_size(0)\n" +"#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE)\n" +"#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE)\n" +"#define AtomInc(x) atom_inc(&(x))\n" +"#define AtomInc1(x, out) out = atom_inc(&(x))\n" +"\n" +"#define make_uint4 (uint4)\n" +"#define make_uint2 (uint2)\n" +"#define make_int2 (int2)\n" +"\n" +"typedef struct\n" +"{\n" +" int m_n;\n" +" int m_padding[3];\n" +"} ConstBuffer;\n" +"\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void Copy1F4Kernel(__global float4* dst, __global float4* src, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < cb.m_n )\n" +" {\n" +" float4 a0 = src[gIdx];\n" +"\n" +" dst[ gIdx ] = a0;\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void Copy2F4Kernel(__global float4* dst, __global float4* src, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( 2*gIdx <= cb.m_n )\n" +" {\n" +" float4 a0 = src[gIdx*2+0];\n" +" float4 a1 = src[gIdx*2+1];\n" +"\n" +" dst[ gIdx*2+0 ] = a0;\n" +" dst[ gIdx*2+1 ] = a1;\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void Copy4F4Kernel(__global float4* dst, __global float4* src, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( 4*gIdx <= cb.m_n )\n" +" {\n" +" int idx0 = gIdx*4+0;\n" +" int idx1 = gIdx*4+1;\n" +" int idx2 = gIdx*4+2;\n" +" int idx3 = gIdx*4+3;\n" +"\n" +" float4 a0 = src[idx0];\n" +" float4 a1 = src[idx1];\n" +" float4 a2 = src[idx2];\n" +" float4 a3 = src[idx3];\n" +"\n" +" dst[ idx0 ] = a0;\n" +" dst[ idx1 ] = a1;\n" +" dst[ idx2 ] = a2;\n" +" dst[ idx3 ] = a3;\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void CopyF1Kernel(__global float* dstF1, __global float* srcF1, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < cb.m_n )\n" +" {\n" +" float a0 = srcF1[gIdx];\n" +"\n" +" dstF1[ gIdx ] = a0;\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void CopyF2Kernel(__global float2* dstF2, __global float2* srcF2, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < cb.m_n )\n" +" {\n" +" float2 a0 = srcF2[gIdx];\n" +"\n" +" dstF2[ gIdx ] = a0;\n" +" }\n" +"}\n" +"\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsDX11.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsDX11.h new file mode 100644 index 000000000..6abcda4a9 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Copy/CopyKernelsDX11.h @@ -0,0 +1,120 @@ +static const char* copyKernelsDX11= \ +"/*\n" +" 2011 Takahiro Harada\n" +"*/\n" +"\n" +"typedef uint u32;\n" +"\n" +"#define GET_GROUP_IDX groupIdx.x\n" +"#define GET_LOCAL_IDX localIdx.x\n" +"#define GET_GLOBAL_IDX globalIdx.x\n" +"#define GROUP_LDS_BARRIER GroupMemoryBarrierWithGroupSync()\n" +"#define GROUP_MEM_FENCE\n" +"#define DEFAULT_ARGS uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID\n" +"#define AtomInc(x) InterlockedAdd(x, 1)\n" +"#define AtomInc1(x, out) InterlockedAdd(x, 1, out)\n" +"\n" +"#define make_uint4 uint4\n" +"#define make_uint2 uint2\n" +"#define make_int2 int2\n" +"\n" +"#define WG_SIZE 64\n" +"\n" +"#define GET_GROUP_SIZE WG_SIZE\n" +"\n" +"\n" +"\n" +"cbuffer CB : register( b0 )\n" +"{\n" +" int m_n;\n" +" int m_padding[3];\n" +"};\n" +"\n" +"RWStructuredBuffer dst : register( u0 );\n" +"StructuredBuffer src : register( t0 );\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void Copy1F4Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < m_n )\n" +" {\n" +" float4 a0 = src[gIdx];\n" +"\n" +" dst[ gIdx ] = a0;\n" +" }\n" +"}\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void Copy2F4Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( 2*gIdx <= m_n )\n" +" {\n" +" float4 a0 = src[gIdx*2+0];\n" +" float4 a1 = src[gIdx*2+1];\n" +"\n" +" dst[ gIdx*2+0 ] = a0;\n" +" dst[ gIdx*2+1 ] = a1;\n" +" }\n" +"}\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void Copy4F4Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( 4*gIdx <= m_n )\n" +" {\n" +" int idx0 = gIdx*4+0;\n" +" int idx1 = gIdx*4+1;\n" +" int idx2 = gIdx*4+2;\n" +" int idx3 = gIdx*4+3;\n" +"\n" +" float4 a0 = src[idx0];\n" +" float4 a1 = src[idx1];\n" +" float4 a2 = src[idx2];\n" +" float4 a3 = src[idx3];\n" +"\n" +" dst[ idx0 ] = a0;\n" +" dst[ idx1 ] = a1;\n" +" dst[ idx2 ] = a2;\n" +" dst[ idx3 ] = a3;\n" +" }\n" +"}\n" +"\n" +"RWStructuredBuffer dstF1 : register( u0 );\n" +"StructuredBuffer srcF1 : register( t0 );\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void CopyF1Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < m_n )\n" +" {\n" +" float a0 = srcF1[gIdx];\n" +"\n" +" dstF1[ gIdx ] = a0;\n" +" }\n" +"\n" +"}\n" +"\n" +"RWStructuredBuffer dstF2 : register( u0 );\n" +"StructuredBuffer srcF2 : register( t0 );\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void CopyF2Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < m_n )\n" +" {\n" +" float2 a0 = srcF2[gIdx];\n" +"\n" +" dstF2[ gIdx ] = a0;\n" +" }\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.h new file mode 100644 index 000000000..35957e83d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.h @@ -0,0 +1,77 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#pragma once + +#include +#include + +namespace adl +{ + +class FillBase +{ + public: + enum Option + { + + }; +}; + +template +class Fill +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct ConstData + { + int4 m_data; + int m_offset; + int m_n; + int m_padding[2]; + }; + + struct Data + { + const Device* m_device; + Kernel* m_fillIntKernel; + Kernel* m_fillInt2Kernel; + Kernel* m_fillInt4Kernel; + Buffer* m_constBuffer; + }; + + static + Data* allocate(const Device* deviceData); + + static + void deallocate(Data* data); + + static + void execute(Data* data, Buffer& src, const int& value, int n, int offset = 0); + + static + void execute(Data* data, Buffer& src, const int2& value, int n, int offset = 0); + + static + void execute(Data* data, Buffer& src, const int4& value, int n, int offset = 0); + +}; + + +#include +#include + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.inl new file mode 100644 index 000000000..913db9b66 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/Fill.inl @@ -0,0 +1,123 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +//#define PATH "..\\..\\AdlPrimitives\\Fill\\FillKernels" +#define PATH "..\\..\\opencl\\primitives\\AdlPrimitives\\Fill\\FillKernels" +#define KERNEL0 "FillIntKernel" +#define KERNEL1 "FillInt2Kernel" +#define KERNEL2 "FillInt4Kernel" + +#include +#include + + +template +typename Fill::Data* Fill::allocate( const Device* device ) +{ + ADLASSERT( TYPE == device->m_type ); + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {fillKernelsCL, fillKernelsDX11}; +#else + {0,0}; +#endif + + Data* data = new Data; + data->m_device = device; + data->m_fillIntKernel = device->getKernel( PATH, KERNEL0, 0, src[TYPE] ); + data->m_fillInt2Kernel = device->getKernel( PATH, KERNEL1, 0, src[TYPE] ); + data->m_fillInt4Kernel = device->getKernel( PATH, KERNEL2, 0, src[TYPE] ); + data->m_constBuffer = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + + return data; +} + +template +void Fill::deallocate( Data* data ) +{ + delete data->m_constBuffer; + delete data; +} + +template +void Fill::execute(Data* data, Buffer& src, const int& value, int n, int offset) +{ + ADLASSERT( n>0 ); + ConstData constBuffer; + { + constBuffer.m_offset = offset; + constBuffer.m_n = n; + constBuffer.m_data = make_int4( value ); + } + + { + BufferInfo bInfo[] = { BufferInfo( &src ) }; + + Launcher launcher( data->m_device, data->m_fillIntKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n ); + } +} + +template +void Fill::execute(Data* data, Buffer& src, const int2& value, int n, int offset) +{ + ADLASSERT( n>0 ); + ConstData constBuffer; + { + constBuffer.m_offset = offset; + constBuffer.m_n = n; + constBuffer.m_data = make_int4( value.x, value.y, 0, 0 ); + } + + { + BufferInfo bInfo[] = { BufferInfo( &src ) }; + + Launcher launcher( data->m_device, data->m_fillInt2Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n ); + } +} + +template +void Fill::execute(Data* data, Buffer& src, const int4& value, int n, int offset) +{ + ADLASSERT( n>0 ); + ConstData constBuffer; + { + constBuffer.m_offset = offset; + constBuffer.m_n = n; + constBuffer.m_data = value; + } + + { + BufferInfo bInfo[] = { BufferInfo( &src ) }; + + Launcher launcher( data->m_device, data->m_fillInt4Kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( n ); + } +} + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 +#undef KERNEL2 + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillHost.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillHost.inl new file mode 100644 index 000000000..c6205fa98 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillHost.inl @@ -0,0 +1,99 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +template<> +class Fill +{ + public: + struct Data + { + }; + + static + Data* allocate(const Device* deviceData) + { + return 0; + } + + static + void deallocate(Data* data) + { + + } + + template + static + void executeImpl(Data* data, Buffer& src, const T& value, int n, int offset = 0) + { + ADLASSERT( src.getType() == TYPE_HOST ); + ADLASSERT( src.m_size >= offset+n ); + HostBuffer& hSrc = (HostBuffer&)src; + + for(int idx=offset; idx& src, const int& value, int n, int offset = 0) + { + executeImpl( data, src, value, n, offset ); + } + + static + void execute(Data* data, Buffer& src, const int2& value, int n, int offset = 0) + { + executeImpl( data, src, value, n, offset ); + } + + static + void execute(Data* data, Buffer& src, const int4& value, int n, int offset = 0) + { + executeImpl( data, src, value, n, offset ); + } + +/* + static + void execute(Data* data, Buffer& src, int value, int n, int offset = 0) + { + ADLASSERT( src.getType() == TYPE_HOST ); + ADLASSERT( src.m_size <= offset+n ); + HostBuffer& hSrc = (HostBuffer&)src; + + for(int idx=offset; idx& src, const int2& value, int n, int offset = 0) + { + ADLASSERT( src.getType() == TYPE_HOST ); + ADLASSERT( src.m_size <= offset+n ); + + } + + static + void execute(Data* data, Buffer& src, const int4& value, int n, int offset = 0) + { + ADLASSERT( src.getType() == TYPE_HOST ); + ADLASSERT( src.m_size <= offset+n ); + + } +*/ +}; + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.cl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.cl new file mode 100644 index 000000000..11a31b0c5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.cl @@ -0,0 +1,81 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable + +typedef unsigned int u32; +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) + +#define make_uint4 (uint4) +#define make_uint2 (uint2) +#define make_int2 (int2) + +typedef struct +{ + int4 m_data; + int m_offset; + int m_n; + int m_padding[2]; +} ConstBuffer; + + +__kernel +__attribute__((reqd_work_group_size(64,1,1))) +void FillIntKernel(__global int* dstInt, + ConstBuffer cb) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < cb.m_n ) + { + dstInt[ cb.m_offset+gIdx ] = cb.m_data.x; + } +} + +__kernel +__attribute__((reqd_work_group_size(64,1,1))) +void FillInt2Kernel(__global int2* dstInt2, + ConstBuffer cb) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < cb.m_n ) + { + dstInt2[ cb.m_offset+gIdx ] = make_int2( cb.m_data.x, cb.m_data.y ); + } +} + +__kernel +__attribute__((reqd_work_group_size(64,1,1))) +void FillInt4Kernel(__global int4* dstInt4, + ConstBuffer cb) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < cb.m_n ) + { + dstInt4[ cb.m_offset+gIdx ] = cb.m_data; + } +} + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.hlsl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.hlsl new file mode 100644 index 000000000..ead907d5e --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernels.hlsl @@ -0,0 +1,79 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +typedef uint u32; + +#define GET_GROUP_IDX groupIdx.x +#define GET_LOCAL_IDX localIdx.x +#define GET_GLOBAL_IDX globalIdx.x +#define GROUP_LDS_BARRIER GroupMemoryBarrierWithGroupSync() +#define GROUP_MEM_FENCE +#define DEFAULT_ARGS uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID +#define AtomInc(x) InterlockedAdd(x, 1) +#define AtomInc1(x, out) InterlockedAdd(x, 1, out) + +#define make_uint4 uint4 +#define make_uint2 uint2 +#define make_int2 int2 + + +cbuffer CB : register( b0 ) +{ + int4 m_data; + int m_offset; + int m_n; + int m_padding[2]; +}; + + +RWStructuredBuffer dstInt : register( u0 ); + +[numthreads(64, 1, 1)] +void FillIntKernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < m_n ) + { + dstInt[ m_offset+gIdx ] = m_data.x; + } +} + +RWStructuredBuffer dstInt2 : register( u0 ); + +[numthreads(64, 1, 1)] +void FillInt2Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < m_n ) + { + dstInt2[ m_offset+gIdx ] = make_int2( m_data.x, m_data.y ); + } +} + +RWStructuredBuffer dstInt4 : register( u0 ); + +[numthreads(64, 1, 1)] +void FillInt4Kernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + + if( gIdx < m_n ) + { + dstInt4[ m_offset+gIdx ] = m_data; + } +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsCL.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsCL.h new file mode 100644 index 000000000..e2899ffbc --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsCL.h @@ -0,0 +1,71 @@ +static const char* fillKernelsCL= \ +"/*\n" +" 2011 Takahiro Harada\n" +"*/\n" +"\n" +"#pragma OPENCL EXTENSION cl_amd_printf : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable\n" +"\n" +"typedef unsigned int u32;\n" +"#define GET_GROUP_IDX get_group_id(0)\n" +"#define GET_LOCAL_IDX get_local_id(0)\n" +"#define GET_GLOBAL_IDX get_global_id(0)\n" +"#define GET_GROUP_SIZE get_local_size(0)\n" +"#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE)\n" +"#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE)\n" +"#define AtomInc(x) atom_inc(&(x))\n" +"#define AtomInc1(x, out) out = atom_inc(&(x))\n" +"\n" +"#define make_uint4 (uint4)\n" +"#define make_uint2 (uint2)\n" +"#define make_int2 (int2)\n" +"\n" +"typedef struct\n" +"{\n" +" int4 m_data;\n" +" int m_offset;\n" +" int m_n;\n" +" int m_padding[2];\n" +"} ConstBuffer;\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void FillIntKernel(__global int* dstInt, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < cb.m_n )\n" +" {\n" +" dstInt[ cb.m_offset+gIdx ] = cb.m_data.x;\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void FillInt2Kernel(__global int2* dstInt2, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < cb.m_n )\n" +" {\n" +" dstInt2[ cb.m_offset+gIdx ] = make_int2( cb.m_data.x, cb.m_data.y );\n" +" }\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(64,1,1)))\n" +"void FillInt4Kernel(__global int4* dstInt4, \n" +" ConstBuffer cb)\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < cb.m_n )\n" +" {\n" +" dstInt4[ cb.m_offset+gIdx ] = cb.m_data;\n" +" }\n" +"}\n" +"\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsDX11.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsDX11.h new file mode 100644 index 000000000..1cdc6ab61 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Fill/FillKernelsDX11.h @@ -0,0 +1,69 @@ +static const char* fillKernelsDX11= \ +"/*\n" +" 2011 Takahiro Harada\n" +"*/\n" +"\n" +"typedef uint u32;\n" +"\n" +"#define GET_GROUP_IDX groupIdx.x\n" +"#define GET_LOCAL_IDX localIdx.x\n" +"#define GET_GLOBAL_IDX globalIdx.x\n" +"#define GROUP_LDS_BARRIER GroupMemoryBarrierWithGroupSync()\n" +"#define GROUP_MEM_FENCE\n" +"#define DEFAULT_ARGS uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID\n" +"#define AtomInc(x) InterlockedAdd(x, 1)\n" +"#define AtomInc1(x, out) InterlockedAdd(x, 1, out)\n" +"\n" +"#define make_uint4 uint4\n" +"#define make_uint2 uint2\n" +"#define make_int2 int2\n" +"\n" +"\n" +"cbuffer CB : register( b0 )\n" +"{\n" +" int4 m_data;\n" +" int m_offset;\n" +" int m_n;\n" +" int m_padding[2];\n" +"};\n" +"\n" +"\n" +"RWStructuredBuffer dstInt : register( u0 );\n" +"\n" +"[numthreads(64, 1, 1)]\n" +"void FillIntKernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < m_n )\n" +" {\n" +" dstInt[ m_offset+gIdx ] = m_data.x;\n" +" }\n" +"}\n" +"\n" +"RWStructuredBuffer dstInt2 : register( u0 );\n" +"\n" +"[numthreads(64, 1, 1)]\n" +"void FillInt2Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < m_n )\n" +" {\n" +" dstInt2[ m_offset+gIdx ] = make_int2( m_data.x, m_data.y );\n" +" }\n" +"}\n" +"\n" +"RWStructuredBuffer dstInt4 : register( u0 );\n" +"\n" +"[numthreads(64, 1, 1)]\n" +"void FillInt4Kernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +"\n" +" if( gIdx < m_n )\n" +" {\n" +" dstInt4[ m_offset+gIdx ] = m_data;\n" +" }\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Array.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Array.h new file mode 100644 index 000000000..5a63eee38 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Array.h @@ -0,0 +1,231 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#ifndef ARRAY_H +#define ARRAY_H + +#include +#include +#include +#include + +namespace adl +{ + +template +class Array +{ + public: + __inline + Array(); + __inline + Array(int size); + __inline + ~Array(); + __inline + T& operator[] (int idx); + __inline + const T& operator[] (int idx) const; + __inline + void pushBack(const T& elem); + __inline + void popBack(); + __inline + void clear(); + __inline + void setSize(int size); + __inline + int getSize() const; + __inline + T* begin(); + __inline + const T* begin() const; + __inline + T* end(); + __inline + const T* end() const; + __inline + int indexOf(const T& data) const; + __inline + void removeAt(int idx); + __inline + T& expandOne(); + + private: + Array(const Array& a){} + + private: + enum + { + DEFAULT_SIZE = 128, + INCREASE_SIZE = 128, + }; + + T* m_data; + int m_size; + int m_capacity; +}; + +template +Array::Array() +{ + m_size = 0; + m_capacity = DEFAULT_SIZE; +// m_data = new T[ m_capacity ]; + m_data = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + for(int i=0; i +Array::Array(int size) +{ + m_size = size; + m_capacity = size; +// m_data = new T[ m_capacity ]; + m_data = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + for(int i=0; i +Array::~Array() +{ + if( m_data ) + { +// delete [] m_data; + _aligned_free( m_data ); + m_data = NULL; + } +} + +template +T& Array::operator[](int idx) +{ + ADLASSERT(idx +const T& Array::operator[](int idx) const +{ + ADLASSERT(idx +void Array::pushBack(const T& elem) +{ + if( m_size == m_capacity ) + { + int oldCap = m_capacity; + m_capacity += INCREASE_SIZE; +// T* s = new T[m_capacity]; + T* s = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + memcpy( s, m_data, sizeof(T)*oldCap ); +// delete [] m_data; + _aligned_free( m_data ); + m_data = s; + } + m_data[ m_size++ ] = elem; +} + +template +void Array::popBack() +{ + ADLASSERT( m_size>0 ); + m_size--; +} + +template +void Array::clear() +{ + m_size = 0; +} + +template +void Array::setSize(int size) +{ + if( size > m_capacity ) + { + int oldCap = m_capacity; + m_capacity = size; +// T* s = new T[m_capacity]; + T* s = (T*)_aligned_malloc(sizeof(T)*m_capacity, 16); + for(int i=0; i +int Array::getSize() const +{ + return m_size; +} + +template +const T* Array::begin() const +{ + return m_data; +} + +template +T* Array::begin() +{ + return m_data; +} + +template +T* Array::end() +{ + return m_data+m_size; +} + +template +const T* Array::end() const +{ + return m_data+m_size; +} + +template +int Array::indexOf(const T& data) const +{ + for(int i=0; i +void Array::removeAt(int idx) +{ + ADLASSERT(idx +T& Array::expandOne() +{ + setSize( m_size+1 ); + return m_data[ m_size-1 ]; +} + +}; + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float2.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float2.inl new file mode 100644 index 000000000..4b2a9e7f7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float2.inl @@ -0,0 +1,173 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +__inline +float2 make_float2(float x, float y) +{ + float2 v; + v.s[0] = x; v.s[1] = y; + return v; +} + +__inline +float2 make_float2(float x) +{ + return make_float2(x,x); +} + +__inline +float2 make_float2(const int2& x) +{ + return make_float2((float)x.s[0], (float)x.s[1]); +} + + + + +__inline +float2 operator-(const float2& a) +{ + return make_float2(-a.x, -a.y); +} + +__inline +float2 operator*(const float2& a, const float2& b) +{ + float2 out; + out.s[0] = a.s[0]*b.s[0]; + out.s[1] = a.s[1]*b.s[1]; + return out; +} + +__inline +float2 operator*(float a, const float2& b) +{ + return make_float2(a*b.s[0], a*b.s[1]); +} + +__inline +float2 operator*(const float2& b, float a) +{ + return make_float2(a*b.s[0], a*b.s[1]); +} + +__inline +void operator*=(float2& a, const float2& b) +{ + a.s[0]*=b.s[0]; + a.s[1]*=b.s[1]; +} + +__inline +void operator*=(float2& a, float b) +{ + a.s[0]*=b; + a.s[1]*=b; +} + +__inline +float2 operator/(const float2& a, const float2& b) +{ + float2 out; + out.s[0] = a.s[0]/b.s[0]; + out.s[1] = a.s[1]/b.s[1]; + return out; +} + +__inline +float2 operator/(const float2& b, float a) +{ + return make_float2(b.s[0]/a, b.s[1]/a); +} + +__inline +void operator/=(float2& a, const float2& b) +{ + a.s[0]/=b.s[0]; + a.s[1]/=b.s[1]; +} + +__inline +void operator/=(float2& a, float b) +{ + a.s[0]/=b; + a.s[1]/=b; +} +// + +__inline +float2 operator+(const float2& a, const float2& b) +{ + float2 out; + out.s[0] = a.s[0]+b.s[0]; + out.s[1] = a.s[1]+b.s[1]; + return out; +} + +__inline +float2 operator+(const float2& a, float b) +{ + float2 out; + out.s[0] = a.s[0]+b; + out.s[1] = a.s[1]+b; + return out; +} + +__inline +float2 operator-(const float2& a, const float2& b) +{ + float2 out; + out.s[0] = a.s[0]-b.s[0]; + out.s[1] = a.s[1]-b.s[1]; + return out; +} + +__inline +float2 operator-(const float2& a, float b) +{ + float2 out; + out.s[0] = a.s[0]-b; + out.s[1] = a.s[1]-b; + return out; +} + +__inline +void operator+=(float2& a, const float2& b) +{ + a.s[0]+=b.s[0]; + a.s[1]+=b.s[1]; +} + +__inline +void operator+=(float2& a, float b) +{ + a.s[0]+=b; + a.s[1]+=b; +} + +__inline +void operator-=(float2& a, const float2& b) +{ + a.s[0]-=b.s[0]; + a.s[1]-=b.s[1]; +} + +__inline +void operator-=(float2& a, float b) +{ + a.s[0]-=b; + a.s[1]-=b; +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float4.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float4.inl new file mode 100644 index 000000000..458a91f65 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Float4.inl @@ -0,0 +1,375 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +//#define CHECK_ALIGNMENT(a) ADLASSERT((u32(&(a)) & 0xf) == 0); +#define CHECK_ALIGNMENT(a) a; + + +__inline +float4 make_float4(float x, float y, float z, float w = 0.f) +{ + float4 v; + v.x = x; v.y = y; v.z = z; v.w = w; + return v; +} + +__inline +float4 make_float4(float x) +{ + return make_float4(x,x,x,x); +} + +__inline +float4 make_float4(const int4& x) +{ + return make_float4((float)x.s[0], (float)x.s[1], (float)x.s[2], (float)x.s[3]); +} + +__inline +int4 make_int4(int x, int y, int z, int w = 0) +{ + int4 v; + v.s[0] = x; v.s[1] = y; v.s[2] = z; v.s[3] = w; + return v; +} + +__inline +int4 make_int4(int x) +{ + return make_int4(x,x,x,x); +} + +__inline +int4 make_int4(const float4& x) +{ + return make_int4((int)x.x, (int)x.y, (int)x.z, (int)x.w); +} + +__inline +int2 make_int2(int a, int b) +{ + int2 ans; ans.x = a; ans.y = b; + return ans; +} + +__inline +bool operator ==(const int2& a, const int2& b) +{ + return a.x==b.x && a.y==b.y; +} + +__inline +bool operator ==(const int4& a, const int4& b) +{ + return a.x==b.x && a.y==b.y && a.z==b.z && a.w==b.w; +} + +__inline +bool operator ==(const float2& a, const float2& b) +{ + return a.x==b.x && a.y==b.y; +} + +__inline +bool operator ==(const float4& a, const float4& b) +{ + return a.x==b.x && a.y==b.y && a.z==b.z && a.w==b.w; +} + +__inline +float4 operator-(const float4& a) +{ + return make_float4(-a.x, -a.y, -a.z, -a.w); +} + +__inline +float4 operator*(const float4& a, const float4& b) +{ +// ADLASSERT((u32(&a) & 0xf) == 0); + + float4 out; + out.s[0] = a.s[0]*b.s[0]; + out.s[1] = a.s[1]*b.s[1]; + out.s[2] = a.s[2]*b.s[2]; + out.s[3] = a.s[3]*b.s[3]; + return out; +} + +__inline +float4 operator*(float a, const float4& b) +{ + return make_float4(a*b.s[0], a*b.s[1], a*b.s[2], a*b.s[3]); +} + +__inline +float4 operator*(const float4& b, float a) +{ + CHECK_ALIGNMENT(b); + + return make_float4(a*b.s[0], a*b.s[1], a*b.s[2], a*b.s[3]); +} + +__inline +void operator*=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]*=b.s[0]; + a.s[1]*=b.s[1]; + a.s[2]*=b.s[2]; + a.s[3]*=b.s[3]; +} + +__inline +void operator*=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]*=b; + a.s[1]*=b; + a.s[2]*=b; + a.s[3]*=b; +} +/* +__inline +bool operator ==(const float4& a, const float4& b) +{ + + +} +*/ +// +__inline +float4 operator/(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]/b.s[0]; + out.s[1] = a.s[1]/b.s[1]; + out.s[2] = a.s[2]/b.s[2]; + out.s[3] = a.s[3]/b.s[3]; + return out; +} + +__inline +float4 operator/(const float4& b, float a) +{ + CHECK_ALIGNMENT(b); + + return make_float4(b.s[0]/a, b.s[1]/a, b.s[2]/a, b.s[3]/a); +} + +__inline +void operator/=(float4& a, const float4& b) +{ + a.s[0]/=b.s[0]; + a.s[1]/=b.s[1]; + a.s[2]/=b.s[2]; + a.s[3]/=b.s[3]; +} + +__inline +void operator/=(float4& a, float b) +{ + ADLASSERT((u32(&a) & 0xf) == 0); + + a.s[0]/=b; + a.s[1]/=b; + a.s[2]/=b; + a.s[3]/=b; +} +// + +__inline +float4 operator+(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]+b.s[0]; + out.s[1] = a.s[1]+b.s[1]; + out.s[2] = a.s[2]+b.s[2]; + out.s[3] = a.s[3]+b.s[3]; + return out; +} + +__inline +float4 operator+(const float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]+b; + out.s[1] = a.s[1]+b; + out.s[2] = a.s[2]+b; + out.s[3] = a.s[3]+b; + return out; +} + +__inline +float4 operator-(const float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]-b.s[0]; + out.s[1] = a.s[1]-b.s[1]; + out.s[2] = a.s[2]-b.s[2]; + out.s[3] = a.s[3]-b.s[3]; + return out; +} + +__inline +float4 operator-(const float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + float4 out; + out.s[0] = a.s[0]-b; + out.s[1] = a.s[1]-b; + out.s[2] = a.s[2]-b; + out.s[3] = a.s[3]-b; + return out; +} + +__inline +void operator+=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]+=b.s[0]; + a.s[1]+=b.s[1]; + a.s[2]+=b.s[2]; + a.s[3]+=b.s[3]; +} + +__inline +void operator+=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]+=b; + a.s[1]+=b; + a.s[2]+=b; + a.s[3]+=b; +} + +__inline +void operator-=(float4& a, const float4& b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]-=b.s[0]; + a.s[1]-=b.s[1]; + a.s[2]-=b.s[2]; + a.s[3]-=b.s[3]; +} + +__inline +void operator-=(float4& a, float b) +{ + CHECK_ALIGNMENT(a); + + a.s[0]-=b; + a.s[1]-=b; + a.s[2]-=b; + a.s[3]-=b; +} + + + + + +__inline +float4 cross3(const float4& a, const float4& b) +{ + return make_float4(a.s[1]*b.s[2]-a.s[2]*b.s[1], + a.s[2]*b.s[0]-a.s[0]*b.s[2], + a.s[0]*b.s[1]-a.s[1]*b.s[0], + 0); +} + +__inline +float dot3F4(const float4& a, const float4& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z; +} + +__inline +float length3(const float4& a) +{ + return sqrtf(dot3F4(a,a)); +} + +__inline +float dot4(const float4& a, const float4& b) +{ + return a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w; +} + +// for height +__inline +float dot3w1(const float4& point, const float4& eqn) +{ + return point.x*eqn.x+point.y*eqn.y+point.z*eqn.z+eqn.w; +} + +__inline +float4 normalize3(const float4& a) +{ + float length = sqrtf(dot3F4(a, a)); + return 1.f/length * a; +} + +__inline +float4 normalize4(const float4& a) +{ + float length = sqrtf(dot4(a, a)); + return 1.f/length * a; +} + +__inline +float4 createEquation(const float4& a, const float4& b, const float4& c) +{ + float4 eqn; + float4 ab = b-a; + float4 ac = c-a; + eqn = normalize3( cross3(ab, ac) ); + eqn.w = -dot3F4(eqn,a); + return eqn; +} + +__inline +float intersectPlaneLine( const float4& planeEqn, const float4& vec, const float4& orig ) +{ + return (-planeEqn.w - dot3F4(planeEqn, orig))/dot3F4(planeEqn, vec); +} + +template<> +__inline +float4 max2(const float4& a, const float4& b) +{ + return make_float4( max2(a.x,b.x), max2(a.y,b.y), max2(a.z,b.z), max2(a.w,b.w) ); +} + +template<> +__inline +float4 min2(const float4& a, const float4& b) +{ + return make_float4( min2(a.x,b.x), min2(a.y,b.y), min2(a.z,b.z), min2(a.w,b.w) ); +} + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Math.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Math.h new file mode 100644 index 000000000..0126e7289 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Math.h @@ -0,0 +1,224 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef CL_MATH_H +#define CL_MATH_H + +#include +#include +#include +#include + + +#include + +#include +#define pxSort std::sort + +#define PI 3.14159265358979323846f +#define NEXTMULTIPLEOF(num, alignment) (((num)/(alignment) + (((num)%(alignment)==0)?0:1))*(alignment)) + + +#define _MEM_CLASSALIGN16 __declspec(align(16)) +#define _MEM_ALIGNED_ALLOCATOR16 void* operator new(size_t size) { return _aligned_malloc( size, 16 ); } \ + void operator delete(void *p) { _aligned_free( p ); } \ + void* operator new[](size_t size) { return _aligned_malloc( size, 16 ); } \ + void operator delete[](void *p) { _aligned_free( p ); } \ + void* operator new(size_t size, void* p) { return p; } \ + void operator delete(void *p, void* pp) {} + +namespace adl +{ + +template +T nextPowerOf2(T n) +{ + n -= 1; + for(int i=0; i>i); + return n+1; +} + +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + +_MEM_CLASSALIGN16 +struct float4 +{ + _MEM_ALIGNED_ALLOCATOR16; + union + { + struct + { + float x,y,z,w; + }; + struct + { + float s[4]; + }; + __m128 m_quad; + }; +}; + +_MEM_CLASSALIGN16 +struct int4 +{ + _MEM_ALIGNED_ALLOCATOR16; + union + { + struct + { + int x,y,z,w; + }; + struct + { + int s[4]; + }; + }; +}; + +_MEM_CLASSALIGN16 +struct uint4 +{ + _MEM_ALIGNED_ALLOCATOR16; + union + { + struct + { + u32 x,y,z,w; + }; + struct + { + u32 s[4]; + }; + }; +}; + +struct int2 +{ + union + { + struct + { + int x,y; + }; + struct + { + int s[2]; + }; + }; +}; + +struct float2 +{ + union + { + struct + { + float x,y; + }; + struct + { + float s[2]; + }; + }; +}; + +template +__inline +T max2(const T& a, const T& b) +{ + return (a>b)? a:b; +} + +template +__inline +T min2(const T& a, const T& b) +{ + return (a +#include + + +template +void swap2(T& a, T& b) +{ + T tmp = a; + a = b; + b = tmp; +} + + +__inline +void seedRandom(int seed) +{ + srand( seed ); +} + +template +__inline +T getRandom(const T& minV, const T& maxV) +{ + float r = (rand()%10000)/10000.f; + T range = maxV - minV; + return (T)(minV + r*range); +} + +template<> +__inline +float4 getRandom(const float4& minV, const float4& maxV) +{ + float4 r = make_float4( (rand()%10000)/10000.f, (rand()%10000)/10000.f, (rand()%10000)/10000.f, (rand()%10000)/10000.f ); + float4 range = maxV - minV; + return (minV + r*range); +} + + + +template +T* addByteOffset(void* baseAddr, u32 offset) +{ + return (T*)(((u32)baseAddr)+offset); +} + + +struct Pair32 +{ + Pair32(){} + Pair32(u32 a, u32 b) : m_a(a), m_b(b){} + + u32 m_a; + u32 m_b; +}; + +struct PtrPair +{ + PtrPair(){} + PtrPair(void* a, void* b) : m_a(a), m_b(b){} + template + PtrPair(T* a, T* b) : m_a((void*)a), m_b((void*)b){} + + void* m_a; + void* m_b; +}; + +}; + +#endif diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/MathCL.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/MathCL.h new file mode 100644 index 000000000..6e36881ef --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/MathCL.h @@ -0,0 +1,357 @@ + +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_extended_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable +#pragma OPENCL EXTENSION cl_ext_atomic_counters_32 : enable + +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; + +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GET_NUM_GROUPS get_num_groups(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) +#define AppendInc(x, out) out = atomic_inc(x) +#define AtomAdd(x, value) atom_add(&(x), value) +#define AtomCmpxhg(x, cmp, value) atom_cmpxchg( &(x), cmp, value ) +#define AtomXhg(x, value) atom_xchg ( &(x), value ) + + +#define SELECT_UINT4( b, a, condition ) select( b,a,condition ) + +#define make_float4 (float4) +#define make_float2 (float2) +#define make_uint4 (uint4) +#define make_int4 (int4) +#define make_uint2 (uint2) +#define make_int2 (int2) + + +#define max2 max +#define min2 min + + +/////////////////////////////////////// +// Vector +/////////////////////////////////////// +__inline +float fastDiv(float numerator, float denominator) +{ + return native_divide(numerator, denominator); +// return numerator/denominator; +} + +__inline +float4 fastDiv4(float4 numerator, float4 denominator) +{ + return native_divide(numerator, denominator); +} + +__inline +float fastSqrtf(float f2) +{ + return native_sqrt(f2); +// return sqrt(f2); +} + +__inline +float fastRSqrt(float f2) +{ + return native_rsqrt(f2); +} + +__inline +float fastLength4(float4 v) +{ + return fast_length(v); +} + +__inline +float4 fastNormalize4(float4 v) +{ + return fast_normalize(v); +} + + +__inline +float sqrtf(float a) +{ +// return sqrt(a); + return native_sqrt(a); +} + +__inline +float4 cross3(float4 a, float4 b) +{ + return cross(a,b); +} + +__inline +float dot3F4(float4 a, float4 b) +{ + float4 a1 = make_float4(a.xyz,0.f); + float4 b1 = make_float4(b.xyz,0.f); + return dot(a1, b1); +} + +__inline +float length3(const float4 a) +{ + return sqrtf(dot3F4(a,a)); +} + +__inline +float dot4(const float4 a, const float4 b) +{ + return dot( a, b ); +} + +// for height +__inline +float dot3w1(const float4 point, const float4 eqn) +{ + return dot3F4(point,eqn) + eqn.w; +} + +__inline +float4 normalize3(const float4 a) +{ + float4 n = make_float4(a.x, a.y, a.z, 0.f); + return fastNormalize4( n ); +// float length = sqrtf(dot3F4(a, a)); +// return 1.f/length * a; +} + +__inline +float4 normalize4(const float4 a) +{ + float length = sqrtf(dot4(a, a)); + return 1.f/length * a; +} + +__inline +float4 createEquation(const float4 a, const float4 b, const float4 c) +{ + float4 eqn; + float4 ab = b-a; + float4 ac = c-a; + eqn = normalize3( cross3(ab, ac) ); + eqn.w = -dot3F4(eqn,a); + return eqn; +} + +/////////////////////////////////////// +// Matrix3x3 +/////////////////////////////////////// + +typedef struct +{ + float4 m_row[3]; +}Matrix3x3; + +__inline +Matrix3x3 mtZero(); + +__inline +Matrix3x3 mtIdentity(); + +__inline +Matrix3x3 mtTranspose(Matrix3x3 m); + +__inline +Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b); + +__inline +float4 mtMul1(Matrix3x3 a, float4 b); + +__inline +float4 mtMul3(float4 a, Matrix3x3 b); + +__inline +Matrix3x3 mtZero() +{ + Matrix3x3 m; + m.m_row[0] = (float4)(0.f); + m.m_row[1] = (float4)(0.f); + m.m_row[2] = (float4)(0.f); + return m; +} + +__inline +Matrix3x3 mtIdentity() +{ + Matrix3x3 m; + m.m_row[0] = (float4)(1,0,0,0); + m.m_row[1] = (float4)(0,1,0,0); + m.m_row[2] = (float4)(0,0,1,0); + return m; +} + +__inline +Matrix3x3 mtTranspose(Matrix3x3 m) +{ + Matrix3x3 out; + out.m_row[0] = (float4)(m.m_row[0].x, m.m_row[1].x, m.m_row[2].x, 0.f); + out.m_row[1] = (float4)(m.m_row[0].y, m.m_row[1].y, m.m_row[2].y, 0.f); + out.m_row[2] = (float4)(m.m_row[0].z, m.m_row[1].z, m.m_row[2].z, 0.f); + return out; +} + +__inline +Matrix3x3 mtMul(Matrix3x3 a, Matrix3x3 b) +{ + Matrix3x3 transB; + transB = mtTranspose( b ); + Matrix3x3 ans; + // why this doesn't run when 0ing in the for{} + a.m_row[0].w = 0.f; + a.m_row[1].w = 0.f; + a.m_row[2].w = 0.f; + for(int i=0; i<3; i++) + { +// a.m_row[i].w = 0.f; + ans.m_row[i].x = dot3F4(a.m_row[i],transB.m_row[0]); + ans.m_row[i].y = dot3F4(a.m_row[i],transB.m_row[1]); + ans.m_row[i].z = dot3F4(a.m_row[i],transB.m_row[2]); + ans.m_row[i].w = 0.f; + } + return ans; +} + +__inline +float4 mtMul1(Matrix3x3 a, float4 b) +{ + float4 ans; + ans.x = dot3F4( a.m_row[0], b ); + ans.y = dot3F4( a.m_row[1], b ); + ans.z = dot3F4( a.m_row[2], b ); + ans.w = 0.f; + return ans; +} + +__inline +float4 mtMul3(float4 a, Matrix3x3 b) +{ + float4 colx = make_float4(b.m_row[0].x, b.m_row[1].x, b.m_row[2].x, 0); + float4 coly = make_float4(b.m_row[0].y, b.m_row[1].y, b.m_row[2].y, 0); + float4 colz = make_float4(b.m_row[0].z, b.m_row[1].z, b.m_row[2].z, 0); + + float4 ans; + ans.x = dot3F4( a, colx ); + ans.y = dot3F4( a, coly ); + ans.z = dot3F4( a, colz ); + return ans; +} + +/////////////////////////////////////// +// Quaternion +/////////////////////////////////////// + +typedef float4 Quaternion; + +__inline +Quaternion qtMul(Quaternion a, Quaternion b); + +__inline +Quaternion qtNormalize(Quaternion in); + +__inline +float4 qtRotate(Quaternion q, float4 vec); + +__inline +Quaternion qtInvert(Quaternion q); + +__inline +Matrix3x3 qtGetRotationMatrix(Quaternion q); + + + +__inline +Quaternion qtMul(Quaternion a, Quaternion b) +{ + Quaternion ans; + ans = cross3( a, b ); + ans += a.w*b+b.w*a; +// ans.w = a.w*b.w - (a.x*b.x+a.y*b.y+a.z*b.z); + ans.w = a.w*b.w - dot3F4(a, b); + return ans; +} + +__inline +Quaternion qtNormalize(Quaternion in) +{ + return fastNormalize4(in); +// in /= length( in ); +// return in; +} +__inline +float4 qtRotate(Quaternion q, float4 vec) +{ + Quaternion qInv = qtInvert( q ); + float4 vcpy = vec; + vcpy.w = 0.f; + float4 out = qtMul(qtMul(q,vcpy),qInv); + return out; +} + +__inline +Quaternion qtInvert(Quaternion q) +{ + return (Quaternion)(-q.xyz, q.w); +} + +__inline +float4 qtInvRotate(const Quaternion q, float4 vec) +{ + return qtRotate( qtInvert( q ), vec ); +} + +__inline +Matrix3x3 qtGetRotationMatrix(Quaternion quat) +{ + float4 quat2 = (float4)(quat.x*quat.x, quat.y*quat.y, quat.z*quat.z, 0.f); + Matrix3x3 out; + + out.m_row[0].x=1-2*quat2.y-2*quat2.z; + out.m_row[0].y=2*quat.x*quat.y-2*quat.w*quat.z; + out.m_row[0].z=2*quat.x*quat.z+2*quat.w*quat.y; + out.m_row[0].w = 0.f; + + out.m_row[1].x=2*quat.x*quat.y+2*quat.w*quat.z; + out.m_row[1].y=1-2*quat2.x-2*quat2.z; + out.m_row[1].z=2*quat.y*quat.z-2*quat.w*quat.x; + out.m_row[1].w = 0.f; + + out.m_row[2].x=2*quat.x*quat.z-2*quat.w*quat.y; + out.m_row[2].y=2*quat.y*quat.z+2*quat.w*quat.x; + out.m_row[2].z=1-2*quat2.x-2*quat2.y; + out.m_row[2].w = 0.f; + + return out; +} + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Matrix3x3.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Matrix3x3.h new file mode 100644 index 000000000..d68176835 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Matrix3x3.h @@ -0,0 +1,197 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#ifndef MATRIX3X3_H +#define MATRIX3X3_H + +#include + +/////////////////////////////////////// +// Matrix3x3 +/////////////////////////////////////// +namespace adl +{ + +typedef +_MEM_CLASSALIGN16 struct +{ + _MEM_ALIGNED_ALLOCATOR16; + float4 m_row[3]; +}Matrix3x3; + +__inline +Matrix3x3 mtZero(); + +__inline +Matrix3x3 mtIdentity(); + +__inline +Matrix3x3 mtDiagonal(float a, float b, float c); + +__inline +Matrix3x3 mtTranspose(const Matrix3x3& m); + +__inline +Matrix3x3 mtMul(const Matrix3x3& a, const Matrix3x3& b); + +__inline +float4 mtMul1(const Matrix3x3& a, const float4& b); + +__inline +Matrix3x3 mtMul2(float a, const Matrix3x3& b); + +__inline +float4 mtMul3(const float4& b, const Matrix3x3& a); + +__inline +Matrix3x3 mtInvert(const Matrix3x3& m); + +__inline +Matrix3x3 mtZero() +{ + Matrix3x3 m; + m.m_row[0] = make_float4(0.f); + m.m_row[1] = make_float4(0.f); + m.m_row[2] = make_float4(0.f); + return m; +} + +__inline +Matrix3x3 mtIdentity() +{ + Matrix3x3 m; + m.m_row[0] = make_float4(1,0,0); + m.m_row[1] = make_float4(0,1,0); + m.m_row[2] = make_float4(0,0,1); + return m; +} + +__inline +Matrix3x3 mtDiagonal(float a, float b, float c) +{ + Matrix3x3 m; + m.m_row[0] = make_float4(a,0,0); + m.m_row[1] = make_float4(0,b,0); + m.m_row[2] = make_float4(0,0,c); + return m; +} + +__inline +Matrix3x3 mtTranspose(const Matrix3x3& m) +{ + Matrix3x3 out; + out.m_row[0] = make_float4(m.m_row[0].s[0], m.m_row[1].s[0], m.m_row[2].s[0], 0.f); + out.m_row[1] = make_float4(m.m_row[0].s[1], m.m_row[1].s[1], m.m_row[2].s[1], 0.f); + out.m_row[2] = make_float4(m.m_row[0].s[2], m.m_row[1].s[2], m.m_row[2].s[2], 0.f); + return out; +} + +__inline +Matrix3x3 mtMul(const Matrix3x3& a, const Matrix3x3& b) +{ + Matrix3x3 transB; + transB = mtTranspose( b ); + Matrix3x3 ans; + for(int i=0; i<3; i++) + { + ans.m_row[i].s[0] = dot3F4(a.m_row[i],transB.m_row[0]); + ans.m_row[i].s[1] = dot3F4(a.m_row[i],transB.m_row[1]); + ans.m_row[i].s[2] = dot3F4(a.m_row[i],transB.m_row[2]); + } + return ans; +} + +__inline +float4 mtMul1(const Matrix3x3& a, const float4& b) +{ + float4 ans; + ans.s[0] = dot3F4( a.m_row[0], b ); + ans.s[1] = dot3F4( a.m_row[1], b ); + ans.s[2] = dot3F4( a.m_row[2], b ); + return ans; +} + +__inline +Matrix3x3 mtMul2(float a, const Matrix3x3& b) +{ + Matrix3x3 ans; + ans.m_row[0] = a*b.m_row[0]; + ans.m_row[1] = a*b.m_row[1]; + ans.m_row[2] = a*b.m_row[2]; + return ans; +} + +__inline +float4 mtMul3(const float4& a, const Matrix3x3& b) +{ + float4 ans; + ans.x = a.x*b.m_row[0].x + a.y*b.m_row[1].x + a.z*b.m_row[2].x; + ans.y = a.x*b.m_row[0].y + a.y*b.m_row[1].y + a.z*b.m_row[2].y; + ans.z = a.x*b.m_row[0].z + a.y*b.m_row[1].z + a.z*b.m_row[2].z; + return ans; +} + +__inline +Matrix3x3 mtInvert(const Matrix3x3& m) +{ + float det = m.m_row[0].s[0]*m.m_row[1].s[1]*m.m_row[2].s[2]+m.m_row[1].s[0]*m.m_row[2].s[1]*m.m_row[0].s[2]+m.m_row[2].s[0]*m.m_row[0].s[1]*m.m_row[1].s[2] + -m.m_row[0].s[0]*m.m_row[2].s[1]*m.m_row[1].s[2]-m.m_row[2].s[0]*m.m_row[1].s[1]*m.m_row[0].s[2]-m.m_row[1].s[0]*m.m_row[0].s[1]*m.m_row[2].s[2]; + + ADLASSERT( det ); + + Matrix3x3 ans; + ans.m_row[0].s[0] = m.m_row[1].s[1]*m.m_row[2].s[2] - m.m_row[1].s[2]*m.m_row[2].s[1]; + ans.m_row[0].s[1] = m.m_row[0].s[2]*m.m_row[2].s[1] - m.m_row[0].s[1]*m.m_row[2].s[2]; + ans.m_row[0].s[2] = m.m_row[0].s[1]*m.m_row[1].s[2] - m.m_row[0].s[2]*m.m_row[1].s[1]; + ans.m_row[0].w = 0.f; + + ans.m_row[1].s[0] = m.m_row[1].s[2]*m.m_row[2].s[0] - m.m_row[1].s[0]*m.m_row[2].s[2]; + ans.m_row[1].s[1] = m.m_row[0].s[0]*m.m_row[2].s[2] - m.m_row[0].s[2]*m.m_row[2].s[0]; + ans.m_row[1].s[2] = m.m_row[0].s[2]*m.m_row[1].s[0] - m.m_row[0].s[0]*m.m_row[1].s[2]; + ans.m_row[1].w = 0.f; + + ans.m_row[2].s[0] = m.m_row[1].s[0]*m.m_row[2].s[1] - m.m_row[1].s[1]*m.m_row[2].s[0]; + ans.m_row[2].s[1] = m.m_row[0].s[1]*m.m_row[2].s[0] - m.m_row[0].s[0]*m.m_row[2].s[1]; + ans.m_row[2].s[2] = m.m_row[0].s[0]*m.m_row[1].s[1] - m.m_row[0].s[1]*m.m_row[1].s[0]; + ans.m_row[2].w = 0.f; + + ans = mtMul2((1.0f/det), ans); + return ans; +} + +__inline +Matrix3x3 mtSet( const float4& a, const float4& b, const float4& c ) +{ + Matrix3x3 m; + m.m_row[0] = a; + m.m_row[1] = b; + m.m_row[2] = c; + return m; +} + +__inline +Matrix3x3 operator+(const Matrix3x3& a, const Matrix3x3& b) +{ + Matrix3x3 out; + out.m_row[0] = a.m_row[0] + b.m_row[0]; + out.m_row[1] = a.m_row[1] + b.m_row[1]; + out.m_row[2] = a.m_row[2] + b.m_row[2]; + return out; +} + +}; + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Quaternion.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Quaternion.h new file mode 100644 index 000000000..3eeef4431 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Math/Quaternion.h @@ -0,0 +1,159 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#ifndef QUATERNION_H +#define QUATERNION_H + +#include + +namespace adl +{ + +typedef float4 Quaternion; + +__inline +Quaternion qtSet(const float4& axis, float angle); + +__inline +Quaternion qtMul(const Quaternion& a, const Quaternion& b); + +__inline +float4 qtRotate(const Quaternion& q, const float4& vec); + +__inline +float4 qtInvRotate(const Quaternion& q, const float4& vec); + +__inline +Quaternion qtInvert(const Quaternion& q); + +__inline +Matrix3x3 qtGetRotationMatrix(const Quaternion& quat); + +__inline +Quaternion qtNormalize(const Quaternion& q); + +__inline +Quaternion qtGetIdentity() { return make_float4(0,0,0,1); } + +__inline +Quaternion qtSet(const float4& axis, float angle) +{ + float4 nAxis = normalize3( axis ); + + Quaternion q; + q.s[0] = nAxis.s[0]*sin(angle/2); + q.s[1] = nAxis.s[1]*sin(angle/2); + q.s[2] = nAxis.s[2]*sin(angle/2); + q.s[3] = cos(angle/2); + return q; +} + +__inline +Quaternion qtMul(const Quaternion& a, const Quaternion& b) +{ + Quaternion ans; + ans = cross3( a, b ); + ans += a.s[3]*b + b.s[3]*a; + ans.s[3] = a.s[3]*b.s[3] - (a.s[0]*b.s[0]+a.s[1]*b.s[1]+a.s[2]*b.s[2]); + return ans; +} + +__inline +float4 qtRotate(const Quaternion& q, const float4& vec) +{ + Quaternion vecQ = vec; + vecQ.s[3] = 0.f; + Quaternion qInv = qtInvert( q ); + float4 out = qtMul(qtMul(q,vecQ),qInv); + return out; +} + +__inline +float4 qtInvRotate(const Quaternion& q, const float4& vec) +{ + return qtRotate( qtInvert( q ), vec ); +} + +__inline +Quaternion qtInvert(const Quaternion& q) +{ + Quaternion ans; + ans.s[0] = -q.s[0]; + ans.s[1] = -q.s[1]; + ans.s[2] = -q.s[2]; + ans.s[3] = q.s[3]; + return ans; +} + +__inline +Matrix3x3 qtGetRotationMatrix(const Quaternion& quat) +{ + float4 quat2 = make_float4(quat.s[0]*quat.s[0], quat.s[1]*quat.s[1], quat.s[2]*quat.s[2], 0.f); + Matrix3x3 out; + + out.m_row[0].s[0]=1-2*quat2.s[1]-2*quat2.s[2]; + out.m_row[0].s[1]=2*quat.s[0]*quat.s[1]-2*quat.s[3]*quat.s[2]; + out.m_row[0].s[2]=2*quat.s[0]*quat.s[2]+2*quat.s[3]*quat.s[1]; + out.m_row[0].s[3] = 0.f; + + out.m_row[1].s[0]=2*quat.s[0]*quat.s[1]+2*quat.s[3]*quat.s[2]; + out.m_row[1].s[1]=1-2*quat2.s[0]-2*quat2.s[2]; + out.m_row[1].s[2]=2*quat.s[1]*quat.s[2]-2*quat.s[3]*quat.s[0]; + out.m_row[1].s[3] = 0.f; + + out.m_row[2].s[0]=2*quat.s[0]*quat.s[2]-2*quat.s[3]*quat.s[1]; + out.m_row[2].s[1]=2*quat.s[1]*quat.s[2]+2*quat.s[3]*quat.s[0]; + out.m_row[2].s[2]=1-2*quat2.s[0]-2*quat2.s[1]; + out.m_row[2].s[3] = 0.f; + + return out; +} + +__inline +Quaternion qtGetQuaternion(const Matrix3x3* m) +{ + Quaternion q; + q.w = sqrtf( m[0].m_row[0].x + m[0].m_row[1].y + m[0].m_row[2].z + 1 ) * 0.5f; + float inv4w = 1.f/(4.f*q.w); + q.x = (m[0].m_row[2].y-m[0].m_row[1].z)*inv4w; + q.y = (m[0].m_row[0].z-m[0].m_row[2].x)*inv4w; + q.z = (m[0].m_row[1].x-m[0].m_row[0].y)*inv4w; + + return q; +} + +__inline +Quaternion qtNormalize(const Quaternion& q) +{ + return normalize4(q); +} + +__inline +float4 transform(const float4& p, const float4& translation, const Quaternion& orientation) +{ + return qtRotate( orientation, p ) + translation; +} + +__inline +float4 invTransform(const float4& p, const float4& translation, const Quaternion& orientation) +{ + return qtRotate( qtInvert( orientation ), p-translation ); // use qtInvRotate +} + +}; + +#endif + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.h new file mode 100644 index 000000000..db7566ede --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#pragma once + +#include +#include + +namespace adl +{ + +class PrefixScanBase +{ + public: + enum Option + { + INCLUSIVE, + EXCLUSIVE + }; +}; + + +template +class PrefixScan : public PrefixScanBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + enum + { + BLOCK_SIZE = 128 + }; + + struct Data + { + Option m_option; + const Device* m_device; + Kernel* m_localScanKernel; + Kernel* m_blockSumKernel; + Kernel* m_propagationKernel; + Buffer* m_workBuffer; + Buffer* m_constBuffer[3];// todo. dx need one for each + int m_maxSize; + }; + + static + Data* allocate(const Device* deviceData, int maxSize, Option option = EXCLUSIVE); + + static + void deallocate(Data* data); + + static + void execute(Data* data, Buffer& src, Buffer& dst, int n, u32* sum = 0); +}; + + + +#include +#include + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.inl new file mode 100644 index 000000000..65e8c06a5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScan.inl @@ -0,0 +1,125 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#define PATH "..\\..\\opencl\\primitives\\AdlPrimitives\\Scan\\PrefixScanKernels" +#define KERNEL0 "LocalScanKernel" +#define KERNEL1 "TopLevelScanKernel" +#define KERNEL2 "AddOffsetKernel" + +#include +#include + +template +typename PrefixScan::Data* PrefixScan::allocate(const Device* device, int maxSize, Option option) +{ + ADLASSERT( TYPE == device->m_type ); + + ADLASSERT( maxSize <= BLOCK_SIZE*2*2048 ); + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {prefixScanKernelsCL, prefixScanKernelsDX11}; +#else + {0,0}; +#endif + Data* data = new Data; + data->m_device = device; + data->m_localScanKernel = device->getKernel( PATH, KERNEL0, 0, src[TYPE] ); + data->m_blockSumKernel = device->getKernel( PATH, KERNEL1, 0, src[TYPE] ); + data->m_propagationKernel = device->getKernel( PATH, KERNEL2, 0, src[TYPE] ); + + int bufSize = (NEXTMULTIPLEOF( max2( maxSize/BLOCK_SIZE, (int)BLOCK_SIZE ), BLOCK_SIZE )+1); + data->m_workBuffer = new Buffer( device, bufSize ); + data->m_constBuffer[0] = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + data->m_constBuffer[1] = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + data->m_constBuffer[2] = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + + data->m_maxSize = maxSize; + data->m_option = option; + + return data; +} + +template +void PrefixScan::deallocate(Data* data) +{ + delete data->m_workBuffer; + delete data->m_constBuffer[0]; + delete data->m_constBuffer[1]; + delete data->m_constBuffer[2]; + delete data; +} + +template +void PrefixScan::execute(Data* data, Buffer& src, Buffer& dst, int n, u32* sum) +{ + ADLASSERT( data ); + ADLASSERT( n <= data->m_maxSize ); + ADLASSERT( data->m_option == EXCLUSIVE ); + const u32 numBlocks = u32( (n+BLOCK_SIZE*2-1)/(BLOCK_SIZE*2) ); + + + int4 constBuffer; + constBuffer.x = n; + constBuffer.y = numBlocks; + constBuffer.z = (int)nextPowerOf2( numBlocks ); + + Buffer* srcNative = BufferUtils::map( data->m_device, &src ); + Buffer* dstNative = BufferUtils::map( data->m_device, &dst ); + + { + BufferInfo bInfo[] = { BufferInfo( dstNative ), BufferInfo( srcNative ), BufferInfo( data->m_workBuffer ) }; + + Launcher launcher( data->m_device, data->m_localScanKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[0], constBuffer ); + launcher.launch1D( numBlocks*BLOCK_SIZE, BLOCK_SIZE ); + } + + { + BufferInfo bInfo[] = { BufferInfo( data->m_workBuffer ) }; + + Launcher launcher( data->m_device, data->m_blockSumKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[1], constBuffer ); + launcher.launch1D( BLOCK_SIZE, BLOCK_SIZE ); + } + + + if( numBlocks > 1 ) + { + BufferInfo bInfo[] = { BufferInfo( dstNative ), BufferInfo( data->m_workBuffer ) }; + Launcher launcher( data->m_device, data->m_propagationKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[2], constBuffer ); + launcher.launch1D( (numBlocks-1)*BLOCK_SIZE, BLOCK_SIZE ); + } + + DeviceUtils::waitForCompletion( data->m_device ); + if( sum ) + { + dstNative->read( sum, 1, n-1); + } + DeviceUtils::waitForCompletion( data->m_device ); + + BufferUtils::unmap( srcNative, &src ); + BufferUtils::unmap( dstNative, &dst ); +} + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 +#undef KERNEL2 \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanHost.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanHost.inl new file mode 100644 index 000000000..44987f548 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Scan/PrefixScanHost.inl @@ -0,0 +1,74 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +template<> +class PrefixScan : public PrefixScanBase +{ + public: + struct Data + { + Option m_option; + }; + + static + Data* allocate(const Device* deviceData, int maxSize, Option option = EXCLUSIVE) + { + ADLASSERT( deviceData->m_type == TYPE_HOST ); + + Data* data = new Data; + data->m_option = option; + return data; + } + + static + void deallocate(Data* data) + { + delete data; + } + + static + void execute(Data* data, Buffer& src, Buffer& dst, int n, u32* sum = 0) + { + ADLASSERT( src.getType() == TYPE_HOST && dst.getType() == TYPE_HOST ); + HostBuffer& hSrc = (HostBuffer&)src; + HostBuffer& hDst = (HostBuffer&)dst; + + u32 s = 0; + if( data->m_option == EXCLUSIVE ) + { + for(int i=0; i>1; nActive>0; nActive>>=1, offset<<=1) + { + GROUP_LDS_BARRIER; + for(int iIdx=lIdx; iIdx>= 1; + for(int nActive=1; nActive>=1 ) + { + GROUP_LDS_BARRIER; + for( int iIdx = lIdx; iIdx dst : register( u0 ); +RWStructuredBuffer src : register( u1 ); +RWStructuredBuffer sumBuffer : register( u2 ); + + +groupshared u32 ldsData[2048]; + +u32 ScanExclusive(u32 n, int lIdx, int lSize) +{ + u32 blocksum; + int offset = 1; + for(int nActive=n>>1; nActive>0; nActive>>=1, offset<<=1) + { + GROUP_LDS_BARRIER; + for(int iIdx=lIdx; iIdx>= 1; + for(int nActive=1; nActive>=1 ) + { + GROUP_LDS_BARRIER; + for( int iIdx = lIdx; iIdx blockSum2 : register( u1 ); + +[numthreads(WG_SIZE, 1, 1)] +void AddOffsetKernel(uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID) +{ + const u32 blockSize = WG_SIZE*2; + + int myIdx = GET_GROUP_IDX+1; + int llIdx = GET_LOCAL_IDX; + + u32 iBlockSum = blockSum2[myIdx]; + + int endValue = min((myIdx+1)*(blockSize), m_numElems); + for(int i=myIdx*blockSize+llIdx; i>1; nActive>0; nActive>>=1, offset<<=1)\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +" for(int iIdx=lIdx; iIdx>= 1;\n" +" for(int nActive=1; nActive>=1 )\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +" for( int iIdx = lIdx; iIdx dst : register( u0 );\n" +"RWStructuredBuffer src : register( u1 );\n" +"RWStructuredBuffer sumBuffer : register( u2 );\n" +"\n" +"\n" +"groupshared u32 ldsData[2048];\n" +"\n" +"u32 ScanExclusive(u32 n, int lIdx, int lSize)\n" +"{\n" +" u32 blocksum;\n" +" int offset = 1;\n" +" for(int nActive=n>>1; nActive>0; nActive>>=1, offset<<=1)\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +" for(int iIdx=lIdx; iIdx>= 1;\n" +" for(int nActive=1; nActive>=1 )\n" +" {\n" +" GROUP_LDS_BARRIER;\n" +" for( int iIdx = lIdx; iIdx blockSum2 : register( u1 );\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void AddOffsetKernel(uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID)\n" +"{\n" +" const u32 blockSize = WG_SIZE*2;\n" +"\n" +" int myIdx = GET_GROUP_IDX+1;\n" +" int llIdx = GET_LOCAL_IDX;\n" +"\n" +" u32 iBlockSum = blockSum2[myIdx];\n" +"\n" +" int endValue = min((myIdx+1)*(blockSize), m_numElems);\n" +" for(int i=myIdx*blockSize+llIdx; i +#include +#include +#include + +namespace adl +{ + +class BoundSearchBase +{ + public: + enum Option + { + BOUND_LOWER, + BOUND_UPPER, + COUNT, + }; +}; + +template +class BoundSearch : public BoundSearchBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct Data + { + const Device* m_device; + Kernel* m_lowerSortDataKernel; + Kernel* m_upperSortDataKernel; + Kernel* m_subtractKernel; + Buffer* m_constBuffer; + Buffer* m_lower; + Buffer* m_upper; + typename Fill::Data* m_fillData; + }; + + static + Data* allocate(const Device* deviceData, int maxSize = 0); + + static + void deallocate(Data* data); + + // src has to be src[i].m_key <= src[i+1].m_key + static + void execute(Data* data, Buffer& src, u32 nSrc, Buffer& dst, u32 nDst, Option option = BOUND_LOWER ); + +// static +// void execute(Data* data, Buffer& src, Buffer& dst, int n, Option option = ); +}; + +#include +#include + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearch.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearch.inl new file mode 100644 index 000000000..33138b4e5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearch.inl @@ -0,0 +1,128 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + +#define PATH "..\\..\\opencl\\primitives\\AdlPrimitives\\Search\\BoundSearchKernels" +#define KERNEL0 "SearchSortDataLowerKernel" +#define KERNEL1 "SearchSortDataUpperKernel" +#define KERNEL2 "SubtractKernel" + +#include +#include + +template +typename BoundSearch::Data* BoundSearch::allocate(const Device* device, int maxSize) +{ + ADLASSERT( TYPE == device->m_type ); + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {boundSearchKernelsCL, boundSearchKernelsDX11}; +#else + {0,0}; +#endif + + Data* data = new Data; + + data->m_device = device; + data->m_lowerSortDataKernel = device->getKernel( PATH, KERNEL0, 0, src[TYPE] ); + data->m_upperSortDataKernel = device->getKernel( PATH, KERNEL1, 0, src[TYPE] ); + data->m_constBuffer = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + if( maxSize ) + { + data->m_subtractKernel = device->getKernel( PATH, KERNEL2, 0, src[TYPE] ); + } + data->m_lower = (maxSize == 0)? 0: new Buffer( device, maxSize ); + data->m_upper = (maxSize == 0)? 0: new Buffer( device, maxSize ); + data->m_fillData = (maxSize == 0)? 0: Fill::allocate( device ); + + return data; +} + +template +void BoundSearch::deallocate(Data* data) +{ + delete data->m_constBuffer; + if( data->m_lower ) delete data->m_lower; + if( data->m_upper ) delete data->m_upper; + if( data->m_fillData ) Fill::deallocate( data->m_fillData ); + delete data; +} + +template +void BoundSearch::execute(Data* data, Buffer& src, u32 nSrc, Buffer& dst, u32 nDst, Option option ) +{ + int4 constBuffer; + constBuffer.x = nSrc; + constBuffer.y = nDst; + + Buffer* srcNative = BufferUtils::map( data->m_device, &src ); + Buffer* dstNative = BufferUtils::map( data->m_device, &dst ); + + if( option == BOUND_LOWER ) + { + BufferInfo bInfo[] = { BufferInfo( srcNative, true ), BufferInfo( dstNative ) }; + + Launcher launcher( data->m_device, data->m_lowerSortDataKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( nSrc, 64 ); + } + else if( option == BOUND_UPPER ) + { + BufferInfo bInfo[] = { BufferInfo( srcNative, true ), BufferInfo( dstNative ) }; + + Launcher launcher( data->m_device, data->m_upperSortDataKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( nSrc+1, 64 ); + } + else if( option == COUNT ) + { + ADLASSERT( data->m_lower ); + ADLASSERT( data->m_upper ); + ADLASSERT( data->m_lower->getSize() <= (int)nDst ); + ADLASSERT( data->m_upper->getSize() <= (int)nDst ); + + int zero = 0; + Fill::execute( data->m_fillData, (Buffer&)*data->m_lower, zero, nDst ); + Fill::execute( data->m_fillData, (Buffer&)*data->m_upper, zero, nDst ); + + execute( data, src, nSrc, *data->m_lower, nDst, BOUND_LOWER ); + execute( data, src, nSrc, *data->m_upper, nDst, BOUND_UPPER ); + + { + BufferInfo bInfo[] = { BufferInfo( data->m_upper, true ), BufferInfo( data->m_lower, true ), BufferInfo( dstNative ) }; + + Launcher launcher( data->m_device, data->m_subtractKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer, constBuffer ); + launcher.launch1D( nDst, 64 ); + } + } + else + { + ADLASSERT( 0 ); + } + + BufferUtils::unmap( srcNative, &src ); + BufferUtils::unmap( dstNative, &dst ); +} + + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 +#undef KERNEL2 + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchHost.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchHost.inl new file mode 100644 index 000000000..b53b3ba48 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchHost.inl @@ -0,0 +1,111 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +template<> +class BoundSearch : public BoundSearchBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + struct Data + { + const Device* m_device; + }; + + static + Data* allocate(const Device* deviceData, int maxSize = 0) + { + ADLASSERT( deviceData->m_type == TYPE_HOST ); + Data* data = new Data; + data->m_device = deviceData; + return data; + } + + static + void deallocate(Data* data) + { + delete data; + } + + static + void execute(Data* data, Buffer& rawSrc, u32 nSrc, Buffer& rawDst, u32 nDst, Option option = BOUND_LOWER) + { + ADLASSERT( rawSrc.getType() == TYPE_HOST ); + ADLASSERT( rawDst.getType() == TYPE_HOST ); + + HostBuffer& src = *(HostBuffer*)&rawSrc; + HostBuffer& dst = *(HostBuffer*)&rawDst; + + for(int i=0; i lower( data->m_device, nDst ); + HostBuffer upper( data->m_device, nDst ); + + for(u32 i=0; i& src, Buffer& dst, int n, Option option = ); +}; + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernels.cl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernels.cl new file mode 100644 index 000000000..105a17a43 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Search/BoundSearchKernels.cl @@ -0,0 +1,112 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +typedef unsigned int u32; +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) + +typedef struct +{ + u32 m_key; + u32 m_value; +}SortData; + + + +typedef struct +{ + u32 m_nSrc; + u32 m_nDst; + u32 m_padding[2]; +} ConstBuffer; + + + +__attribute__((reqd_work_group_size(64,1,1))) +__kernel +void SearchSortDataLowerKernel(__global SortData* src, __global u32 *dst, + ConstBuffer cb) +{ + int gIdx = GET_GLOBAL_IDX; + u32 nSrc = cb.m_nSrc; + u32 nDst = cb.m_nDst; + + if( gIdx < nSrc ) + { + SortData first; first.m_key = (u32)(-1); first.m_value = (u32)(-1); + SortData end; end.m_key = nDst; end.m_value = nDst; + + SortData iData = (gIdx==0)? first: src[gIdx-1]; + SortData jData = (gIdx==nSrc)? end: src[gIdx]; + + if( iData.m_key != jData.m_key ) + { +// for(u32 k=iData.m_key+1; k<=min(jData.m_key, nDst-1); k++) + u32 k = jData.m_key; + { + dst[k] = gIdx; + } + } + } +} + + +__attribute__((reqd_work_group_size(64,1,1))) +__kernel +void SearchSortDataUpperKernel(__global SortData* src, __global u32 *dst, + ConstBuffer cb) +{ + int gIdx = GET_GLOBAL_IDX; + u32 nSrc = cb.m_nSrc; + u32 nDst = cb.m_nDst; + + if( gIdx < nSrc+1 ) + { + SortData first; first.m_key = 0; first.m_value = 0; + SortData end; end.m_key = nDst; end.m_value = nDst; + + SortData iData = (gIdx==0)? first: src[gIdx-1]; + SortData jData = (gIdx==nSrc)? end: src[gIdx]; + + if( iData.m_key != jData.m_key ) + { +// for(u32 k=iData.m_key; k src : register( t0 ); +RWStructuredBuffer dst : register( u0 ); + + +[numthreads(64, 1, 1)] +void SearchSortDataLowerKernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + u32 nSrc = m_nSrc; + u32 nDst = m_nDst; + + if( gIdx < nSrc ) + { + SortData iData; + SortData jData; + if( gIdx==0 ) iData.m_key = iData.m_value = (u32)-1; + else iData = src[gIdx-1]; + + if( gIdx==nSrc ) jData.m_key = jData.m_value = nDst; + else jData = src[gIdx]; + + if( iData.m_key != jData.m_key ) + { +// for(u32 k=iData.m_key+1; k<=min(jData.m_key, nDst-1); k++) + u32 k = jData.m_key; + { + dst[k] = gIdx; + } + } + } +} + +[numthreads(64, 1, 1)] +void SearchSortDataUpperKernel( DEFAULT_ARGS ) +{ + int gIdx = GET_GLOBAL_IDX; + u32 nSrc = m_nSrc; + u32 nDst = m_nDst; + + if( gIdx < nSrc+1 ) + { + SortData iData; + SortData jData; + if( gIdx==0 ) iData.m_key = iData.m_value = 0; + else iData = src[gIdx-1]; + + if( gIdx==nSrc ) jData.m_key = jData.m_value = nDst; + else jData = src[gIdx]; + + if( iData.m_key != jData.m_key ) + { +// for(u32 k=iData.m_key; k src : register( t0 );\n" +"RWStructuredBuffer dst : register( u0 );\n" +"\n" +"\n" +"[numthreads(64, 1, 1)]\n" +"void SearchSortDataLowerKernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" u32 nSrc = m_nSrc;\n" +" u32 nDst = m_nDst;\n" +"\n" +" if( gIdx < nSrc )\n" +" {\n" +" SortData iData;\n" +" SortData jData;\n" +" if( gIdx==0 ) iData.m_key = iData.m_value = (u32)-1;\n" +" else iData = src[gIdx-1];\n" +"\n" +" if( gIdx==nSrc ) jData.m_key = jData.m_value = nDst;\n" +" else jData = src[gIdx];\n" +"\n" +" if( iData.m_key != jData.m_key )\n" +" {\n" +"// for(u32 k=iData.m_key+1; k<=min(jData.m_key, nDst-1); k++)\n" +" u32 k = jData.m_key;\n" +" {\n" +" dst[k] = gIdx;\n" +" }\n" +" }\n" +" }\n" +"}\n" +"\n" +"[numthreads(64, 1, 1)]\n" +"void SearchSortDataUpperKernel( DEFAULT_ARGS )\n" +"{\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" u32 nSrc = m_nSrc;\n" +" u32 nDst = m_nDst;\n" +"\n" +" if( gIdx < nSrc+1 )\n" +" {\n" +" SortData iData;\n" +" SortData jData;\n" +" if( gIdx==0 ) iData.m_key = iData.m_value = 0;\n" +" else iData = src[gIdx-1];\n" +"\n" +" if( gIdx==nSrc ) jData.m_key = jData.m_value = nDst;\n" +" else jData = src[gIdx];\n" +"\n" +" if( iData.m_key != jData.m_key )\n" +" {\n" +"// for(u32 k=iData.m_key; k +#include +#include +#include + +namespace adl +{ + +class RadixSortBase +{ + public: + enum Option + { + SORT_SIMPLE, + SORT_STANDARD, + SORT_ADVANCED + }; +}; + +template +class RadixSort : public RadixSortBase +{ + public: + struct Data + { + Option m_option; + const Device* m_deviceData; + typename PrefixScan::Data* m_scanData; + int m_maxSize; + }; + + + static + Data* allocate(const Device* deviceData, int maxSize, Option option = SORT_STANDARD); + + static + void deallocate(Data* data); + + static + void execute(Data* data, Buffer& inout, int n, int sortBits = 32); +}; + + +#include +#include + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort.inl new file mode 100644 index 000000000..f7da098b6 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort.inl @@ -0,0 +1,58 @@ +/* + 2011 Takahiro Harada +*/ + +#include +#include +#include + + +#define DISPATCH_IMPL(x) \ + switch( data->m_option ) \ + { \ + case SORT_SIMPLE: RadixSortSimple::x; break; \ + case SORT_STANDARD: RadixSortStandard::x; break; \ + case SORT_ADVANCED: RadixSortAdvanced::x; break; \ + default:ADLASSERT(0);break; \ + } + +template +typename RadixSort::Data* RadixSort::allocate(const Device* deviceData, int maxSize, Option option) +{ + ADLASSERT( TYPE == deviceData->m_type ); + + void* dataOut; + switch( option ) + { + case SORT_SIMPLE: + dataOut = RadixSortSimple::allocate( deviceData, maxSize, option ); + break; + case SORT_STANDARD: + dataOut = RadixSortStandard::allocate( deviceData, maxSize, option ); + break; + case SORT_ADVANCED: + dataOut = RadixSortAdvanced::allocate( deviceData, maxSize, option ); + break; + default: + ADLASSERT(0); + break; + } + return (typename RadixSort::Data*)dataOut; +} + +template +void RadixSort::deallocate(Data* data) +{ + DISPATCH_IMPL( deallocate( data ) ); +} + +template +void RadixSort::execute(Data* data, Buffer& inout, int n, int sortBits) +{ + DISPATCH_IMPL( execute( data, inout, n, sortBits ) ); +} + + +#undef DISPATCH_IMPL + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.h new file mode 100644 index 000000000..c5433e72f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.h @@ -0,0 +1,98 @@ +/* + 2011 Takahiro Harada +*/ + +#pragma once + +#include +#include +#include +#include + +namespace adl +{ + +class RadixSort32Base +{ + public: +// enum Option +// { +// SORT_SIMPLE, +// SORT_STANDARD, +// SORT_ADVANCED +// }; +}; + +template +class RadixSort32 : public RadixSort32Base +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + enum + { + DATA_ALIGNMENT = 256, + WG_SIZE = 64, + ELEMENTS_PER_WORK_ITEM = (256/WG_SIZE), + BITS_PER_PASS = 4, + + // if you change this, change nPerWI in kernel as well + NUM_WGS = 20*6, // cypress +// NUM_WGS = 24*6, // cayman +// NUM_WGS = 32*4, // nv + }; + + struct ConstData + { + int m_n; + int m_nWGs; + int m_startBit; + int m_nBlocksPerWG; + }; + + struct Data + { + const Device* m_device; + int m_maxSize; + + Kernel* m_streamCountKernel; + Kernel* m_streamCountSortDataKernel; + Kernel* m_prefixScanKernel; + Kernel* m_sortAndScatterKernel; + Kernel* m_sortAndScatterKeyValueKernel; + Kernel* m_sortAndScatterSortDataKernel; + + Buffer* m_workBuffer0; + Buffer* m_workBuffer1; + Buffer* m_workBuffer2; + Buffer* m_workBuffer3; + + Buffer* m_constBuffer[32/BITS_PER_PASS]; + + typename Copy::Data* m_copyData; + }; + + static + Data* allocate(const Device* device, int maxSize); + + static + void deallocate(Data* data); + + static + void execute(Data* data, Buffer& inout, int n, int sortBits = 32); + + static + void execute(Data* data, Buffer& in, Buffer& out, int n, int sortBits = 32); + + static + void execute(Data* data, Buffer& keysIn, Buffer& keysOut, Buffer& valuesIn, Buffer& valuesOut, int n, int sortBits = 32); + + static + void execute(Data* data, Buffer& keyValuesInOut, int n, int sortBits = 32 ); +}; + + +#include +#include + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.inl new file mode 100644 index 000000000..468943227 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32.inl @@ -0,0 +1,346 @@ +/* + 2011 Takahiro Harada +*/ + +#define PATH "..\\..\\opencl\\primitives\\AdlPrimitives\\Sort\\RadixSort32Kernels" +#define RADIXSORT32_KERNEL0 "StreamCountKernel" +#define RADIXSORT32_KERNEL1 "PrefixScanKernel" +#define RADIXSORT32_KERNEL2 "SortAndScatterKernel" +#define RADIXSORT32_KERNEL3 "SortAndScatterKeyValueKernel" +#define RADIXSORT32_KERNEL4 "SortAndScatterSortDataKernel" +#define RADIXSORT32_KERNEL5 "StreamCountSortDataKernel" + +#include "RadixSort32KernelsCL.h" +#include "RadixSort32KernelsDX11.h" + +// todo. Shader compiler (2010JuneSDK) doesn't allow me to place Barriers in SortAndScatterKernel... +// So it only works on a GPU with 64 wide SIMD. + +template +typename RadixSort32::Data* RadixSort32::allocate( const Device* device, int maxSize ) +{ + ADLASSERT( TYPE == device->m_type ); + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {radixSort32KernelsCL, radixSort32KernelsDX11}; +#else + {0,0}; +#endif + + Data* data = new Data; + data->m_device = device; + data->m_maxSize = maxSize; + data->m_streamCountKernel = device->getKernel( PATH, RADIXSORT32_KERNEL0, 0, src[TYPE] ); + data->m_streamCountSortDataKernel = device->getKernel( PATH, RADIXSORT32_KERNEL5, 0, src[TYPE] ); + + + + data->m_prefixScanKernel = device->getKernel( PATH, RADIXSORT32_KERNEL1, 0, src[TYPE] ); + data->m_sortAndScatterKernel = device->getKernel( PATH, RADIXSORT32_KERNEL2, 0, src[TYPE] ); + data->m_sortAndScatterKeyValueKernel = device->getKernel( PATH, RADIXSORT32_KERNEL3, 0, src[TYPE] ); + data->m_sortAndScatterSortDataKernel = device->getKernel( PATH, RADIXSORT32_KERNEL4, 0, src[TYPE] ); + + int wtf = NUM_WGS*(1<m_workBuffer0 = new Buffer( device, maxSize ); + data->m_workBuffer1 = new Buffer( device , wtf ); + data->m_workBuffer2 = new Buffer( device, maxSize ); + data->m_workBuffer3 = new Buffer(device,maxSize); + + + for(int i=0; i<32/BITS_PER_PASS; i++) + data->m_constBuffer[i] = new Buffer( device, 1, BufferBase::BUFFER_CONST ); + + data->m_copyData = Copy::allocate( device ); + + return data; +} + +template +void RadixSort32::deallocate( Data* data ) +{ + delete data->m_workBuffer0; + delete data->m_workBuffer1; + delete data->m_workBuffer2; + delete data->m_workBuffer3; + + for(int i=0; i<32/BITS_PER_PASS; i++) + delete data->m_constBuffer[i]; + + Copy::deallocate( data->m_copyData ); + + delete data; +} + +template +void RadixSort32::execute(Data* data, Buffer& inout, int n, int sortBits /* = 32 */ ) +{ + ADLASSERT( n%DATA_ALIGNMENT == 0 ); + ADLASSERT( n <= data->m_maxSize ); +// ADLASSERT( ELEMENTS_PER_WORK_ITEM == 4 ); + ADLASSERT( BITS_PER_PASS == 4 ); + ADLASSERT( WG_SIZE == 64 ); + ADLASSERT( (sortBits&0x3) == 0 ); + + Buffer* src = &inout; + Buffer* dst = data->m_workBuffer0; + Buffer* histogramBuffer = data->m_workBuffer1; + + int nWGs = NUM_WGS; + ConstData cdata; + { + int nBlocks = (n+ELEMENTS_PER_WORK_ITEM*WG_SIZE-1)/(ELEMENTS_PER_WORK_ITEM*WG_SIZE); + + cdata.m_n = n; + cdata.m_nWGs = NUM_WGS; + cdata.m_startBit = 0; + cdata.m_nBlocksPerWG = (nBlocks + cdata.m_nWGs - 1)/cdata.m_nWGs; + + if( nBlocks < NUM_WGS ) + { + cdata.m_nBlocksPerWG = 1; + nWGs = nBlocks; + } + } + + for(int ib=0; ibm_device, data->m_streamCountKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( NUM_WGS*WG_SIZE, WG_SIZE ); + } + {// prefix scan group histogram + BufferInfo bInfo[] = { BufferInfo( histogramBuffer ) }; + Launcher launcher( data->m_device, data->m_prefixScanKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( 128, 128 ); + } + {// local sort and distribute + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( histogramBuffer, true ), BufferInfo( dst ) }; + Launcher launcher( data->m_device, data->m_sortAndScatterKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( nWGs*WG_SIZE, WG_SIZE ); + } + swap2( src, dst ); + } + + if( src != &inout ) + { + Copy::execute( data->m_copyData, (Buffer&)inout, (Buffer&)*src, n ); + } +} + +template +void RadixSort32::execute(Data* data, Buffer& in, Buffer& out, int n, int sortBits /* = 32 */ ) +{ + ADLASSERT( n%DATA_ALIGNMENT == 0 ); + ADLASSERT( n <= data->m_maxSize ); +// ADLASSERT( ELEMENTS_PER_WORK_ITEM == 4 ); + ADLASSERT( BITS_PER_PASS == 4 ); + ADLASSERT( WG_SIZE == 64 ); + ADLASSERT( (sortBits&0x3) == 0 ); + + Buffer* src = ∈ + Buffer* dst = data->m_workBuffer0; + Buffer* histogramBuffer = data->m_workBuffer1; + + int nWGs = NUM_WGS; + ConstData cdata; + { + int nBlocks = (n+ELEMENTS_PER_WORK_ITEM*WG_SIZE-1)/(ELEMENTS_PER_WORK_ITEM*WG_SIZE); + cdata.m_n = n; + cdata.m_nWGs = NUM_WGS; + cdata.m_startBit = 0; + cdata.m_nBlocksPerWG = (nBlocks + cdata.m_nWGs - 1)/cdata.m_nWGs; + if( nBlocks < NUM_WGS ) + { + cdata.m_nBlocksPerWG = 1; + nWGs = nBlocks; + } + } + + if( sortBits == 4 ) dst = &out; + + for(int ib=0; ibm_device, data->m_streamCountKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( NUM_WGS*WG_SIZE, WG_SIZE ); + } + {// prefix scan group histogram + BufferInfo bInfo[] = { BufferInfo( histogramBuffer ) }; + Launcher launcher( data->m_device, data->m_prefixScanKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( 128, 128 ); + } + {// local sort and distribute + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( histogramBuffer, true ), BufferInfo( dst ) }; + Launcher launcher( data->m_device, data->m_sortAndScatterKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( nWGs*WG_SIZE, WG_SIZE ); + } + swap2( src, dst ); + } +} + +template +void RadixSort32::execute(Data* data, Buffer& keysIn, Buffer& keysOut, Buffer& valuesIn, Buffer& valuesOut, int n, int sortBits /* = 32 */) +{ + ADLASSERT( n%DATA_ALIGNMENT == 0 ); + ADLASSERT( n <= data->m_maxSize ); +// ADLASSERT( ELEMENTS_PER_WORK_ITEM == 4 ); + ADLASSERT( BITS_PER_PASS == 4 ); + ADLASSERT( WG_SIZE == 64 ); + ADLASSERT( (sortBits&0x3) == 0 ); + + Buffer* src = &keysIn; + Buffer* srcVal = &valuesIn; + Buffer* dst = data->m_workBuffer0; + Buffer* dstVal = data->m_workBuffer2; + Buffer* histogramBuffer = data->m_workBuffer1; + + int nWGs = NUM_WGS; + ConstData cdata; + { + int nBlocks = (n+ELEMENTS_PER_WORK_ITEM*WG_SIZE-1)/(ELEMENTS_PER_WORK_ITEM*WG_SIZE); + cdata.m_n = n; + cdata.m_nWGs = NUM_WGS; + cdata.m_startBit = 0; + cdata.m_nBlocksPerWG = (nBlocks + cdata.m_nWGs - 1)/cdata.m_nWGs; + if( nBlocks < NUM_WGS ) + { + cdata.m_nBlocksPerWG = 1; + nWGs = nBlocks; + } + } + + if( sortBits == 4 ) + { + dst = &keysOut; + dstVal = &valuesOut; + } + + for(int ib=0; ibm_device, data->m_streamCountKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( NUM_WGS*WG_SIZE, WG_SIZE ); + } + {// prefix scan group histogram + BufferInfo bInfo[] = { BufferInfo( histogramBuffer ) }; + Launcher launcher( data->m_device, data->m_prefixScanKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( 128, 128 ); + } + {// local sort and distribute + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( srcVal, true ), BufferInfo( histogramBuffer, true ), BufferInfo( dst ), BufferInfo( dstVal ) }; + Launcher launcher( data->m_device, data->m_sortAndScatterKeyValueKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( nWGs*WG_SIZE, WG_SIZE ); + } + swap2( src, dst ); + swap2( srcVal, dstVal ); + } +} + +template +void RadixSort32::execute(Data* data, Buffer& keyValuesInOut, int n, int sortBits /* = 32 */) +{ + ADLASSERT( n%DATA_ALIGNMENT == 0 ); + ADLASSERT( n <= data->m_maxSize ); +// ADLASSERT( ELEMENTS_PER_WORK_ITEM == 4 ); + ADLASSERT( BITS_PER_PASS == 4 ); + ADLASSERT( WG_SIZE == 64 ); + ADLASSERT( (sortBits&0x3) == 0 ); + + Buffer* src = &keyValuesInOut; + Buffer* dst = data->m_workBuffer3; + + Buffer* histogramBuffer = data->m_workBuffer1; + + int nWGs = NUM_WGS; + ConstData cdata; + { + int nBlocks = (n+ELEMENTS_PER_WORK_ITEM*WG_SIZE-1)/(ELEMENTS_PER_WORK_ITEM*WG_SIZE); + cdata.m_n = n; + cdata.m_nWGs = NUM_WGS; + cdata.m_startBit = 0; + cdata.m_nBlocksPerWG = (nBlocks + cdata.m_nWGs - 1)/cdata.m_nWGs; + if( nBlocks < NUM_WGS ) + { + cdata.m_nBlocksPerWG = 1; + nWGs = nBlocks; + } + } + + int count=0; + for(int ib=0; ibm_device, data->m_streamCountSortDataKernel); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( NUM_WGS*WG_SIZE, WG_SIZE ); + } + {// prefix scan group histogram + BufferInfo bInfo[] = { BufferInfo( histogramBuffer ) }; + Launcher launcher( data->m_device, data->m_prefixScanKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( 128, 128 ); + } + {// local sort and distribute + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( histogramBuffer, true ), BufferInfo( dst )}; + Launcher launcher( data->m_device, data->m_sortAndScatterSortDataKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[ib/4], cdata ); + launcher.launch1D( nWGs*WG_SIZE, WG_SIZE ); + } + swap2( src, dst ); + count++; + } + + if (count&1) + { + ADLASSERT(0);//need to copy from workbuffer to keyValuesInOut + + } +} +#undef PATH +#undef RADIXSORT32_KERNEL0 +#undef RADIXSORT32_KERNEL1 +#undef RADIXSORT32_KERNEL2 +#undef RADIXSORT32_KERNEL3 + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Host.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Host.inl new file mode 100644 index 000000000..7e174f48d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Host.inl @@ -0,0 +1,163 @@ +/* + 2011 Takahiro Harada +*/ + +template<> +class RadixSort32 : public RadixSort32Base +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + enum + { + BITS_PER_PASS = 8, + NUM_TABLES = (1<* m_workBuffer; + }; + + static + Data* allocate(const Device* device, int maxSize) + { + ADLASSERT( device->m_type == TYPE_HOST ); + + Data* data = new Data; + data->m_workBuffer = new HostBuffer( device, maxSize ); + return data; + } + + static + void deallocate(Data* data) + { + delete data->m_workBuffer; + delete data; + } + + static + void execute(Data* data, Buffer& inout, int n, int sortBits = 32) + { + ADLASSERT( inout.getType() == TYPE_HOST ); + + int tables[NUM_TABLES]; + int counter[NUM_TABLES]; + + u32* src = inout.m_ptr; + u32* dst = data->m_workBuffer->m_ptr; + + for(int startBit=0; startBit> startBit) & (NUM_TABLES-1); + tables[tableIdx]++; + } + + // prefix scan + int sum = 0; + for(int i=0; i> startBit) & (NUM_TABLES-1); + + dst[tables[tableIdx] + counter[tableIdx]] = src[i]; + counter[tableIdx] ++; + } + + swap2( src, dst ); + } + + { + if( src != inout.m_ptr ) + { + memcpy( dst, src, sizeof(u32)*n ); + } + } + + } + + static + void execute(Data* data, Buffer& keyInout, const Buffer& valueInout, int n, int sortBits = 32) + { + ADLASSERT( keyInout.getType() == TYPE_HOST ); + + int tables[NUM_TABLES]; + int counter[NUM_TABLES]; + + u32* src = keyInout.m_ptr; + u32* dst = data->m_workBuffer->m_ptr; + + HostBuffer bufVal(valueInout.m_device, valueInout.m_size); + bufVal.write(valueInout.m_ptr, valueInout.m_size); + + u32* srcVal = valueInout.m_ptr; + u32* dstVal = bufVal.m_ptr; + + for(int startBit=0; startBit> startBit) & (NUM_TABLES-1); + tables[tableIdx]++; + } + + // prefix scan + int sum = 0; + for(int i=0; i> startBit) & (NUM_TABLES-1); + int newIdx = tables[tableIdx] + counter[tableIdx]; + dst[newIdx] = src[i]; + dstVal[newIdx] = srcVal[i]; + counter[tableIdx]++; + } + + swap2( src, dst ); + swap2( srcVal, dstVal ); + } + + { + if( src != keyInout.m_ptr ) + { + memcpy( dst, src, sizeof(u32)*n ); + } + + if( srcVal != valueInout.m_ptr ) + { + memcpy( dstVal, srcVal, sizeof(u32)*n ); + } + } + + } +}; + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Kernels.cl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Kernels.cl new file mode 100644 index 000000000..44dd9a90f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSort32Kernels.cl @@ -0,0 +1,1104 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2011 Advanced Micro Devices, Inc. http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Author Takahiro Harada + + +//#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable + +typedef unsigned int u32; +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) +#define AtomAdd(x, value) atom_add(&(x), value) + +#define SELECT_UINT4( b, a, condition ) select( b,a,condition ) + + +#define make_uint4 (uint4) +#define make_uint2 (uint2) +#define make_int2 (int2) + +#define WG_SIZE 64 +#define ELEMENTS_PER_WORK_ITEM (256/WG_SIZE) +#define BITS_PER_PASS 4 +#define NUM_BUCKET (1< 64 ) + { + sorterSharedMemory[idx] += sorterSharedMemory[idx-64]; + GROUP_MEM_FENCE; + } + + sorterSharedMemory[idx-1] += sorterSharedMemory[idx-2]; + GROUP_MEM_FENCE; + } +#else + if( lIdx < 64 ) + { + sorterSharedMemory[idx] += sorterSharedMemory[idx-1]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-2]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-4]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-8]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-16]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-32]; + GROUP_MEM_FENCE; + if( wgSize > 64 ) + { + sorterSharedMemory[idx] += sorterSharedMemory[idx-64]; + GROUP_MEM_FENCE; + } + + sorterSharedMemory[idx-1] += sorterSharedMemory[idx-2]; + GROUP_MEM_FENCE; + } +#endif + } + + GROUP_LDS_BARRIER; + + *totalSum = sorterSharedMemory[wgSize*2-1]; + u32 addValue = sorterSharedMemory[lIdx+wgSize-1]; + return addValue; +} + +//__attribute__((reqd_work_group_size(128,1,1))) +uint4 localPrefixSum128V( uint4 pData, uint lIdx, uint* totalSum, __local u32 sorterSharedMemory[] ) +{ + u32 s4 = prefixScanVectorEx( &pData ); + u32 rank = localPrefixSum( s4, lIdx, totalSum, sorterSharedMemory, 128 ); + return pData + make_uint4( rank, rank, rank, rank ); +} + + +//__attribute__((reqd_work_group_size(64,1,1))) +uint4 localPrefixSum64V( uint4 pData, uint lIdx, uint* totalSum, __local u32 sorterSharedMemory[] ) +{ + u32 s4 = prefixScanVectorEx( &pData ); + u32 rank = localPrefixSum( s4, lIdx, totalSum, sorterSharedMemory, 64 ); + return pData + make_uint4( rank, rank, rank, rank ); +} + +u32 unpack4Key( u32 key, int keyIdx ){ return (key>>(keyIdx*8)) & 0xff;} + +u32 bit8Scan(u32 v) +{ + return (v<<8) + (v<<16) + (v<<24); +} + +//=== + + + + +#define MY_HISTOGRAM(idx) localHistogramMat[(idx)*WG_SIZE+lIdx] + + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void StreamCountKernel( __global u32* gSrc, __global u32* histogramOut, ConstBuffer cb ) +{ + __local u32 localHistogramMat[NUM_BUCKET*WG_SIZE]; + + u32 gIdx = GET_GLOBAL_IDX; + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + u32 wgSize = GET_GROUP_SIZE; + const int startBit = cb.m_startBit; + const int n = cb.m_n; + const int nWGs = cb.m_nWGs; + const int nBlocksPerWG = cb.m_nBlocksPerWG; + + for(int i=0; i>startBit) & 0xf; +#if defined(NV_GPU) + MY_HISTOGRAM( localKey )++; +#else + AtomInc( MY_HISTOGRAM( localKey ) ); +#endif + } + } + } + + GROUP_LDS_BARRIER; + + if( lIdx < NUM_BUCKET ) + { + u32 sum = 0; + for(int i=0; i>startBit) & 0xf; +#if defined(NV_GPU) + MY_HISTOGRAM( localKey )++; +#else + AtomInc( MY_HISTOGRAM( localKey ) ); +#endif + } + } + } + + GROUP_LDS_BARRIER; + + if( lIdx < NUM_BUCKET ) + { + u32 sum = 0; + for(int i=0; i>startBit) & mask, (sortData[1]>>startBit) & mask, (sortData[2]>>startBit) & mask, (sortData[3]>>startBit) & mask ); + uint4 prefixSum = SELECT_UINT4( make_uint4(1,1,1,1), make_uint4(0,0,0,0), cmpResult != make_uint4(0,0,0,0) ); + u32 total; + prefixSum = localPrefixSum64V( prefixSum, lIdx, &total, ldsSortData ); + { + uint4 localAddr = make_uint4(lIdx*4+0,lIdx*4+1,lIdx*4+2,lIdx*4+3); + uint4 dstAddr = localAddr - prefixSum + make_uint4( total, total, total, total ); + dstAddr = SELECT_UINT4( prefixSum, dstAddr, cmpResult != make_uint4(0, 0, 0, 0) ); + + GROUP_LDS_BARRIER; + + ldsSortData[dstAddr.x] = sortData[0]; + ldsSortData[dstAddr.y] = sortData[1]; + ldsSortData[dstAddr.z] = sortData[2]; + ldsSortData[dstAddr.w] = sortData[3]; + + GROUP_LDS_BARRIER; + + sortData[0] = ldsSortData[localAddr.x]; + sortData[1] = ldsSortData[localAddr.y]; + sortData[2] = ldsSortData[localAddr.z]; + sortData[3] = ldsSortData[localAddr.w]; + + GROUP_LDS_BARRIER; + } + } +} + +// 2 scan, 2 exchange +void sort4Bits1(u32 sortData[4], int startBit, int lIdx, __local u32* ldsSortData) +{ + for(uint ibit=0; ibit>(startBit+ibit)) & 0x3, + (sortData[1]>>(startBit+ibit)) & 0x3, + (sortData[2]>>(startBit+ibit)) & 0x3, + (sortData[3]>>(startBit+ibit)) & 0x3); + + u32 key4; + u32 sKeyPacked[4] = { 0, 0, 0, 0 }; + { + sKeyPacked[0] |= 1<<(8*b.x); + sKeyPacked[1] |= 1<<(8*b.y); + sKeyPacked[2] |= 1<<(8*b.z); + sKeyPacked[3] |= 1<<(8*b.w); + + key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3]; + } + + u32 rankPacked; + u32 sumPacked; + { + rankPacked = localPrefixSum( key4, lIdx, &sumPacked, ldsSortData, WG_SIZE ); + } + + GROUP_LDS_BARRIER; + + u32 newOffset[4] = { 0,0,0,0 }; + { + u32 sumScanned = bit8Scan( sumPacked ); + + u32 scannedKeys[4]; + scannedKeys[0] = 1<<(8*b.x); + scannedKeys[1] = 1<<(8*b.y); + scannedKeys[2] = 1<<(8*b.z); + scannedKeys[3] = 1<<(8*b.w); + { // 4 scans at once + u32 sum4 = 0; + for(int ie=0; ie<4; ie++) + { + u32 tmp = scannedKeys[ie]; + scannedKeys[ie] = sum4; + sum4 += tmp; + } + } + + { + u32 sumPlusRank = sumScanned + rankPacked; + { u32 ie = b.x; + scannedKeys[0] += sumPlusRank; + newOffset[0] = unpack4Key( scannedKeys[0], ie ); + } + { u32 ie = b.y; + scannedKeys[1] += sumPlusRank; + newOffset[1] = unpack4Key( scannedKeys[1], ie ); + } + { u32 ie = b.z; + scannedKeys[2] += sumPlusRank; + newOffset[2] = unpack4Key( scannedKeys[2], ie ); + } + { u32 ie = b.w; + scannedKeys[3] += sumPlusRank; + newOffset[3] = unpack4Key( scannedKeys[3], ie ); + } + } + } + + + GROUP_LDS_BARRIER; + + { + ldsSortData[newOffset[0]] = sortData[0]; + ldsSortData[newOffset[1]] = sortData[1]; + ldsSortData[newOffset[2]] = sortData[2]; + ldsSortData[newOffset[3]] = sortData[3]; + + GROUP_LDS_BARRIER; + + u32 dstAddr = 4*lIdx; + sortData[0] = ldsSortData[dstAddr+0]; + sortData[1] = ldsSortData[dstAddr+1]; + sortData[2] = ldsSortData[dstAddr+2]; + sortData[3] = ldsSortData[dstAddr+3]; + + GROUP_LDS_BARRIER; + } + } +} + +#define SET_HISTOGRAM(setIdx, key) ldsSortData[(setIdx)*NUM_BUCKET+key] + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void SortAndScatterKernel( __global const u32* restrict gSrc, __global const u32* rHistogram, __global u32* restrict gDst, ConstBuffer cb ) +{ + __local u32 ldsSortData[WG_SIZE*ELEMENTS_PER_WORK_ITEM+16]; + __local u32 localHistogramToCarry[NUM_BUCKET]; + __local u32 localHistogram[NUM_BUCKET*2]; + + u32 gIdx = GET_GLOBAL_IDX; + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + u32 wgSize = GET_GROUP_SIZE; + + const int n = cb.m_n; + const int nWGs = cb.m_nWGs; + const int startBit = cb.m_startBit; + const int nBlocksPerWG = cb.m_nBlocksPerWG; + + if( lIdx < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx] = rHistogram[lIdx*nWGs + wgIdx]; + } + + GROUP_LDS_BARRIER; + + const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE; + + int nBlocks = n/blockSize - nBlocksPerWG*wgIdx; + + int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx; + + for(int iblock=0; iblock>startBit) & 0xf; + + { // create histogram + u32 setIdx = lIdx/16; + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + } + ldsSortData[lIdx] = 0; + GROUP_LDS_BARRIER; + + for(int i=0; i>(startBit+ibit)) & 0x3, + (sortData[1]>>(startBit+ibit)) & 0x3, + (sortData[2]>>(startBit+ibit)) & 0x3, + (sortData[3]>>(startBit+ibit)) & 0x3); + + u32 key4; + u32 sKeyPacked[4] = { 0, 0, 0, 0 }; + { + sKeyPacked[0] |= 1<<(8*b.x); + sKeyPacked[1] |= 1<<(8*b.y); + sKeyPacked[2] |= 1<<(8*b.z); + sKeyPacked[3] |= 1<<(8*b.w); + + key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3]; + } + + u32 rankPacked; + u32 sumPacked; + { + rankPacked = localPrefixSum( key4, lIdx, &sumPacked, ldsSortData, WG_SIZE ); + } + + GROUP_LDS_BARRIER; + + u32 newOffset[4] = { 0,0,0,0 }; + { + u32 sumScanned = bit8Scan( sumPacked ); + + u32 scannedKeys[4]; + scannedKeys[0] = 1<<(8*b.x); + scannedKeys[1] = 1<<(8*b.y); + scannedKeys[2] = 1<<(8*b.z); + scannedKeys[3] = 1<<(8*b.w); + { // 4 scans at once + u32 sum4 = 0; + for(int ie=0; ie<4; ie++) + { + u32 tmp = scannedKeys[ie]; + scannedKeys[ie] = sum4; + sum4 += tmp; + } + } + + { + u32 sumPlusRank = sumScanned + rankPacked; + { u32 ie = b.x; + scannedKeys[0] += sumPlusRank; + newOffset[0] = unpack4Key( scannedKeys[0], ie ); + } + { u32 ie = b.y; + scannedKeys[1] += sumPlusRank; + newOffset[1] = unpack4Key( scannedKeys[1], ie ); + } + { u32 ie = b.z; + scannedKeys[2] += sumPlusRank; + newOffset[2] = unpack4Key( scannedKeys[2], ie ); + } + { u32 ie = b.w; + scannedKeys[3] += sumPlusRank; + newOffset[3] = unpack4Key( scannedKeys[3], ie ); + } + } + } + + + GROUP_LDS_BARRIER; + + { + ldsSortData[newOffset[0]] = sortData[0]; + ldsSortData[newOffset[1]] = sortData[1]; + ldsSortData[newOffset[2]] = sortData[2]; + ldsSortData[newOffset[3]] = sortData[3]; + + ldsSortVal[newOffset[0]] = sortVal[0]; + ldsSortVal[newOffset[1]] = sortVal[1]; + ldsSortVal[newOffset[2]] = sortVal[2]; + ldsSortVal[newOffset[3]] = sortVal[3]; + + GROUP_LDS_BARRIER; + + u32 dstAddr = 4*lIdx; + sortData[0] = ldsSortData[dstAddr+0]; + sortData[1] = ldsSortData[dstAddr+1]; + sortData[2] = ldsSortData[dstAddr+2]; + sortData[3] = ldsSortData[dstAddr+3]; + + sortVal[0] = ldsSortVal[dstAddr+0]; + sortVal[1] = ldsSortVal[dstAddr+1]; + sortVal[2] = ldsSortVal[dstAddr+2]; + sortVal[3] = ldsSortVal[dstAddr+3]; + + GROUP_LDS_BARRIER; + } + } +} + + + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void SortAndScatterKeyValueKernel( __global const u32* restrict gSrc, __global const int* restrict gSrcVal, __global const u32* rHistogram, __global u32* restrict gDst, __global int* restrict gDstVal, ConstBuffer cb) +{ + __local u32 ldsSortData[WG_SIZE*ELEMENTS_PER_WORK_ITEM+16]; + __local int ldsSortVal[WG_SIZE*ELEMENTS_PER_WORK_ITEM+16]; + __local u32 localHistogramToCarry[NUM_BUCKET]; + __local u32 localHistogram[NUM_BUCKET*2]; + + u32 gIdx = GET_GLOBAL_IDX; + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + u32 wgSize = GET_GROUP_SIZE; + + const int n = cb.m_n; + const int nWGs = cb.m_nWGs; + const int startBit = cb.m_startBit; + const int nBlocksPerWG = cb.m_nBlocksPerWG; + + if( lIdx < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx] = rHistogram[lIdx*nWGs + wgIdx]; + } + + GROUP_LDS_BARRIER; + + const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE; + + int nBlocks = n/blockSize - nBlocksPerWG*wgIdx; + + int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx; + + for(int iblock=0; iblock>startBit) & 0xf; + + { // create histogram + u32 setIdx = lIdx/16; + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + } + ldsSortData[lIdx] = 0; + GROUP_LDS_BARRIER; + + for(int i=0; i>startBit) & 0xf; + + { // create histogram + u32 setIdx = lIdx/16; + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + } + ldsSortData[lIdx] = 0; + GROUP_LDS_BARRIER; + + for(int i=0; i gSrc : register( t0 ); +StructuredBuffer gSrcVal : register( t1 ); +StructuredBuffer rHistogram : register( t1 ); +StructuredBuffer rHistogram2 : register( t2 ); +RWStructuredBuffer histogramOut : register( u0 ); +RWStructuredBuffer wHistogram1 : register( u0 ); +RWStructuredBuffer gDst : register( u0 ); +RWStructuredBuffer gDstVal : register( u1 ); + +groupshared u32 localHistogramMat[NUM_BUCKET*WG_SIZE]; +#define MY_HISTOGRAM(idx) localHistogramMat[(idx)*WG_SIZE+lIdx] + + +[numthreads(WG_SIZE, 1, 1)] +void StreamCountKernel( DEFAULT_ARGS ) +{ + u32 gIdx = GET_GLOBAL_IDX; + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + u32 wgSize = GET_GROUP_SIZE; + const int startBit = m_startBit; + + const int n = m_n; + const int nWGs = m_nWGs; + const int nBlocksPerWG = m_nBlocksPerWG; + + for(int i=0; i>startBit) & 0xf; +#if defined(NV_GPU) + MY_HISTOGRAM( localKey )++; +#else + AtomInc( MY_HISTOGRAM( localKey ) ); +#endif + } + } + } + + GROUP_LDS_BARRIER; + + if( lIdx < NUM_BUCKET ) + { + u32 sum = 0; + for(int i=0; i 64 ) + { + ldsSortData[idx] += ldsSortData[idx-64]; + GROUP_MEM_FENCE; + } + + ldsSortData[idx-1] += ldsSortData[idx-2]; + GROUP_MEM_FENCE; + } +#else + if( lIdx < 64 ) + { + ldsSortData[idx] += ldsSortData[idx-1]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-2]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-4]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-8]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-16]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-32]; + GROUP_MEM_FENCE; + if( wgSize > 64 ) + { + ldsSortData[idx] += ldsSortData[idx-64]; + GROUP_MEM_FENCE; + } + + ldsSortData[idx-1] += ldsSortData[idx-2]; + GROUP_MEM_FENCE; + } +#endif + } + + GROUP_LDS_BARRIER; + + totalSum = ldsSortData[wgSize*2-1]; + u32 addValue = ldsSortData[lIdx+wgSize-1]; + return addValue; +} + +//__attribute__((reqd_work_group_size(128,1,1))) +uint4 localPrefixSum128V( uint4 pData, uint lIdx, inout uint totalSum ) +{ + u32 s4 = prefixScanVectorEx( pData ); + u32 rank = localPrefixSum( s4, lIdx, totalSum, 128 ); + return pData + make_uint4( rank, rank, rank, rank ); +} + +//__attribute__((reqd_work_group_size(64,1,1))) +uint4 localPrefixSum64V( uint4 pData, uint lIdx, inout uint totalSum ) +{ + u32 s4 = prefixScanVectorEx( pData ); + u32 rank = localPrefixSum( s4, lIdx, totalSum, 64 ); + return pData + make_uint4( rank, rank, rank, rank ); +} + + + + + +#define nPerLane (nPerWI/4) + +// NUM_BUCKET*nWGs < 128*nPerWI +[numthreads(128, 1, 1)] +void PrefixScanKernel( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + const int nWGs = m_nWGs; + + u32 data[nPerWI]; + for(int i=0; i>(keyIdx*8)) & 0xff;} + +u32 bit8Scan(u32 v) +{ + return (v<<8) + (v<<16) + (v<<24); +} + + + + +void sort4Bits1(inout u32 sortData[4], int startBit, int lIdx) +{ +/* + for(uint ibit=0; ibit>(startBit+ibit)) & 0x3, + (sortData[1]>>(startBit+ibit)) & 0x3, + (sortData[2]>>(startBit+ibit)) & 0x3, + (sortData[3]>>(startBit+ibit)) & 0x3); + + u32 key4; + u32 sKeyPacked[4] = { 0, 0, 0, 0 }; + { + sKeyPacked[0] |= 1<<(8*b.x); + sKeyPacked[1] |= 1<<(8*b.y); + sKeyPacked[2] |= 1<<(8*b.z); + sKeyPacked[3] |= 1<<(8*b.w); + + key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3]; + } + + u32 rankPacked; + u32 sumPacked; + { + rankPacked = localPrefixSum64VSingle( key4, lIdx, sumPacked ); + } + +// GROUP_LDS_BARRIER; + + u32 sum[4] = { unpack4Key( sumPacked,0 ), unpack4Key( sumPacked,1 ), unpack4Key( sumPacked,2 ), unpack4Key( sumPacked,3 ) }; + + { + u32 sum4 = 0; + for(int ie=0; ie<4; ie++) + { + u32 tmp = sum[ie]; + sum[ie] = sum4; + sum4 += tmp; + } + } + + u32 newOffset[4] = { 0,0,0,0 }; + + for(int ie=0; ie<4; ie++) + { + uint4 key = extractKeys( b, ie ); + uint4 scannedKey = key; + prefixScanVectorEx( scannedKey ); + uint offset = sum[ie] + unpack4Key( rankPacked, ie ); + uint4 dstAddress = make_uint4( offset, offset, offset, offset ) + scannedKey; + + newOffset[0] += dstAddress.x*key.x; + newOffset[1] += dstAddress.y*key.y; + newOffset[2] += dstAddress.z*key.z; + newOffset[3] += dstAddress.w*key.w; + } + + + + { + ldsSortData[newOffset[0]] = sortData[0]; + ldsSortData[newOffset[1]] = sortData[1]; + ldsSortData[newOffset[2]] = sortData[2]; + ldsSortData[newOffset[3]] = sortData[3]; + +// GROUP_LDS_BARRIER; + + sortData[0] = ldsSortData[lIdx*4+0]; + sortData[1] = ldsSortData[lIdx*4+1]; + sortData[2] = ldsSortData[lIdx*4+2]; + sortData[3] = ldsSortData[lIdx*4+3]; + +// GROUP_LDS_BARRIER; + } + } +*/ + for(uint ibit=0; ibit>(startBit+ibit)) & 0x3, + (sortData[1]>>(startBit+ibit)) & 0x3, + (sortData[2]>>(startBit+ibit)) & 0x3, + (sortData[3]>>(startBit+ibit)) & 0x3); + + u32 key4; + u32 sKeyPacked[4] = { 0, 0, 0, 0 }; + { + sKeyPacked[0] |= 1<<(8*b.x); + sKeyPacked[1] |= 1<<(8*b.y); + sKeyPacked[2] |= 1<<(8*b.z); + sKeyPacked[3] |= 1<<(8*b.w); + + key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3]; + } + + u32 rankPacked; + u32 sumPacked; + { + rankPacked = localPrefixSum( key4, lIdx, sumPacked, WG_SIZE ); + } + + GROUP_LDS_BARRIER; + + u32 newOffset[4] = { 0,0,0,0 }; + { + u32 sumScanned = bit8Scan( sumPacked ); + + u32 scannedKeys[4]; + scannedKeys[0] = 1<<(8*b.x); + scannedKeys[1] = 1<<(8*b.y); + scannedKeys[2] = 1<<(8*b.z); + scannedKeys[3] = 1<<(8*b.w); + { // 4 scans at once + u32 sum4 = 0; + for(int ie=0; ie<4; ie++) + { + u32 tmp = scannedKeys[ie]; + scannedKeys[ie] = sum4; + sum4 += tmp; + } + } + + { + u32 sumPlusRank = sumScanned + rankPacked; + { u32 ie = b.x; + scannedKeys[0] += sumPlusRank; + newOffset[0] = unpack4Key( scannedKeys[0], ie ); + } + { u32 ie = b.y; + scannedKeys[1] += sumPlusRank; + newOffset[1] = unpack4Key( scannedKeys[1], ie ); + } + { u32 ie = b.z; + scannedKeys[2] += sumPlusRank; + newOffset[2] = unpack4Key( scannedKeys[2], ie ); + } + { u32 ie = b.w; + scannedKeys[3] += sumPlusRank; + newOffset[3] = unpack4Key( scannedKeys[3], ie ); + } + } + } + + + GROUP_LDS_BARRIER; + + { + ldsSortData[newOffset[0]] = sortData[0]; + ldsSortData[newOffset[1]] = sortData[1]; + ldsSortData[newOffset[2]] = sortData[2]; + ldsSortData[newOffset[3]] = sortData[3]; + + GROUP_LDS_BARRIER; + + u32 dstAddr = 4*lIdx; + sortData[0] = ldsSortData[dstAddr+0]; + sortData[1] = ldsSortData[dstAddr+1]; + sortData[2] = ldsSortData[dstAddr+2]; + sortData[3] = ldsSortData[dstAddr+3]; + + GROUP_LDS_BARRIER; + } + } +} + + +groupshared u32 localHistogramToCarry[NUM_BUCKET]; +groupshared u32 localHistogram[NUM_BUCKET*2]; +#define SET_HISTOGRAM(setIdx, key) ldsSortData[(setIdx)*NUM_BUCKET+key] + + +[numthreads(WG_SIZE, 1, 1)] +void SortAndScatterKernel( DEFAULT_ARGS ) +{ + u32 gIdx = GET_GLOBAL_IDX; + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + u32 wgSize = GET_GROUP_SIZE; + + const int n = m_n; + const int nWGs = m_nWGs; + const int startBit = m_startBit; + const int nBlocksPerWG = m_nBlocksPerWG; + + if( lIdx < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx] = rHistogram[lIdx*nWGs + wgIdx]; + } + + GROUP_LDS_BARRIER; + + const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE; + + int nBlocks = n/blockSize - nBlocksPerWG*wgIdx; + + int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx; + + for(int iblock=0; iblock>startBit) & 0xf; + + { // create histogram + u32 setIdx = lIdx/16; + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + } + ldsSortData[lIdx] = 0; + GROUP_LDS_BARRIER; + + for(int i=0; i>startBit) & 0xf; + + { // create histogram + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + localHistogram[NUM_BUCKET+lIdx] = 0; + } +// GROUP_LDS_BARRIER; + + AtomInc( localHistogram[NUM_BUCKET+keys[0]] ); + AtomInc( localHistogram[NUM_BUCKET+keys[1]] ); + AtomInc( localHistogram[NUM_BUCKET+keys[2]] ); + AtomInc( localHistogram[NUM_BUCKET+keys[3]] ); + +// GROUP_LDS_BARRIER; + + uint hIdx = NUM_BUCKET+lIdx; + if( lIdx < NUM_BUCKET ) + { + myHistogram = localHistogram[hIdx]; + } +// GROUP_LDS_BARRIER; + +#if defined(USE_2LEVEL_REDUCE) + if( lIdx < NUM_BUCKET ) + { + localHistogram[hIdx] = localHistogram[hIdx-1]; + GROUP_MEM_FENCE; + + u32 u0, u1, u2; + u0 = localHistogram[hIdx-3]; + u1 = localHistogram[hIdx-2]; + u2 = localHistogram[hIdx-1]; + AtomAdd( localHistogram[hIdx], u0 + u1 + u2 ); + GROUP_MEM_FENCE; + u0 = localHistogram[hIdx-12]; + u1 = localHistogram[hIdx-8]; + u2 = localHistogram[hIdx-4]; + AtomAdd( localHistogram[hIdx], u0 + u1 + u2 ); + GROUP_MEM_FENCE; + } +#else + if( lIdx < NUM_BUCKET ) + { + localHistogram[hIdx] = localHistogram[hIdx-1]; + GROUP_MEM_FENCE; + localHistogram[hIdx] += localHistogram[hIdx-1]; + GROUP_MEM_FENCE; + localHistogram[hIdx] += localHistogram[hIdx-2]; + GROUP_MEM_FENCE; + localHistogram[hIdx] += localHistogram[hIdx-4]; + GROUP_MEM_FENCE; + localHistogram[hIdx] += localHistogram[hIdx-8]; + GROUP_MEM_FENCE; + } +#endif + +// GROUP_LDS_BARRIER; + } + + { + for(int ie=0; ie>(startBit+ibit)) & 0x3, + (sortData[1]>>(startBit+ibit)) & 0x3, + (sortData[2]>>(startBit+ibit)) & 0x3, + (sortData[3]>>(startBit+ibit)) & 0x3); + + u32 key4; + u32 sKeyPacked[4] = { 0, 0, 0, 0 }; + { + sKeyPacked[0] |= 1<<(8*b.x); + sKeyPacked[1] |= 1<<(8*b.y); + sKeyPacked[2] |= 1<<(8*b.z); + sKeyPacked[3] |= 1<<(8*b.w); + + key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3]; + } + + u32 rankPacked; + u32 sumPacked; + { + rankPacked = localPrefixSum( key4, lIdx, sumPacked, WG_SIZE ); + } + + GROUP_LDS_BARRIER; + + u32 newOffset[4] = { 0,0,0,0 }; + { + u32 sumScanned = bit8Scan( sumPacked ); + + u32 scannedKeys[4]; + scannedKeys[0] = 1<<(8*b.x); + scannedKeys[1] = 1<<(8*b.y); + scannedKeys[2] = 1<<(8*b.z); + scannedKeys[3] = 1<<(8*b.w); + { // 4 scans at once + u32 sum4 = 0; + for(int ie=0; ie<4; ie++) + { + u32 tmp = scannedKeys[ie]; + scannedKeys[ie] = sum4; + sum4 += tmp; + } + } + + { + u32 sumPlusRank = sumScanned + rankPacked; + { u32 ie = b.x; + scannedKeys[0] += sumPlusRank; + newOffset[0] = unpack4Key( scannedKeys[0], ie ); + } + { u32 ie = b.y; + scannedKeys[1] += sumPlusRank; + newOffset[1] = unpack4Key( scannedKeys[1], ie ); + } + { u32 ie = b.z; + scannedKeys[2] += sumPlusRank; + newOffset[2] = unpack4Key( scannedKeys[2], ie ); + } + { u32 ie = b.w; + scannedKeys[3] += sumPlusRank; + newOffset[3] = unpack4Key( scannedKeys[3], ie ); + } + } + } + + + GROUP_LDS_BARRIER; + + { + ldsSortData[newOffset[0]] = sortData[0]; + ldsSortData[newOffset[1]] = sortData[1]; + ldsSortData[newOffset[2]] = sortData[2]; + ldsSortData[newOffset[3]] = sortData[3]; + + ldsSortVal[newOffset[0]] = sortVal[0]; + ldsSortVal[newOffset[1]] = sortVal[1]; + ldsSortVal[newOffset[2]] = sortVal[2]; + ldsSortVal[newOffset[3]] = sortVal[3]; + + GROUP_LDS_BARRIER; + + u32 dstAddr = 4*lIdx; + sortData[0] = ldsSortData[dstAddr+0]; + sortData[1] = ldsSortData[dstAddr+1]; + sortData[2] = ldsSortData[dstAddr+2]; + sortData[3] = ldsSortData[dstAddr+3]; + + sortVal[0] = ldsSortVal[dstAddr+0]; + sortVal[1] = ldsSortVal[dstAddr+1]; + sortVal[2] = ldsSortVal[dstAddr+2]; + sortVal[3] = ldsSortVal[dstAddr+3]; + + GROUP_LDS_BARRIER; + } + } +} + + + +[numthreads(WG_SIZE, 1, 1)] +void SortAndScatterKeyValueKernel( DEFAULT_ARGS ) +{ + u32 gIdx = GET_GLOBAL_IDX; + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + u32 wgSize = GET_GROUP_SIZE; + + const int n = m_n; + const int nWGs = m_nWGs; + const int startBit = m_startBit; + const int nBlocksPerWG = m_nBlocksPerWG; + + if( lIdx < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx] = rHistogram2[lIdx*nWGs + wgIdx]; + } + + GROUP_LDS_BARRIER; + + const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE; + + int nBlocks = n/blockSize - nBlocksPerWG*wgIdx; + + int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx; + + for(int iblock=0; iblock>startBit) & 0xf; + + { // create histogram + u32 setIdx = lIdx/16; + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + } + ldsSortData[lIdx] = 0; + GROUP_LDS_BARRIER; + + for(int i=0; i 64 )\n" +" {\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-64];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"\n" +" sorterSharedMemory[idx-1] += sorterSharedMemory[idx-2];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"#else\n" +" if( lIdx < 64 )\n" +" {\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-1];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-2]; \n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-4];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-8];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-16];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-32];\n" +" GROUP_MEM_FENCE;\n" +" if( wgSize > 64 )\n" +" {\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-64];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"\n" +" sorterSharedMemory[idx-1] += sorterSharedMemory[idx-2];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"#endif\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" *totalSum = sorterSharedMemory[wgSize*2-1];\n" +" u32 addValue = sorterSharedMemory[lIdx+wgSize-1];\n" +" return addValue;\n" +"}\n" +"\n" +"//__attribute__((reqd_work_group_size(128,1,1)))\n" +"uint4 localPrefixSum128V( uint4 pData, uint lIdx, uint* totalSum, __local u32 sorterSharedMemory[] )\n" +"{\n" +" u32 s4 = prefixScanVectorEx( &pData );\n" +" u32 rank = localPrefixSum( s4, lIdx, totalSum, sorterSharedMemory, 128 );\n" +" return pData + make_uint4( rank, rank, rank, rank );\n" +"}\n" +"\n" +"\n" +"//__attribute__((reqd_work_group_size(64,1,1)))\n" +"uint4 localPrefixSum64V( uint4 pData, uint lIdx, uint* totalSum, __local u32 sorterSharedMemory[] )\n" +"{\n" +" u32 s4 = prefixScanVectorEx( &pData );\n" +" u32 rank = localPrefixSum( s4, lIdx, totalSum, sorterSharedMemory, 64 );\n" +" return pData + make_uint4( rank, rank, rank, rank );\n" +"}\n" +"\n" +"u32 unpack4Key( u32 key, int keyIdx ){ return (key>>(keyIdx*8)) & 0xff;}\n" +"\n" +"u32 bit8Scan(u32 v)\n" +"{\n" +" return (v<<8) + (v<<16) + (v<<24);\n" +"}\n" +"\n" +"//===\n" +"\n" +"\n" +"\n" +"\n" +"#define MY_HISTOGRAM(idx) localHistogramMat[(idx)*WG_SIZE+lIdx]\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void StreamCountKernel( __global u32* gSrc, __global u32* histogramOut, ConstBuffer cb )\n" +"{\n" +" __local u32 localHistogramMat[NUM_BUCKET*WG_SIZE];\n" +"\n" +" u32 gIdx = GET_GLOBAL_IDX;\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" u32 wgSize = GET_GROUP_SIZE;\n" +" const int startBit = cb.m_startBit;\n" +" const int n = cb.m_n;\n" +" const int nWGs = cb.m_nWGs;\n" +" const int nBlocksPerWG = cb.m_nBlocksPerWG;\n" +"\n" +" for(int i=0; i>startBit) & 0xf;\n" +"#if defined(NV_GPU)\n" +" MY_HISTOGRAM( localKey )++;\n" +"#else\n" +" AtomInc( MY_HISTOGRAM( localKey ) );\n" +"#endif\n" +" }\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" u32 sum = 0;\n" +" for(int i=0; i>startBit) & 0xf;\n" +"#if defined(NV_GPU)\n" +" MY_HISTOGRAM( localKey )++;\n" +"#else\n" +" AtomInc( MY_HISTOGRAM( localKey ) );\n" +"#endif\n" +" }\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" u32 sum = 0;\n" +" for(int i=0; i>startBit) & mask, (sortData[1]>>startBit) & mask, (sortData[2]>>startBit) & mask, (sortData[3]>>startBit) & mask );\n" +" uint4 prefixSum = SELECT_UINT4( make_uint4(1,1,1,1), make_uint4(0,0,0,0), cmpResult != make_uint4(0,0,0,0) );\n" +" u32 total;\n" +" prefixSum = localPrefixSum64V( prefixSum, lIdx, &total, ldsSortData );\n" +" {\n" +" uint4 localAddr = make_uint4(lIdx*4+0,lIdx*4+1,lIdx*4+2,lIdx*4+3);\n" +" uint4 dstAddr = localAddr - prefixSum + make_uint4( total, total, total, total );\n" +" dstAddr = SELECT_UINT4( prefixSum, dstAddr, cmpResult != make_uint4(0, 0, 0, 0) );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" ldsSortData[dstAddr.x] = sortData[0];\n" +" ldsSortData[dstAddr.y] = sortData[1];\n" +" ldsSortData[dstAddr.z] = sortData[2];\n" +" ldsSortData[dstAddr.w] = sortData[3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" sortData[0] = ldsSortData[localAddr.x];\n" +" sortData[1] = ldsSortData[localAddr.y];\n" +" sortData[2] = ldsSortData[localAddr.z];\n" +" sortData[3] = ldsSortData[localAddr.w];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" }\n" +"}\n" +"\n" +"// 2 scan, 2 exchange\n" +"void sort4Bits1(u32 sortData[4], int startBit, int lIdx, __local u32* ldsSortData)\n" +"{\n" +" for(uint ibit=0; ibit>(startBit+ibit)) & 0x3, \n" +" (sortData[1]>>(startBit+ibit)) & 0x3, \n" +" (sortData[2]>>(startBit+ibit)) & 0x3, \n" +" (sortData[3]>>(startBit+ibit)) & 0x3);\n" +"\n" +" u32 key4;\n" +" u32 sKeyPacked[4] = { 0, 0, 0, 0 };\n" +" {\n" +" sKeyPacked[0] |= 1<<(8*b.x);\n" +" sKeyPacked[1] |= 1<<(8*b.y);\n" +" sKeyPacked[2] |= 1<<(8*b.z);\n" +" sKeyPacked[3] |= 1<<(8*b.w);\n" +"\n" +" key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3];\n" +" }\n" +"\n" +" u32 rankPacked;\n" +" u32 sumPacked;\n" +" {\n" +" rankPacked = localPrefixSum( key4, lIdx, &sumPacked, ldsSortData, WG_SIZE );\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 newOffset[4] = { 0,0,0,0 };\n" +" {\n" +" u32 sumScanned = bit8Scan( sumPacked );\n" +"\n" +" u32 scannedKeys[4];\n" +" scannedKeys[0] = 1<<(8*b.x);\n" +" scannedKeys[1] = 1<<(8*b.y);\n" +" scannedKeys[2] = 1<<(8*b.z);\n" +" scannedKeys[3] = 1<<(8*b.w);\n" +" { // 4 scans at once\n" +" u32 sum4 = 0;\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" u32 tmp = scannedKeys[ie];\n" +" scannedKeys[ie] = sum4;\n" +" sum4 += tmp;\n" +" }\n" +" }\n" +"\n" +" {\n" +" u32 sumPlusRank = sumScanned + rankPacked;\n" +" { u32 ie = b.x;\n" +" scannedKeys[0] += sumPlusRank;\n" +" newOffset[0] = unpack4Key( scannedKeys[0], ie );\n" +" }\n" +" { u32 ie = b.y;\n" +" scannedKeys[1] += sumPlusRank;\n" +" newOffset[1] = unpack4Key( scannedKeys[1], ie );\n" +" }\n" +" { u32 ie = b.z;\n" +" scannedKeys[2] += sumPlusRank;\n" +" newOffset[2] = unpack4Key( scannedKeys[2], ie );\n" +" }\n" +" { u32 ie = b.w;\n" +" scannedKeys[3] += sumPlusRank;\n" +" newOffset[3] = unpack4Key( scannedKeys[3], ie );\n" +" }\n" +" }\n" +" }\n" +"\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" {\n" +" ldsSortData[newOffset[0]] = sortData[0];\n" +" ldsSortData[newOffset[1]] = sortData[1];\n" +" ldsSortData[newOffset[2]] = sortData[2];\n" +" ldsSortData[newOffset[3]] = sortData[3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 dstAddr = 4*lIdx;\n" +" sortData[0] = ldsSortData[dstAddr+0];\n" +" sortData[1] = ldsSortData[dstAddr+1];\n" +" sortData[2] = ldsSortData[dstAddr+2];\n" +" sortData[3] = ldsSortData[dstAddr+3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" }\n" +"}\n" +"\n" +"#define SET_HISTOGRAM(setIdx, key) ldsSortData[(setIdx)*NUM_BUCKET+key]\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void SortAndScatterKernel( __global const u32* restrict gSrc, __global const u32* rHistogram, __global u32* restrict gDst, ConstBuffer cb )\n" +"{\n" +" __local u32 ldsSortData[WG_SIZE*ELEMENTS_PER_WORK_ITEM+16];\n" +" __local u32 localHistogramToCarry[NUM_BUCKET];\n" +" __local u32 localHistogram[NUM_BUCKET*2];\n" +"\n" +" u32 gIdx = GET_GLOBAL_IDX;\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" u32 wgSize = GET_GROUP_SIZE;\n" +"\n" +" const int n = cb.m_n;\n" +" const int nWGs = cb.m_nWGs;\n" +" const int startBit = cb.m_startBit;\n" +" const int nBlocksPerWG = cb.m_nBlocksPerWG;\n" +"\n" +" if( lIdx < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx] = rHistogram[lIdx*nWGs + wgIdx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE;\n" +"\n" +" int nBlocks = n/blockSize - nBlocksPerWG*wgIdx;\n" +"\n" +" int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx;\n" +"\n" +" for(int iblock=0; iblock>startBit) & 0xf;\n" +"\n" +" { // create histogram\n" +" u32 setIdx = lIdx/16;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" }\n" +" ldsSortData[lIdx] = 0;\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(int i=0; i>(startBit+ibit)) & 0x3, \n" +" (sortData[1]>>(startBit+ibit)) & 0x3, \n" +" (sortData[2]>>(startBit+ibit)) & 0x3, \n" +" (sortData[3]>>(startBit+ibit)) & 0x3);\n" +"\n" +" u32 key4;\n" +" u32 sKeyPacked[4] = { 0, 0, 0, 0 };\n" +" {\n" +" sKeyPacked[0] |= 1<<(8*b.x);\n" +" sKeyPacked[1] |= 1<<(8*b.y);\n" +" sKeyPacked[2] |= 1<<(8*b.z);\n" +" sKeyPacked[3] |= 1<<(8*b.w);\n" +"\n" +" key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3];\n" +" }\n" +"\n" +" u32 rankPacked;\n" +" u32 sumPacked;\n" +" {\n" +" rankPacked = localPrefixSum( key4, lIdx, &sumPacked, ldsSortData, WG_SIZE );\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 newOffset[4] = { 0,0,0,0 };\n" +" {\n" +" u32 sumScanned = bit8Scan( sumPacked );\n" +"\n" +" u32 scannedKeys[4];\n" +" scannedKeys[0] = 1<<(8*b.x);\n" +" scannedKeys[1] = 1<<(8*b.y);\n" +" scannedKeys[2] = 1<<(8*b.z);\n" +" scannedKeys[3] = 1<<(8*b.w);\n" +" { // 4 scans at once\n" +" u32 sum4 = 0;\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" u32 tmp = scannedKeys[ie];\n" +" scannedKeys[ie] = sum4;\n" +" sum4 += tmp;\n" +" }\n" +" }\n" +"\n" +" {\n" +" u32 sumPlusRank = sumScanned + rankPacked;\n" +" { u32 ie = b.x;\n" +" scannedKeys[0] += sumPlusRank;\n" +" newOffset[0] = unpack4Key( scannedKeys[0], ie );\n" +" }\n" +" { u32 ie = b.y;\n" +" scannedKeys[1] += sumPlusRank;\n" +" newOffset[1] = unpack4Key( scannedKeys[1], ie );\n" +" }\n" +" { u32 ie = b.z;\n" +" scannedKeys[2] += sumPlusRank;\n" +" newOffset[2] = unpack4Key( scannedKeys[2], ie );\n" +" }\n" +" { u32 ie = b.w;\n" +" scannedKeys[3] += sumPlusRank;\n" +" newOffset[3] = unpack4Key( scannedKeys[3], ie );\n" +" }\n" +" }\n" +" }\n" +"\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" {\n" +" ldsSortData[newOffset[0]] = sortData[0];\n" +" ldsSortData[newOffset[1]] = sortData[1];\n" +" ldsSortData[newOffset[2]] = sortData[2];\n" +" ldsSortData[newOffset[3]] = sortData[3];\n" +"\n" +" ldsSortVal[newOffset[0]] = sortVal[0];\n" +" ldsSortVal[newOffset[1]] = sortVal[1];\n" +" ldsSortVal[newOffset[2]] = sortVal[2];\n" +" ldsSortVal[newOffset[3]] = sortVal[3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 dstAddr = 4*lIdx;\n" +" sortData[0] = ldsSortData[dstAddr+0];\n" +" sortData[1] = ldsSortData[dstAddr+1];\n" +" sortData[2] = ldsSortData[dstAddr+2];\n" +" sortData[3] = ldsSortData[dstAddr+3];\n" +"\n" +" sortVal[0] = ldsSortVal[dstAddr+0];\n" +" sortVal[1] = ldsSortVal[dstAddr+1];\n" +" sortVal[2] = ldsSortVal[dstAddr+2];\n" +" sortVal[3] = ldsSortVal[dstAddr+3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" }\n" +"}\n" +"\n" +"\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void SortAndScatterKeyValueKernel( __global const u32* restrict gSrc, __global const int* restrict gSrcVal, __global const u32* rHistogram, __global u32* restrict gDst, __global int* restrict gDstVal, ConstBuffer cb)\n" +"{\n" +" __local u32 ldsSortData[WG_SIZE*ELEMENTS_PER_WORK_ITEM+16];\n" +" __local int ldsSortVal[WG_SIZE*ELEMENTS_PER_WORK_ITEM+16];\n" +" __local u32 localHistogramToCarry[NUM_BUCKET];\n" +" __local u32 localHistogram[NUM_BUCKET*2];\n" +"\n" +" u32 gIdx = GET_GLOBAL_IDX;\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" u32 wgSize = GET_GROUP_SIZE;\n" +"\n" +" const int n = cb.m_n;\n" +" const int nWGs = cb.m_nWGs;\n" +" const int startBit = cb.m_startBit;\n" +" const int nBlocksPerWG = cb.m_nBlocksPerWG;\n" +"\n" +" if( lIdx < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx] = rHistogram[lIdx*nWGs + wgIdx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE;\n" +"\n" +" int nBlocks = n/blockSize - nBlocksPerWG*wgIdx;\n" +"\n" +" int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx;\n" +"\n" +" for(int iblock=0; iblock>startBit) & 0xf;\n" +"\n" +" { // create histogram\n" +" u32 setIdx = lIdx/16;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" }\n" +" ldsSortData[lIdx] = 0;\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(int i=0; i>startBit) & 0xf;\n" +"\n" +" { // create histogram\n" +" u32 setIdx = lIdx/16;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" }\n" +" ldsSortData[lIdx] = 0;\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(int i=0; i gSrc : register( t0 );\n" +"StructuredBuffer gSrcVal : register( t1 );\n" +"StructuredBuffer rHistogram : register( t1 );\n" +"StructuredBuffer rHistogram2 : register( t2 );\n" +"RWStructuredBuffer histogramOut : register( u0 );\n" +"RWStructuredBuffer wHistogram1 : register( u0 );\n" +"RWStructuredBuffer gDst : register( u0 );\n" +"RWStructuredBuffer gDstVal : register( u1 );\n" +"\n" +"groupshared u32 localHistogramMat[NUM_BUCKET*WG_SIZE];\n" +"#define MY_HISTOGRAM(idx) localHistogramMat[(idx)*WG_SIZE+lIdx]\n" +"\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void StreamCountKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 gIdx = GET_GLOBAL_IDX;\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" u32 wgSize = GET_GROUP_SIZE;\n" +" const int startBit = m_startBit;\n" +"\n" +" const int n = m_n;\n" +" const int nWGs = m_nWGs;\n" +" const int nBlocksPerWG = m_nBlocksPerWG;\n" +"\n" +" for(int i=0; i>startBit) & 0xf;\n" +"#if defined(NV_GPU)\n" +" MY_HISTOGRAM( localKey )++;\n" +"#else\n" +" AtomInc( MY_HISTOGRAM( localKey ) );\n" +"#endif\n" +" }\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" u32 sum = 0;\n" +" for(int i=0; i 64 )\n" +" {\n" +" ldsSortData[idx] += ldsSortData[idx-64];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"\n" +" ldsSortData[idx-1] += ldsSortData[idx-2];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"#else\n" +" if( lIdx < 64 )\n" +" {\n" +" ldsSortData[idx] += ldsSortData[idx-1];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-2]; \n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-4];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-8];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-16];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-32];\n" +" GROUP_MEM_FENCE;\n" +" if( wgSize > 64 )\n" +" {\n" +" ldsSortData[idx] += ldsSortData[idx-64];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"\n" +" ldsSortData[idx-1] += ldsSortData[idx-2];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"#endif\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" totalSum = ldsSortData[wgSize*2-1];\n" +" u32 addValue = ldsSortData[lIdx+wgSize-1];\n" +" return addValue;\n" +"}\n" +"\n" +"//__attribute__((reqd_work_group_size(128,1,1)))\n" +"uint4 localPrefixSum128V( uint4 pData, uint lIdx, inout uint totalSum )\n" +"{\n" +" u32 s4 = prefixScanVectorEx( pData );\n" +" u32 rank = localPrefixSum( s4, lIdx, totalSum, 128 );\n" +" return pData + make_uint4( rank, rank, rank, rank );\n" +"}\n" +"\n" +"//__attribute__((reqd_work_group_size(64,1,1)))\n" +"uint4 localPrefixSum64V( uint4 pData, uint lIdx, inout uint totalSum )\n" +"{\n" +" u32 s4 = prefixScanVectorEx( pData );\n" +" u32 rank = localPrefixSum( s4, lIdx, totalSum, 64 );\n" +" return pData + make_uint4( rank, rank, rank, rank );\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"#define nPerLane (nPerWI/4)\n" +"\n" +"// NUM_BUCKET*nWGs < 128*nPerWI\n" +"[numthreads(128, 1, 1)]\n" +"void PrefixScanKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" const int nWGs = m_nWGs;\n" +"\n" +" u32 data[nPerWI];\n" +" for(int i=0; i>(keyIdx*8)) & 0xff;}\n" +"\n" +"u32 bit8Scan(u32 v)\n" +"{\n" +" return (v<<8) + (v<<16) + (v<<24);\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"void sort4Bits1(inout u32 sortData[4], int startBit, int lIdx)\n" +"{\n" +"/*\n" +" for(uint ibit=0; ibit>(startBit+ibit)) & 0x3, \n" +" (sortData[1]>>(startBit+ibit)) & 0x3, \n" +" (sortData[2]>>(startBit+ibit)) & 0x3, \n" +" (sortData[3]>>(startBit+ibit)) & 0x3);\n" +"\n" +" u32 key4;\n" +" u32 sKeyPacked[4] = { 0, 0, 0, 0 };\n" +" {\n" +" sKeyPacked[0] |= 1<<(8*b.x);\n" +" sKeyPacked[1] |= 1<<(8*b.y);\n" +" sKeyPacked[2] |= 1<<(8*b.z);\n" +" sKeyPacked[3] |= 1<<(8*b.w);\n" +"\n" +" key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3];\n" +" }\n" +"\n" +" u32 rankPacked;\n" +" u32 sumPacked;\n" +" {\n" +" rankPacked = localPrefixSum64VSingle( key4, lIdx, sumPacked );\n" +" }\n" +"\n" +"// GROUP_LDS_BARRIER;\n" +"\n" +" u32 sum[4] = { unpack4Key( sumPacked,0 ), unpack4Key( sumPacked,1 ), unpack4Key( sumPacked,2 ), unpack4Key( sumPacked,3 ) };\n" +"\n" +" {\n" +" u32 sum4 = 0;\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" u32 tmp = sum[ie];\n" +" sum[ie] = sum4;\n" +" sum4 += tmp;\n" +" }\n" +" }\n" +"\n" +" u32 newOffset[4] = { 0,0,0,0 };\n" +"\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" uint4 key = extractKeys( b, ie );\n" +" uint4 scannedKey = key;\n" +" prefixScanVectorEx( scannedKey );\n" +" uint offset = sum[ie] + unpack4Key( rankPacked, ie );\n" +" uint4 dstAddress = make_uint4( offset, offset, offset, offset ) + scannedKey;\n" +"\n" +" newOffset[0] += dstAddress.x*key.x;\n" +" newOffset[1] += dstAddress.y*key.y;\n" +" newOffset[2] += dstAddress.z*key.z;\n" +" newOffset[3] += dstAddress.w*key.w;\n" +" }\n" +"\n" +"\n" +"\n" +" {\n" +" ldsSortData[newOffset[0]] = sortData[0];\n" +" ldsSortData[newOffset[1]] = sortData[1];\n" +" ldsSortData[newOffset[2]] = sortData[2];\n" +" ldsSortData[newOffset[3]] = sortData[3];\n" +"\n" +"// GROUP_LDS_BARRIER;\n" +"\n" +" sortData[0] = ldsSortData[lIdx*4+0];\n" +" sortData[1] = ldsSortData[lIdx*4+1];\n" +" sortData[2] = ldsSortData[lIdx*4+2];\n" +" sortData[3] = ldsSortData[lIdx*4+3];\n" +"\n" +"// GROUP_LDS_BARRIER;\n" +" }\n" +" }\n" +"*/\n" +" for(uint ibit=0; ibit>(startBit+ibit)) & 0x3, \n" +" (sortData[1]>>(startBit+ibit)) & 0x3, \n" +" (sortData[2]>>(startBit+ibit)) & 0x3, \n" +" (sortData[3]>>(startBit+ibit)) & 0x3);\n" +"\n" +" u32 key4;\n" +" u32 sKeyPacked[4] = { 0, 0, 0, 0 };\n" +" {\n" +" sKeyPacked[0] |= 1<<(8*b.x);\n" +" sKeyPacked[1] |= 1<<(8*b.y);\n" +" sKeyPacked[2] |= 1<<(8*b.z);\n" +" sKeyPacked[3] |= 1<<(8*b.w);\n" +"\n" +" key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3];\n" +" }\n" +"\n" +" u32 rankPacked;\n" +" u32 sumPacked;\n" +" {\n" +" rankPacked = localPrefixSum( key4, lIdx, sumPacked, WG_SIZE );\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 newOffset[4] = { 0,0,0,0 };\n" +" {\n" +" u32 sumScanned = bit8Scan( sumPacked );\n" +"\n" +" u32 scannedKeys[4];\n" +" scannedKeys[0] = 1<<(8*b.x);\n" +" scannedKeys[1] = 1<<(8*b.y);\n" +" scannedKeys[2] = 1<<(8*b.z);\n" +" scannedKeys[3] = 1<<(8*b.w);\n" +" { // 4 scans at once\n" +" u32 sum4 = 0;\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" u32 tmp = scannedKeys[ie];\n" +" scannedKeys[ie] = sum4;\n" +" sum4 += tmp;\n" +" }\n" +" }\n" +"\n" +" {\n" +" u32 sumPlusRank = sumScanned + rankPacked;\n" +" { u32 ie = b.x;\n" +" scannedKeys[0] += sumPlusRank;\n" +" newOffset[0] = unpack4Key( scannedKeys[0], ie );\n" +" }\n" +" { u32 ie = b.y;\n" +" scannedKeys[1] += sumPlusRank;\n" +" newOffset[1] = unpack4Key( scannedKeys[1], ie );\n" +" }\n" +" { u32 ie = b.z;\n" +" scannedKeys[2] += sumPlusRank;\n" +" newOffset[2] = unpack4Key( scannedKeys[2], ie );\n" +" }\n" +" { u32 ie = b.w;\n" +" scannedKeys[3] += sumPlusRank;\n" +" newOffset[3] = unpack4Key( scannedKeys[3], ie );\n" +" }\n" +" }\n" +" }\n" +"\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" {\n" +" ldsSortData[newOffset[0]] = sortData[0];\n" +" ldsSortData[newOffset[1]] = sortData[1];\n" +" ldsSortData[newOffset[2]] = sortData[2];\n" +" ldsSortData[newOffset[3]] = sortData[3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 dstAddr = 4*lIdx;\n" +" sortData[0] = ldsSortData[dstAddr+0];\n" +" sortData[1] = ldsSortData[dstAddr+1];\n" +" sortData[2] = ldsSortData[dstAddr+2];\n" +" sortData[3] = ldsSortData[dstAddr+3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" }\n" +"}\n" +"\n" +"\n" +"groupshared u32 localHistogramToCarry[NUM_BUCKET];\n" +"groupshared u32 localHistogram[NUM_BUCKET*2];\n" +"#define SET_HISTOGRAM(setIdx, key) ldsSortData[(setIdx)*NUM_BUCKET+key]\n" +"\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void SortAndScatterKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 gIdx = GET_GLOBAL_IDX;\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" u32 wgSize = GET_GROUP_SIZE;\n" +"\n" +" const int n = m_n;\n" +" const int nWGs = m_nWGs;\n" +" const int startBit = m_startBit;\n" +" const int nBlocksPerWG = m_nBlocksPerWG;\n" +"\n" +" if( lIdx < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx] = rHistogram[lIdx*nWGs + wgIdx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE;\n" +"\n" +" int nBlocks = n/blockSize - nBlocksPerWG*wgIdx;\n" +"\n" +" int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx;\n" +"\n" +" for(int iblock=0; iblock>startBit) & 0xf;\n" +"\n" +" { // create histogram\n" +" u32 setIdx = lIdx/16;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" }\n" +" ldsSortData[lIdx] = 0;\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(int i=0; i>startBit) & 0xf;\n" +"\n" +" { // create histogram\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" localHistogram[NUM_BUCKET+lIdx] = 0;\n" +" }\n" +"// GROUP_LDS_BARRIER;\n" +"\n" +" AtomInc( localHistogram[NUM_BUCKET+keys[0]] );\n" +" AtomInc( localHistogram[NUM_BUCKET+keys[1]] );\n" +" AtomInc( localHistogram[NUM_BUCKET+keys[2]] );\n" +" AtomInc( localHistogram[NUM_BUCKET+keys[3]] );\n" +" \n" +"// GROUP_LDS_BARRIER;\n" +" \n" +" uint hIdx = NUM_BUCKET+lIdx;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" myHistogram = localHistogram[hIdx];\n" +" }\n" +"// GROUP_LDS_BARRIER;\n" +"\n" +"#if defined(USE_2LEVEL_REDUCE)\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[hIdx] = localHistogram[hIdx-1];\n" +" GROUP_MEM_FENCE;\n" +"\n" +" u32 u0, u1, u2;\n" +" u0 = localHistogram[hIdx-3];\n" +" u1 = localHistogram[hIdx-2];\n" +" u2 = localHistogram[hIdx-1];\n" +" AtomAdd( localHistogram[hIdx], u0 + u1 + u2 );\n" +" GROUP_MEM_FENCE;\n" +" u0 = localHistogram[hIdx-12];\n" +" u1 = localHistogram[hIdx-8];\n" +" u2 = localHistogram[hIdx-4];\n" +" AtomAdd( localHistogram[hIdx], u0 + u1 + u2 );\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"#else\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[hIdx] = localHistogram[hIdx-1];\n" +" GROUP_MEM_FENCE;\n" +" localHistogram[hIdx] += localHistogram[hIdx-1];\n" +" GROUP_MEM_FENCE;\n" +" localHistogram[hIdx] += localHistogram[hIdx-2];\n" +" GROUP_MEM_FENCE;\n" +" localHistogram[hIdx] += localHistogram[hIdx-4];\n" +" GROUP_MEM_FENCE;\n" +" localHistogram[hIdx] += localHistogram[hIdx-8];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +"#endif\n" +"\n" +"// GROUP_LDS_BARRIER;\n" +" }\n" +"\n" +" {\n" +" for(int ie=0; ie>(startBit+ibit)) & 0x3, \n" +" (sortData[1]>>(startBit+ibit)) & 0x3, \n" +" (sortData[2]>>(startBit+ibit)) & 0x3, \n" +" (sortData[3]>>(startBit+ibit)) & 0x3);\n" +"\n" +" u32 key4;\n" +" u32 sKeyPacked[4] = { 0, 0, 0, 0 };\n" +" {\n" +" sKeyPacked[0] |= 1<<(8*b.x);\n" +" sKeyPacked[1] |= 1<<(8*b.y);\n" +" sKeyPacked[2] |= 1<<(8*b.z);\n" +" sKeyPacked[3] |= 1<<(8*b.w);\n" +"\n" +" key4 = sKeyPacked[0] + sKeyPacked[1] + sKeyPacked[2] + sKeyPacked[3];\n" +" }\n" +"\n" +" u32 rankPacked;\n" +" u32 sumPacked;\n" +" {\n" +" rankPacked = localPrefixSum( key4, lIdx, sumPacked, WG_SIZE );\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 newOffset[4] = { 0,0,0,0 };\n" +" {\n" +" u32 sumScanned = bit8Scan( sumPacked );\n" +"\n" +" u32 scannedKeys[4];\n" +" scannedKeys[0] = 1<<(8*b.x);\n" +" scannedKeys[1] = 1<<(8*b.y);\n" +" scannedKeys[2] = 1<<(8*b.z);\n" +" scannedKeys[3] = 1<<(8*b.w);\n" +" { // 4 scans at once\n" +" u32 sum4 = 0;\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" u32 tmp = scannedKeys[ie];\n" +" scannedKeys[ie] = sum4;\n" +" sum4 += tmp;\n" +" }\n" +" }\n" +"\n" +" {\n" +" u32 sumPlusRank = sumScanned + rankPacked;\n" +" { u32 ie = b.x;\n" +" scannedKeys[0] += sumPlusRank;\n" +" newOffset[0] = unpack4Key( scannedKeys[0], ie );\n" +" }\n" +" { u32 ie = b.y;\n" +" scannedKeys[1] += sumPlusRank;\n" +" newOffset[1] = unpack4Key( scannedKeys[1], ie );\n" +" }\n" +" { u32 ie = b.z;\n" +" scannedKeys[2] += sumPlusRank;\n" +" newOffset[2] = unpack4Key( scannedKeys[2], ie );\n" +" }\n" +" { u32 ie = b.w;\n" +" scannedKeys[3] += sumPlusRank;\n" +" newOffset[3] = unpack4Key( scannedKeys[3], ie );\n" +" }\n" +" }\n" +" }\n" +"\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" {\n" +" ldsSortData[newOffset[0]] = sortData[0];\n" +" ldsSortData[newOffset[1]] = sortData[1];\n" +" ldsSortData[newOffset[2]] = sortData[2];\n" +" ldsSortData[newOffset[3]] = sortData[3];\n" +"\n" +" ldsSortVal[newOffset[0]] = sortVal[0];\n" +" ldsSortVal[newOffset[1]] = sortVal[1];\n" +" ldsSortVal[newOffset[2]] = sortVal[2];\n" +" ldsSortVal[newOffset[3]] = sortVal[3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" u32 dstAddr = 4*lIdx;\n" +" sortData[0] = ldsSortData[dstAddr+0];\n" +" sortData[1] = ldsSortData[dstAddr+1];\n" +" sortData[2] = ldsSortData[dstAddr+2];\n" +" sortData[3] = ldsSortData[dstAddr+3];\n" +"\n" +" sortVal[0] = ldsSortVal[dstAddr+0];\n" +" sortVal[1] = ldsSortVal[dstAddr+1];\n" +" sortVal[2] = ldsSortVal[dstAddr+2];\n" +" sortVal[3] = ldsSortVal[dstAddr+3];\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" }\n" +"}\n" +"\n" +"\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void SortAndScatterKeyValueKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 gIdx = GET_GLOBAL_IDX;\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +" u32 wgSize = GET_GROUP_SIZE;\n" +"\n" +" const int n = m_n;\n" +" const int nWGs = m_nWGs;\n" +" const int startBit = m_startBit;\n" +" const int nBlocksPerWG = m_nBlocksPerWG;\n" +"\n" +" if( lIdx < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx] = rHistogram2[lIdx*nWGs + wgIdx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" const int blockSize = ELEMENTS_PER_WORK_ITEM*WG_SIZE;\n" +"\n" +" int nBlocks = n/blockSize - nBlocksPerWG*wgIdx;\n" +"\n" +" int addr = blockSize*nBlocksPerWG*wgIdx + ELEMENTS_PER_WORK_ITEM*lIdx;\n" +"\n" +" for(int iblock=0; iblock>startBit) & 0xf;\n" +"\n" +" { // create histogram\n" +" u32 setIdx = lIdx/16;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" }\n" +" ldsSortData[lIdx] = 0;\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(int i=0; i rHistogram : register(t0); + +RWStructuredBuffer dataToSort : register( u0 ); +RWStructuredBuffer dataToSortOut : register( u1 ); + + + +#define WG_SIZE 128 +#define ELEMENTS_PER_WORK_ITEM 4 +#define BITS_PER_PASS 4 +#define NUM_BUCKET (1<> targetKey; + key.y = (data.y & mask) >> targetKey; + key.z = (data.z & mask) >> targetKey; + key.w = (data.w & mask) >> targetKey; + return key; +} + +uint packKeys(uint lower, uint upper) +{ + return lower|(upper<<16); +} + +uint4 packKeys(uint4 lower, uint4 upper) +{ + return uint4( lower.x|(upper.x<<16), lower.y|(upper.y<<16), lower.z|(upper.z<<16), lower.w|(upper.w<<16) ); +} + +uint extractLower( uint data ) +{ + return data&0xffff; +} + +uint extractUpper( uint data ) +{ + return (data>>16)&0xffff; +} + +uint4 extractLower( uint4 data ) +{ + return uint4( data.x&0xffff, data.y&0xffff, data.z&0xffff, data.w&0xffff ); +} + +uint4 extractUpper( uint4 data ) +{ + return uint4( (data.x>>16)&0xffff, (data.y>>16)&0xffff, (data.z>>16)&0xffff, (data.w>>16)&0xffff ); +} + +[numthreads(WG_SIZE, 1, 1)] +void SortAndScatterKernel( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + + if( lIdx < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx] = rHistogram[lIdx*m_nWorkGroupsToExecute + wgIdx]; + } + + GROUP_LDS_BARRIER; + + for(uint igroup=wgIdx*m_nBlocksPerGroup; igroup>m_startBit) & 0xf, (myData[1].key>>m_startBit) & 0xf, (myData[2].key>>m_startBit) & 0xf, (myData[3].key>>m_startBit) & 0xf); + for(uint targetKey=0; targetKey<(NUM_BUCKET); targetKey+=4) + { + uint4 key[4]; + uint keySet[2]; + { // pack 4 + uint4 scannedKey[4]; + key[0] = scannedKey[0] = extractKeys( b, targetKey+0 ); + key[1] = scannedKey[1] = extractKeys( b, targetKey+1 ); + key[2] = scannedKey[2] = extractKeys( b, targetKey+2 ); + key[3] = scannedKey[3] = extractKeys( b, targetKey+3 ); + { + uint s[4]; + s[0] = prefixScanVectorEx( scannedKey[0] ); + s[1] = prefixScanVectorEx( scannedKey[1] ); + s[2] = prefixScanVectorEx( scannedKey[2] ); + s[3] = prefixScanVectorEx( scannedKey[3] ); + keySet[0] = packKeys( s[0], s[1] ); + keySet[1] = packKeys( s[2], s[3] ); + } + } + + uint dstAddressBase[4]; + { + + uint totalSumPacked[2]; + uint dstAddressPacked[2]; + + localPrefixScan128Dual( keySet[0], keySet[1], lIdx, dstAddressPacked[0], dstAddressPacked[1], totalSumPacked[0], totalSumPacked[1] ); + + dstAddressBase[0] = extractLower( dstAddressPacked[0] ); + dstAddressBase[1] = extractUpper( dstAddressPacked[0] ); + dstAddressBase[2] = extractLower( dstAddressPacked[1] ); + dstAddressBase[3] = extractUpper( dstAddressPacked[1] ); + + uint4 histogram; + histogram.x = extractLower(totalSumPacked[0]); + histogram.y = extractUpper(totalSumPacked[0]); + histogram.z = extractLower(totalSumPacked[1]); + histogram.w = extractUpper(totalSumPacked[1]); + + if( lIdx == targetKey + 0 ) myHistogram = histogram.x; + else if( lIdx == targetKey + 1 ) myHistogram = histogram.y; + else if( lIdx == targetKey + 2 ) myHistogram = histogram.z; + else if( lIdx == targetKey + 3 ) myHistogram = histogram.w; + + uint histogramSum = prefixScanVectorEx( histogram ); + + if( lIdx == targetKey + 0 ) localPrefixSum[targetKey+0] = localOffset+histogram.x; + else if( lIdx == targetKey + 1 ) localPrefixSum[targetKey+1] = localOffset+histogram.y; + else if( lIdx == targetKey + 2 ) localPrefixSum[targetKey+2] = localOffset+histogram.z; + else if( lIdx == targetKey + 3 ) localPrefixSum[targetKey+3] = localOffset+histogram.w; + + localOffset += histogramSum; + } + + GROUP_LDS_BARRIER; + + + for(int ie=0; ie<4; ie++) + { + uint4 scannedKey = key[ie]; + prefixScanVectorEx( scannedKey ); + + uint offset = localPrefixSum[targetKey + ie] + dstAddressBase[ie]; + uint4 dstAddress = uint4( offset, offset, offset, offset ) + scannedKey; + + newOffset[0] += dstAddress.x*key[ie].x; + newOffset[1] += dstAddress.y*key[ie].y; + newOffset[2] += dstAddress.z*key[ie].z; + newOffset[3] += dstAddress.w*key[ie].w; + } + } + + { // local scatter + SET_LOCAL_SORT_DATA(newOffset[0], myData[0]); + SET_LOCAL_SORT_DATA(newOffset[1], myData[1]); + SET_LOCAL_SORT_DATA(newOffset[2], myData[2]); + SET_LOCAL_SORT_DATA(newOffset[3], myData[3]); + } + + GROUP_LDS_BARRIER; + + { // write data + for(int i=0; i> m_startBit) & 0xf; + int groupOffset = localHistogramToCarry[binIdx]; + int myIdx = dataIdx - localPrefixSum[binIdx]; + + dataToSortOut[ groupOffset + myIdx ] = localData; + } + } + + GROUP_LDS_BARRIER; + if( lIdx < NUM_BUCKET ) + { + localHistogramToCarry[lIdx] += myHistogram; + } + GROUP_LDS_BARRIER; + } +} + + +[numthreads(WG_SIZE, 1, 1)] +void SortAndScatterKernel1( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + + if( lIdx < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx] = rHistogram[lIdx*m_nWorkGroupsToExecute + wgIdx.x]; + } + + GROUP_LDS_BARRIER; + + for(uint igroup=wgIdx.x*m_nBlocksPerGroup; igroup>ib) & 0x1, ~(myData[1].key>>ib) & 0x1, ~(myData[2].key>>ib) & 0x1, ~(myData[3].key>>ib) & 0x1); + uint total; + uint4 rankOfP = localPrefixSum128V( keys, lIdx, total ); + uint4 rankOfN = uint4(startAddrBlock, startAddrBlock+1, startAddrBlock+2, startAddrBlock+3) - rankOfP + uint4( total, total, total, total ); + + uint4 myAddr = (keys==uint4(1,1,1,1))? rankOfP: rankOfN; + + GROUP_LDS_BARRIER; + + SET_LOCAL_SORT_DATA( myAddr.x, myData[0] ); + SET_LOCAL_SORT_DATA( myAddr.y, myData[1] ); + SET_LOCAL_SORT_DATA( myAddr.z, myData[2] ); + SET_LOCAL_SORT_DATA( myAddr.w, myData[3] ); + + GROUP_LDS_BARRIER; + + GET_LOCAL_SORT_DATA( startAddrBlock+0, myData[0] ); + GET_LOCAL_SORT_DATA( startAddrBlock+1, myData[1] ); + GET_LOCAL_SORT_DATA( startAddrBlock+2, myData[2] ); + GET_LOCAL_SORT_DATA( startAddrBlock+3, myData[3] ); + } + + {// create histogram -> prefix sum + if( lIdx < NUM_BUCKET ) + { + localHistogram[lIdx] = 0; + localHistogram[NUM_BUCKET+lIdx] = 0; + } + GROUP_LDS_BARRIER; + uint4 keys = uint4((myData[0].key>>m_startBit) & 0xf, (myData[1].key>>m_startBit) & 0xf, (myData[2].key>>m_startBit) & 0xf, (myData[3].key>>m_startBit) & 0xf); + + InterlockedAdd( localHistogram[NUM_BUCKET+keys.x], 1 ); + InterlockedAdd( localHistogram[NUM_BUCKET+keys.y], 1 ); + InterlockedAdd( localHistogram[NUM_BUCKET+keys.z], 1 ); + InterlockedAdd( localHistogram[NUM_BUCKET+keys.w], 1 ); + + GROUP_LDS_BARRIER; + + uint hIdx = NUM_BUCKET+lIdx; + if( lIdx < NUM_BUCKET ) + { + myHistogram = localHistogram[hIdx]; + } + GROUP_LDS_BARRIER; + + if( lIdx < NUM_BUCKET ) + { + localHistogram[hIdx] = localHistogram[hIdx-1]; + + localHistogram[hIdx] += localHistogram[hIdx-1]; + localHistogram[hIdx] += localHistogram[hIdx-2]; + localHistogram[hIdx] += localHistogram[hIdx-4]; + localHistogram[hIdx] += localHistogram[hIdx-8]; + } + + GROUP_LDS_BARRIER; + } +/* + {// write back + int numLocalElements = WG_SIZE*ELEMENTS_PER_WORK_ITEM; + startAddrBlock = lIdx*4; + uint startAddress = igroup*numLocalElements + startAddrBlock; + + for(int ie=0; ie>m_startBit)&0xf; + int groupOffset = localHistogramToCarry[binIdx]; + int myIdx = dataIdx - localHistogram[NUM_BUCKET+binIdx]; + dataToSortOut[ groupOffset + myIdx ] = myData[ie]; + } + } + + GROUP_LDS_BARRIER; + if( lIdx < NUM_BUCKET ) + { + localHistogramToCarry[lIdx] += myHistogram; + } + GROUP_LDS_BARRIER; + + } +} + +/* +[numthreads(WG_SIZE, 1, 1)] +void SortAndScatterKernel1( uint3 gIdx : SV_GroupID, uint3 lIdx : SV_GroupThreadID ) +{ + if( lIdx.x < (NUM_BUCKET) ) + { + localHistogramToCarry[lIdx.x] = rHistogram[lIdx.x*m_nWorkGroupsToExecute + gIdx.x]; + } + + GROUP_LDS_BARRIER; + + for(uint igroup=gIdx.x*m_nBlocksPerGroup; igroup>ib) & 0x1, ~(myData[1].key>>ib) & 0x1, ~(myData[2].key>>ib) & 0x1, ~(myData[3].key>>ib) & 0x1); + uint total; + uint4 rankOfP = localPrefixSum128V( keys, lIdx.x, total ); + uint4 rankOfN = uint4(startAddrBlock, startAddrBlock+1, startAddrBlock+2, startAddrBlock+3) - rankOfP + uint4( total, total, total, total ); + + uint4 myAddr = (keys==uint4(1,1,1,1))? rankOfP: rankOfN; + + GROUP_LDS_BARRIER; + + SET_LOCAL_SORT_DATA( myAddr.x, myData[0] ); + SET_LOCAL_SORT_DATA( myAddr.y, myData[1] ); + SET_LOCAL_SORT_DATA( myAddr.z, myData[2] ); + SET_LOCAL_SORT_DATA( myAddr.w, myData[3] ); + + GROUP_LDS_BARRIER; + + GET_LOCAL_SORT_DATA( startAddrBlock+0, myData[0] ); + GET_LOCAL_SORT_DATA( startAddrBlock+1, myData[1] ); + GET_LOCAL_SORT_DATA( startAddrBlock+2, myData[2] ); + GET_LOCAL_SORT_DATA( startAddrBlock+3, myData[3] ); + } + + {// create histogram -> prefix sum + if( lIdx.x < NUM_BUCKET ) + { + localHistogram[lIdx.x] = 0; + localHistogram[NUM_BUCKET+lIdx.x] = 0; + } + GROUP_LDS_BARRIER; + uint4 keys = uint4((myData[0].key>>m_startBit) & 0xf, (myData[1].key>>m_startBit) & 0xf, (myData[2].key>>m_startBit) & 0xf, (myData[3].key>>m_startBit) & 0xf); + + InterlockedAdd( localHistogram[NUM_BUCKET+keys.x], 1 ); + InterlockedAdd( localHistogram[NUM_BUCKET+keys.y], 1 ); + InterlockedAdd( localHistogram[NUM_BUCKET+keys.z], 1 ); + InterlockedAdd( localHistogram[NUM_BUCKET+keys.w], 1 ); + + GROUP_LDS_BARRIER; + + uint hIdx = NUM_BUCKET+lIdx.x; + if( lIdx.x < NUM_BUCKET ) + { + myHistogram = localHistogram[hIdx]; + } + GROUP_LDS_BARRIER; + + + if( lIdx.x < NUM_BUCKET ) + { + localHistogram[hIdx] = localHistogram[hIdx-1]; + + localHistogram[hIdx] += localHistogram[hIdx-1]; + localHistogram[hIdx] += localHistogram[hIdx-2]; + localHistogram[hIdx] += localHistogram[hIdx-4]; + localHistogram[hIdx] += localHistogram[hIdx-8]; + } + + GROUP_LDS_BARRIER; + } + {// write back + for(int ie=0; ie>m_startBit)&0xf; + int groupOffset = localHistogramToCarry[binIdx]; + int myIdx = dataIdx - localHistogram[NUM_BUCKET+binIdx]; + + dataToSortOut[ groupOffset + myIdx ] = myData[ie]; + } + } + + GROUP_LDS_BARRIER; + if( lIdx.x < NUM_BUCKET ) + { + localHistogramToCarry[lIdx.x] += myHistogram; + } + GROUP_LDS_BARRIER; + + } +} +*/ + +StructuredBuffer dataToSort1 : register( t0 ); +RWStructuredBuffer wHistogram1 : register(u0); + +#define MY_HISTOGRAM(idx) localHistogramMat[(idx)*WG_SIZE+lIdx.x] + +[numthreads(WG_SIZE, 1, 1)] +void StreamCountKernel( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + + int myHistogram[NUM_BUCKET]; + + for(int i=0; i> m_startBit) & 0xf; + localKeys[1] = (localData1.key >> m_startBit) & 0xf; + localKeys[2] = (localData2.key >> m_startBit) & 0xf; + localKeys[3] = (localData3.key >> m_startBit) & 0xf; + } + + MY_HISTOGRAM( localKeys[0] )++; + MY_HISTOGRAM( localKeys[1] )++; + MY_HISTOGRAM( localKeys[2] )++; + MY_HISTOGRAM( localKeys[3] )++; + } + + GROUP_LDS_BARRIER; + + { // reduce to 1 + if( lIdx < 64 )//WG_SIZE/2 ) + { + for(int i=0; i> m_startBit) & 0xf; + localKeys[1] = (localData1.key >> m_startBit) & 0xf; + localKeys[2] = (localData2.key >> m_startBit) & 0xf; + localKeys[3] = (localData3.key >> m_startBit) & 0xf; + } + + myHistogram[ localKeys[0] ]++; + myHistogram[ localKeys[1] ]++; + myHistogram[ localKeys[2] ]++; + myHistogram[ localKeys[3] ]++; + } + + { // move to shared + for(int i=0; i 80*16 ) +[numthreads(WG_SIZE, 1, 1)] +void PrefixScanKernel( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + + uint data[12] = {0,0,0,0,0,0,0,0,0,0,0,0}; + for(int i=0; i<12; i++) + { + if( int(12*lIdx+i) < NUM_BUCKET*m_nWorkGroupsToExecute ) + data[i] = wHistogram1[12*lIdx+i]; + } + + uint4 myData = uint4(0,0,0,0); + myData.x = data[0] + data[1]; + myData.y = data[2] + data[3]; + myData.z = data[4] + data[5]; + myData.w = data[6] + data[7]; + + + uint totalSum; + uint4 scanned = localPrefixSum128V( myData, lIdx, totalSum ); + + data[11] = scanned.w + data[9] + data[10]; + data[10] = scanned.w + data[9]; + data[9] = scanned.w; + data[8] = scanned.z + data[6] + data[7]; + data[7] = scanned.z + data[6]; + data[6] = scanned.z; + data[5] = scanned.y + data[3] + data[4]; + data[4] = scanned.y + data[3]; + data[3] = scanned.y; + data[2] = scanned.x + data[0] + data[1]; + data[1] = scanned.x + data[0]; + data[0] = scanned.x; + + for(int i=0; i<12; i++) + { + wHistogram1[12*lIdx+i] = data[i]; + } +} +/* +[numthreads(WG_SIZE, 1, 1)] +void PrefixScanKernel( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + + uint data[8] = {0,0,0,0,0,0,0,0}; + for(int i=0; i<8; i++) + { + if( int(8*lIdx+i) < NUM_BUCKET*m_nWorkGroupsToExecute ) + data[i] = wHistogram1[8*lIdx+i]; + } + + uint4 myData = uint4(0,0,0,0); + myData.x = data[0] + data[1]; + myData.y = data[2] + data[3]; + myData.z = data[4] + data[5]; + myData.w = data[6] + data[7]; + + + uint totalSum; + uint4 scanned = localPrefixSum128V( myData, lIdx, totalSum ); + + data[7] = scanned.w + data[6]; + data[6] = scanned.w;// + data[5]; + data[5] = scanned.z + data[4]; + data[4] = scanned.z;// + data[3]; + data[3] = scanned.y + data[2]; + data[2] = scanned.y;// + data[1]; + data[1] = scanned.x + data[0]; + data[0] = scanned.x; + + for(int i=0; i<8; i++) + { + wHistogram1[8*lIdx+i] = data[i]; + } +} +*/ + + +[numthreads(WG_SIZE, 1, 1)] +void CopyKernel( DEFAULT_ARGS ) +{ + u32 lIdx = GET_LOCAL_IDX; + u32 wgIdx = GET_GROUP_IDX; + + for(uint igroup=wgIdx.x*m_nBlocksPerGroup; igroup rHistogram : register(t0);\n" +"\n" +"RWStructuredBuffer dataToSort : register( u0 );\n" +"RWStructuredBuffer dataToSortOut : register( u1 );\n" +"\n" +"\n" +"\n" +"#define WG_SIZE 128\n" +"#define ELEMENTS_PER_WORK_ITEM 4\n" +"#define BITS_PER_PASS 4\n" +"#define NUM_BUCKET (1<> targetKey;\n" +" key.y = (data.y & mask) >> targetKey;\n" +" key.z = (data.z & mask) >> targetKey;\n" +" key.w = (data.w & mask) >> targetKey;\n" +" return key;\n" +"}\n" +"\n" +"uint packKeys(uint lower, uint upper)\n" +"{\n" +" return lower|(upper<<16);\n" +"}\n" +"\n" +"uint4 packKeys(uint4 lower, uint4 upper)\n" +"{\n" +" return uint4( lower.x|(upper.x<<16), lower.y|(upper.y<<16), lower.z|(upper.z<<16), lower.w|(upper.w<<16) );\n" +"}\n" +"\n" +"uint extractLower( uint data )\n" +"{\n" +" return data&0xffff;\n" +"}\n" +"\n" +"uint extractUpper( uint data )\n" +"{\n" +" return (data>>16)&0xffff;\n" +"}\n" +"\n" +"uint4 extractLower( uint4 data )\n" +"{\n" +" return uint4( data.x&0xffff, data.y&0xffff, data.z&0xffff, data.w&0xffff );\n" +"}\n" +"\n" +"uint4 extractUpper( uint4 data )\n" +"{\n" +" return uint4( (data.x>>16)&0xffff, (data.y>>16)&0xffff, (data.z>>16)&0xffff, (data.w>>16)&0xffff );\n" +"}\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void SortAndScatterKernel( DEFAULT_ARGS ) \n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +"\n" +" if( lIdx < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx] = rHistogram[lIdx*m_nWorkGroupsToExecute + wgIdx];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(uint igroup=wgIdx*m_nBlocksPerGroup; igroup>m_startBit) & 0xf, (myData[1].key>>m_startBit) & 0xf, (myData[2].key>>m_startBit) & 0xf, (myData[3].key>>m_startBit) & 0xf);\n" +" for(uint targetKey=0; targetKey<(NUM_BUCKET); targetKey+=4)\n" +" {\n" +" uint4 key[4];\n" +" uint keySet[2];\n" +" { // pack 4\n" +" uint4 scannedKey[4];\n" +" key[0] = scannedKey[0] = extractKeys( b, targetKey+0 );\n" +" key[1] = scannedKey[1] = extractKeys( b, targetKey+1 );\n" +" key[2] = scannedKey[2] = extractKeys( b, targetKey+2 );\n" +" key[3] = scannedKey[3] = extractKeys( b, targetKey+3 );\n" +" {\n" +" uint s[4];\n" +" s[0] = prefixScanVectorEx( scannedKey[0] );\n" +" s[1] = prefixScanVectorEx( scannedKey[1] );\n" +" s[2] = prefixScanVectorEx( scannedKey[2] );\n" +" s[3] = prefixScanVectorEx( scannedKey[3] );\n" +" keySet[0] = packKeys( s[0], s[1] );\n" +" keySet[1] = packKeys( s[2], s[3] );\n" +" }\n" +" }\n" +"\n" +" uint dstAddressBase[4];\n" +" {\n" +"\n" +" uint totalSumPacked[2];\n" +" uint dstAddressPacked[2];\n" +"\n" +" localPrefixScan128Dual( keySet[0], keySet[1], lIdx, dstAddressPacked[0], dstAddressPacked[1], totalSumPacked[0], totalSumPacked[1] );\n" +"\n" +" dstAddressBase[0] = extractLower( dstAddressPacked[0] );\n" +" dstAddressBase[1] = extractUpper( dstAddressPacked[0] );\n" +" dstAddressBase[2] = extractLower( dstAddressPacked[1] );\n" +" dstAddressBase[3] = extractUpper( dstAddressPacked[1] );\n" +"\n" +" uint4 histogram;\n" +" histogram.x = extractLower(totalSumPacked[0]);\n" +" histogram.y = extractUpper(totalSumPacked[0]);\n" +" histogram.z = extractLower(totalSumPacked[1]);\n" +" histogram.w = extractUpper(totalSumPacked[1]);\n" +"\n" +" if( lIdx == targetKey + 0 ) myHistogram = histogram.x;\n" +" else if( lIdx == targetKey + 1 ) myHistogram = histogram.y;\n" +" else if( lIdx == targetKey + 2 ) myHistogram = histogram.z;\n" +" else if( lIdx == targetKey + 3 ) myHistogram = histogram.w;\n" +" \n" +" uint histogramSum = prefixScanVectorEx( histogram );\n" +"\n" +" if( lIdx == targetKey + 0 ) localPrefixSum[targetKey+0] = localOffset+histogram.x;\n" +" else if( lIdx == targetKey + 1 ) localPrefixSum[targetKey+1] = localOffset+histogram.y;\n" +" else if( lIdx == targetKey + 2 ) localPrefixSum[targetKey+2] = localOffset+histogram.z;\n" +" else if( lIdx == targetKey + 3 ) localPrefixSum[targetKey+3] = localOffset+histogram.w;\n" +"\n" +" localOffset += histogramSum;\n" +" }\n" +" \n" +" GROUP_LDS_BARRIER;\n" +"\n" +"\n" +" for(int ie=0; ie<4; ie++)\n" +" {\n" +" uint4 scannedKey = key[ie];\n" +" prefixScanVectorEx( scannedKey );\n" +"\n" +" uint offset = localPrefixSum[targetKey + ie] + dstAddressBase[ie];\n" +" uint4 dstAddress = uint4( offset, offset, offset, offset ) + scannedKey;\n" +"\n" +" newOffset[0] += dstAddress.x*key[ie].x;\n" +" newOffset[1] += dstAddress.y*key[ie].y;\n" +" newOffset[2] += dstAddress.z*key[ie].z;\n" +" newOffset[3] += dstAddress.w*key[ie].w;\n" +" }\n" +" }\n" +"\n" +" { // local scatter\n" +" SET_LOCAL_SORT_DATA(newOffset[0], myData[0]);\n" +" SET_LOCAL_SORT_DATA(newOffset[1], myData[1]);\n" +" SET_LOCAL_SORT_DATA(newOffset[2], myData[2]);\n" +" SET_LOCAL_SORT_DATA(newOffset[3], myData[3]);\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" { // write data\n" +" for(int i=0; i> m_startBit) & 0xf;\n" +" int groupOffset = localHistogramToCarry[binIdx];\n" +" int myIdx = dataIdx - localPrefixSum[binIdx];\n" +"\n" +" dataToSortOut[ groupOffset + myIdx ] = localData;\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogramToCarry[lIdx] += myHistogram;\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +"}\n" +"\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void SortAndScatterKernel1( DEFAULT_ARGS )\n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +"\n" +" if( lIdx < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx] = rHistogram[lIdx*m_nWorkGroupsToExecute + wgIdx.x];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(uint igroup=wgIdx.x*m_nBlocksPerGroup; igroup>ib) & 0x1, ~(myData[1].key>>ib) & 0x1, ~(myData[2].key>>ib) & 0x1, ~(myData[3].key>>ib) & 0x1);\n" +" uint total;\n" +" uint4 rankOfP = localPrefixSum128V( keys, lIdx, total );\n" +" uint4 rankOfN = uint4(startAddrBlock, startAddrBlock+1, startAddrBlock+2, startAddrBlock+3) - rankOfP + uint4( total, total, total, total );\n" +"\n" +" uint4 myAddr = (keys==uint4(1,1,1,1))? rankOfP: rankOfN;\n" +" \n" +" GROUP_LDS_BARRIER;\n" +"\n" +" SET_LOCAL_SORT_DATA( myAddr.x, myData[0] );\n" +" SET_LOCAL_SORT_DATA( myAddr.y, myData[1] );\n" +" SET_LOCAL_SORT_DATA( myAddr.z, myData[2] );\n" +" SET_LOCAL_SORT_DATA( myAddr.w, myData[3] );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" GET_LOCAL_SORT_DATA( startAddrBlock+0, myData[0] );\n" +" GET_LOCAL_SORT_DATA( startAddrBlock+1, myData[1] );\n" +" GET_LOCAL_SORT_DATA( startAddrBlock+2, myData[2] );\n" +" GET_LOCAL_SORT_DATA( startAddrBlock+3, myData[3] );\n" +" }\n" +"\n" +" {// create histogram -> prefix sum\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx] = 0;\n" +" localHistogram[NUM_BUCKET+lIdx] = 0;\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" uint4 keys = uint4((myData[0].key>>m_startBit) & 0xf, (myData[1].key>>m_startBit) & 0xf, (myData[2].key>>m_startBit) & 0xf, (myData[3].key>>m_startBit) & 0xf);\n" +" \n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.x], 1 );\n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.y], 1 );\n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.z], 1 );\n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.w], 1 );\n" +" \n" +" GROUP_LDS_BARRIER;\n" +" \n" +" uint hIdx = NUM_BUCKET+lIdx;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" myHistogram = localHistogram[hIdx];\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogram[hIdx] = localHistogram[hIdx-1];\n" +"\n" +" localHistogram[hIdx] += localHistogram[hIdx-1];\n" +" localHistogram[hIdx] += localHistogram[hIdx-2];\n" +" localHistogram[hIdx] += localHistogram[hIdx-4];\n" +" localHistogram[hIdx] += localHistogram[hIdx-8];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +"/*\n" +" {// write back\n" +" int numLocalElements = WG_SIZE*ELEMENTS_PER_WORK_ITEM;\n" +" startAddrBlock = lIdx*4;\n" +" uint startAddress = igroup*numLocalElements + startAddrBlock;\n" +"\n" +" for(int ie=0; ie>m_startBit)&0xf;\n" +" int groupOffset = localHistogramToCarry[binIdx];\n" +" int myIdx = dataIdx - localHistogram[NUM_BUCKET+binIdx];\n" +" dataToSortOut[ groupOffset + myIdx ] = myData[ie];\n" +" }\n" +" }\n" +" \n" +" GROUP_LDS_BARRIER;\n" +" if( lIdx < NUM_BUCKET )\n" +" {\n" +" localHistogramToCarry[lIdx] += myHistogram;\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" }\n" +"}\n" +"\n" +"/*\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void SortAndScatterKernel1( uint3 gIdx : SV_GroupID, uint3 lIdx : SV_GroupThreadID )\n" +"{\n" +" if( lIdx.x < (NUM_BUCKET) )\n" +" {\n" +" localHistogramToCarry[lIdx.x] = rHistogram[lIdx.x*m_nWorkGroupsToExecute + gIdx.x];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" for(uint igroup=gIdx.x*m_nBlocksPerGroup; igroup>ib) & 0x1, ~(myData[1].key>>ib) & 0x1, ~(myData[2].key>>ib) & 0x1, ~(myData[3].key>>ib) & 0x1);\n" +" uint total;\n" +" uint4 rankOfP = localPrefixSum128V( keys, lIdx.x, total );\n" +" uint4 rankOfN = uint4(startAddrBlock, startAddrBlock+1, startAddrBlock+2, startAddrBlock+3) - rankOfP + uint4( total, total, total, total );\n" +"\n" +" uint4 myAddr = (keys==uint4(1,1,1,1))? rankOfP: rankOfN;\n" +" \n" +" GROUP_LDS_BARRIER;\n" +"\n" +" SET_LOCAL_SORT_DATA( myAddr.x, myData[0] );\n" +" SET_LOCAL_SORT_DATA( myAddr.y, myData[1] );\n" +" SET_LOCAL_SORT_DATA( myAddr.z, myData[2] );\n" +" SET_LOCAL_SORT_DATA( myAddr.w, myData[3] );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" GET_LOCAL_SORT_DATA( startAddrBlock+0, myData[0] );\n" +" GET_LOCAL_SORT_DATA( startAddrBlock+1, myData[1] );\n" +" GET_LOCAL_SORT_DATA( startAddrBlock+2, myData[2] );\n" +" GET_LOCAL_SORT_DATA( startAddrBlock+3, myData[3] );\n" +" }\n" +" \n" +" {// create histogram -> prefix sum\n" +" if( lIdx.x < NUM_BUCKET )\n" +" {\n" +" localHistogram[lIdx.x] = 0;\n" +" localHistogram[NUM_BUCKET+lIdx.x] = 0;\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" uint4 keys = uint4((myData[0].key>>m_startBit) & 0xf, (myData[1].key>>m_startBit) & 0xf, (myData[2].key>>m_startBit) & 0xf, (myData[3].key>>m_startBit) & 0xf);\n" +" \n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.x], 1 );\n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.y], 1 );\n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.z], 1 );\n" +" InterlockedAdd( localHistogram[NUM_BUCKET+keys.w], 1 );\n" +" \n" +" GROUP_LDS_BARRIER;\n" +" \n" +" uint hIdx = NUM_BUCKET+lIdx.x;\n" +" if( lIdx.x < NUM_BUCKET )\n" +" {\n" +" myHistogram = localHistogram[hIdx];\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" \n" +"\n" +" if( lIdx.x < NUM_BUCKET )\n" +" {\n" +" localHistogram[hIdx] = localHistogram[hIdx-1];\n" +"\n" +" localHistogram[hIdx] += localHistogram[hIdx-1];\n" +" localHistogram[hIdx] += localHistogram[hIdx-2];\n" +" localHistogram[hIdx] += localHistogram[hIdx-4];\n" +" localHistogram[hIdx] += localHistogram[hIdx-8];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" }\n" +" {// write back\n" +" for(int ie=0; ie>m_startBit)&0xf;\n" +" int groupOffset = localHistogramToCarry[binIdx];\n" +" int myIdx = dataIdx - localHistogram[NUM_BUCKET+binIdx];\n" +" \n" +" dataToSortOut[ groupOffset + myIdx ] = myData[ie];\n" +" }\n" +" }\n" +" \n" +" GROUP_LDS_BARRIER;\n" +" if( lIdx.x < NUM_BUCKET )\n" +" {\n" +" localHistogramToCarry[lIdx.x] += myHistogram;\n" +" }\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" }\n" +"}\n" +"*/\n" +"\n" +"StructuredBuffer dataToSort1 : register( t0 );\n" +"RWStructuredBuffer wHistogram1 : register(u0);\n" +"\n" +"#define MY_HISTOGRAM(idx) localHistogramMat[(idx)*WG_SIZE+lIdx.x]\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void StreamCountKernel( DEFAULT_ARGS ) \n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +"\n" +" int myHistogram[NUM_BUCKET];\n" +"\n" +" for(int i=0; i> m_startBit) & 0xf;\n" +" localKeys[1] = (localData1.key >> m_startBit) & 0xf;\n" +" localKeys[2] = (localData2.key >> m_startBit) & 0xf;\n" +" localKeys[3] = (localData3.key >> m_startBit) & 0xf;\n" +" }\n" +"\n" +" MY_HISTOGRAM( localKeys[0] )++;\n" +" MY_HISTOGRAM( localKeys[1] )++;\n" +" MY_HISTOGRAM( localKeys[2] )++;\n" +" MY_HISTOGRAM( localKeys[3] )++;\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" { // reduce to 1\n" +" if( lIdx < 64 )//WG_SIZE/2 )\n" +" {\n" +" for(int i=0; i> m_startBit) & 0xf;\n" +" localKeys[1] = (localData1.key >> m_startBit) & 0xf;\n" +" localKeys[2] = (localData2.key >> m_startBit) & 0xf;\n" +" localKeys[3] = (localData3.key >> m_startBit) & 0xf;\n" +" }\n" +"\n" +" myHistogram[ localKeys[0] ]++;\n" +" myHistogram[ localKeys[1] ]++;\n" +" myHistogram[ localKeys[2] ]++;\n" +" myHistogram[ localKeys[3] ]++;\n" +" }\n" +"\n" +" { // move to shared\n" +" for(int i=0; i 80*16 )\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void PrefixScanKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +"\n" +" uint data[12] = {0,0,0,0,0,0,0,0,0,0,0,0};\n" +" for(int i=0; i<12; i++)\n" +" {\n" +" if( int(12*lIdx+i) < NUM_BUCKET*m_nWorkGroupsToExecute )\n" +" data[i] = wHistogram1[12*lIdx+i];\n" +" }\n" +"\n" +" uint4 myData = uint4(0,0,0,0);\n" +" myData.x = data[0] + data[1];\n" +" myData.y = data[2] + data[3];\n" +" myData.z = data[4] + data[5];\n" +" myData.w = data[6] + data[7];\n" +"\n" +"\n" +" uint totalSum;\n" +" uint4 scanned = localPrefixSum128V( myData, lIdx, totalSum );\n" +"\n" +" data[11] = scanned.w + data[9] + data[10];\n" +" data[10] = scanned.w + data[9];\n" +" data[9] = scanned.w;\n" +" data[8] = scanned.z + data[6] + data[7];\n" +" data[7] = scanned.z + data[6];\n" +" data[6] = scanned.z;\n" +" data[5] = scanned.y + data[3] + data[4];\n" +" data[4] = scanned.y + data[3];\n" +" data[3] = scanned.y;\n" +" data[2] = scanned.x + data[0] + data[1];\n" +" data[1] = scanned.x + data[0];\n" +" data[0] = scanned.x;\n" +"\n" +" for(int i=0; i<12; i++)\n" +" {\n" +" wHistogram1[12*lIdx+i] = data[i];\n" +" }\n" +"}\n" +"/*\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void PrefixScanKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +"\n" +" uint data[8] = {0,0,0,0,0,0,0,0};\n" +" for(int i=0; i<8; i++)\n" +" {\n" +" if( int(8*lIdx+i) < NUM_BUCKET*m_nWorkGroupsToExecute )\n" +" data[i] = wHistogram1[8*lIdx+i];\n" +" }\n" +"\n" +" uint4 myData = uint4(0,0,0,0);\n" +" myData.x = data[0] + data[1];\n" +" myData.y = data[2] + data[3];\n" +" myData.z = data[4] + data[5];\n" +" myData.w = data[6] + data[7];\n" +"\n" +"\n" +" uint totalSum;\n" +" uint4 scanned = localPrefixSum128V( myData, lIdx, totalSum );\n" +"\n" +" data[7] = scanned.w + data[6];\n" +" data[6] = scanned.w;// + data[5];\n" +" data[5] = scanned.z + data[4];\n" +" data[4] = scanned.z;// + data[3];\n" +" data[3] = scanned.y + data[2];\n" +" data[2] = scanned.y;// + data[1];\n" +" data[1] = scanned.x + data[0];\n" +" data[0] = scanned.x;\n" +"\n" +" for(int i=0; i<8; i++)\n" +" {\n" +" wHistogram1[8*lIdx+i] = data[i];\n" +" }\n" +"}\n" +"*/\n" +"\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void CopyKernel( DEFAULT_ARGS )\n" +"{\n" +" u32 lIdx = GET_LOCAL_IDX;\n" +" u32 wgIdx = GET_GROUP_IDX;\n" +"\n" +" for(uint igroup=wgIdx.x*m_nBlocksPerGroup; igroup +class RadixSort : public RadixSortBase +{ + public: + struct Data + { + HostBuffer* m_workBuffer; + }; + + enum + { + BITS_PER_PASS = 8, + NUM_TABLES = (1<m_type == TYPE_HOST ); + + Data* data = new Data; + data->m_workBuffer = new HostBuffer( deviceData, maxSize ); + return data; + } + + static + void deallocate(Data* data) + { + delete data->m_workBuffer; + delete data; + } + + static + void execute(Data* data, Buffer& inout, int n, int sortBits = 32) + { + ADLASSERT( inout.getType() == TYPE_HOST ); + + int tables[NUM_TABLES]; + int counter[NUM_TABLES]; + + SortData* src = inout.m_ptr; + SortData* dst = data->m_workBuffer->m_ptr; + + int count=0; + for(int startBit=0; startBit> startBit) & (NUM_TABLES-1); + tables[tableIdx]++; + } + + // prefix scan + int sum = 0; + for(int i=0; i> startBit) & (NUM_TABLES-1); + + dst[tables[tableIdx] + counter[tableIdx]] = src[i]; + counter[tableIdx] ++; + } + + swap2( src, dst ); + count++; + } + + { + if (count&1) + //if( src != inout.m_ptr ) + { + memcpy( dst, src, sizeof(SortData)*n ); + } + } + } +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleCL.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleCL.h new file mode 100644 index 000000000..8325529ff --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortSimpleCL.h @@ -0,0 +1,134 @@ +static const char* radixSortSimpleKernelsCL = \ + "#pragma OPENCL EXTENSION cl_amd_printf : enable\n" + "#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable\n" + "\n" + "typedef unsigned int u32;\n" + "#define GET_GROUP_IDX get_group_id(0)\n" + "#define GET_LOCAL_IDX get_local_id(0)\n" + "#define GET_GLOBAL_IDX get_global_id(0)\n" + "#define GET_GROUP_SIZE get_local_size(0)\n" + "#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE)\n" + "#define AtomInc(x) atom_inc(&(x))\n" + "#define AtomInc1(x, out) out = atom_inc(&(x))\n" + "\n" + "\n" + "#define WG_SIZE 128\n" + "#define NUM_PER_WI 4\n" + "\n" + "\n" + "typedef struct\n" + "{\n" + " u32 m_key;\n" + " u32 m_value;\n" + "}SortData;\n" + "\n" + "\n" + "typedef struct\n" + "{\n" + " u32 m_startBit;\n" + " u32 m_numGroups;\n" + " u32 m_padding[2];\n" + "} ConstBuffer;\n" + "\n" + "\n" + "__kernel\n" + "__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" + "void LocalCountKernel(__global SortData* sortData,\n" + " __global u32* ldsHistogramOut,\n" + " ConstBuffer cb)\n" + "{\n" + " __local u32 ldsHistogram[16][256];\n" + "\n" + " int lIdx = GET_LOCAL_IDX;\n" + " int gIdx = GET_GLOBAL_IDX;\n" + "\n" + " for(int i=0; i<16; i++)\n" + " {\n" + " ldsHistogram[i][lIdx] = 0.f;\n" + " ldsHistogram[i][lIdx+128] = 0.f;\n" + " }\n" + "\n" + " GROUP_LDS_BARRIER;\n" + "\n" + " SortData datas[NUM_PER_WI];\n" + " datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" + " datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" + " datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" + " datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" + "\n" + " datas[0].m_key = (datas[0].m_key >> cb.m_startBit) & 0xff;\n" + " datas[1].m_key = (datas[1].m_key >> cb.m_startBit) & 0xff;\n" + " datas[2].m_key = (datas[2].m_key >> cb.m_startBit) & 0xff;\n" + " datas[3].m_key = (datas[3].m_key >> cb.m_startBit) & 0xff;\n" + "\n" + " int tableIdx = lIdx%16;\n" + "\n" + " AtomInc(ldsHistogram[tableIdx][datas[0].m_key]);\n" + " AtomInc(ldsHistogram[tableIdx][datas[1].m_key]);\n" + " AtomInc(ldsHistogram[tableIdx][datas[2].m_key]);\n" + " AtomInc(ldsHistogram[tableIdx][datas[3].m_key]);\n" + "\n" + " GROUP_LDS_BARRIER;\n" + "\n" + " u32 sum0, sum1;\n" + " sum0 = sum1 = 0;\n" + " for(int i=0; i<16; i++)\n" + " {\n" + " sum0 += ldsHistogram[i][lIdx];\n" + " sum1 += ldsHistogram[i][lIdx+128];\n" + " }\n" + "\n" + " ldsHistogramOut[lIdx*cb.m_numGroups+GET_GROUP_IDX] = sum0;\n" + " ldsHistogramOut[(lIdx+128)*cb.m_numGroups+GET_GROUP_IDX] = sum1;\n" + "}\n" + "\n" + "__kernel\n" + "__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" + "void ScatterKernel(__global SortData* sortData,\n" + " __global SortData* sortDataOut,\n" + " __global u32* scannedHistogram,\n" + " ConstBuffer cb)\n" + "{\n" + " __local u32 ldsCurrentLocation[256];\n" + "\n" + " int lIdx = GET_LOCAL_IDX;\n" + " int gIdx = GET_GLOBAL_IDX;\n" + "\n" + " {\n" + " ldsCurrentLocation[lIdx] = scannedHistogram[lIdx*cb.m_numGroups+GET_GROUP_IDX];\n" + " ldsCurrentLocation[lIdx+128] = scannedHistogram[(lIdx+128)*cb.m_numGroups+GET_GROUP_IDX];\n" + " }\n" + "\n" + " GROUP_LDS_BARRIER;\n" + "\n" + " SortData datas[NUM_PER_WI];\n" + " int keys[NUM_PER_WI];\n" + " datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" + " datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" + " datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" + " datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" + "\n" + " keys[0] = (datas[0].m_key >> cb.m_startBit) & 0xff;\n" + " keys[1] = (datas[1].m_key >> cb.m_startBit) & 0xff;\n" + " keys[2] = (datas[2].m_key >> cb.m_startBit) & 0xff;\n" + " keys[3] = (datas[3].m_key >> cb.m_startBit) & 0xff;\n" + "\n" + " int dst[NUM_PER_WI];\n" + " for(int i=0; i sortData : register( t0 );\n" + "RWStructuredBuffer ldsHistogramOut : register( u0 );\n" + "\n" + "groupshared u32 ldsHistogram[16][256];\n" + "\n" + "[numthreads(WG_SIZE, 1, 1)]\n" + "void LocalCountKernel( DEFAULT_ARGS )\n" + "{\n" + " int lIdx = GET_LOCAL_IDX;\n" + " int gIdx = GET_GLOBAL_IDX;\n" + "\n" + " for(int i=0; i<16; i++)\n" + " {\n" + " ldsHistogram[i][lIdx] = 0.f;\n" + " ldsHistogram[i][lIdx+128] = 0.f;\n" + " }\n" + "\n" + " GROUP_LDS_BARRIER;\n" + "\n" + " SortData datas[NUM_PER_WI];\n" + " datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" + " datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" + " datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" + " datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" + "\n" + " datas[0].m_key = (datas[0].m_key >> m_startBit) & 0xff;\n" + " datas[1].m_key = (datas[1].m_key >> m_startBit) & 0xff;\n" + " datas[2].m_key = (datas[2].m_key >> m_startBit) & 0xff;\n" + " datas[3].m_key = (datas[3].m_key >> m_startBit) & 0xff;\n" + "\n" + " int tableIdx = lIdx%16;\n" + "\n" + " AtomInc(ldsHistogram[tableIdx][datas[0].m_key]);\n" + " AtomInc(ldsHistogram[tableIdx][datas[1].m_key]);\n" + " AtomInc(ldsHistogram[tableIdx][datas[2].m_key]);\n" + " AtomInc(ldsHistogram[tableIdx][datas[3].m_key]);\n" + "\n" + " GROUP_LDS_BARRIER;\n" + "\n" + " u32 sum0, sum1;\n" + " sum0 = sum1 = 0;\n" + " for(int i=0; i<16; i++)\n" + " {\n" + " sum0 += ldsHistogram[i][lIdx];\n" + " sum1 += ldsHistogram[i][lIdx+128];\n" + " }\n" + "\n" + " ldsHistogramOut[lIdx*m_numGroups+GET_GROUP_IDX] = sum0;\n" + " ldsHistogramOut[(lIdx+128)*m_numGroups+GET_GROUP_IDX] = sum1;\n" + "}\n" + "\n" + "\n" + "RWStructuredBuffer sortDataOut : register( u0 );\n" + "RWStructuredBuffer scannedHistogram : register( u1 );\n" + "\n" + "groupshared u32 ldsCurrentLocation[256];\n" + "\n" + "[numthreads(WG_SIZE, 1, 1)]\n" + "void ScatterKernel( DEFAULT_ARGS )\n" + "{\n" + " int lIdx = GET_LOCAL_IDX;\n" + " int gIdx = GET_GLOBAL_IDX;\n" + "\n" + " {\n" + " ldsCurrentLocation[lIdx] = scannedHistogram[lIdx*m_numGroups+GET_GROUP_IDX];\n" + " ldsCurrentLocation[lIdx+128] = scannedHistogram[(lIdx+128)*m_numGroups+GET_GROUP_IDX];\n" + " }\n" + "\n" + " GROUP_LDS_BARRIER;\n" + "\n" + " SortData datas[NUM_PER_WI];\n" + " int keys[NUM_PER_WI];\n" + " datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" + " datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" + " datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" + " datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" + "\n" + " keys[0] = (datas[0].m_key >> m_startBit) & 0xff;\n" + " keys[1] = (datas[1].m_key >> m_startBit) & 0xff;\n" + " keys[2] = (datas[2].m_key >> m_startBit) & 0xff;\n" + " keys[3] = (datas[3].m_key >> m_startBit) & 0xff;\n" + "\n" + " int dst[NUM_PER_WI];\n" + " for(int i=0; i> cb.m_startBit) & 0xff; + datas[1].m_key = (datas[1].m_key >> cb.m_startBit) & 0xff; + datas[2].m_key = (datas[2].m_key >> cb.m_startBit) & 0xff; + datas[3].m_key = (datas[3].m_key >> cb.m_startBit) & 0xff; + + int tableIdx = lIdx%16; + + AtomInc(ldsHistogram[tableIdx][datas[0].m_key]); + AtomInc(ldsHistogram[tableIdx][datas[1].m_key]); + AtomInc(ldsHistogram[tableIdx][datas[2].m_key]); + AtomInc(ldsHistogram[tableIdx][datas[3].m_key]); + + GROUP_LDS_BARRIER; + + u32 sum0, sum1; + sum0 = sum1 = 0; + for(int i=0; i<16; i++) + { + sum0 += ldsHistogram[i][lIdx]; + sum1 += ldsHistogram[i][lIdx+128]; + } + + ldsHistogramOut[lIdx*cb.m_numGroups+GET_GROUP_IDX] = sum0; + ldsHistogramOut[(lIdx+128)*cb.m_numGroups+GET_GROUP_IDX] = sum1; +} + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void ScatterKernel(__global SortData* sortData, + __global SortData* sortDataOut, + __global u32* scannedHistogram, + ConstBuffer cb) +{ + __local u32 ldsCurrentLocation[256]; + + int lIdx = GET_LOCAL_IDX; + int gIdx = GET_GLOBAL_IDX; + + { + ldsCurrentLocation[lIdx] = scannedHistogram[lIdx*cb.m_numGroups+GET_GROUP_IDX]; + ldsCurrentLocation[lIdx+128] = scannedHistogram[(lIdx+128)*cb.m_numGroups+GET_GROUP_IDX]; + } + + GROUP_LDS_BARRIER; + + SortData datas[NUM_PER_WI]; + int keys[NUM_PER_WI]; + datas[0] = sortData[gIdx*NUM_PER_WI+0]; + datas[1] = sortData[gIdx*NUM_PER_WI+1]; + datas[2] = sortData[gIdx*NUM_PER_WI+2]; + datas[3] = sortData[gIdx*NUM_PER_WI+3]; + + keys[0] = (datas[0].m_key >> cb.m_startBit) & 0xff; + keys[1] = (datas[1].m_key >> cb.m_startBit) & 0xff; + keys[2] = (datas[2].m_key >> cb.m_startBit) & 0xff; + keys[3] = (datas[3].m_key >> cb.m_startBit) & 0xff; + + int dst[NUM_PER_WI]; + for(int i=0; i sortData : register( t0 ); +RWStructuredBuffer ldsHistogramOut : register( u0 ); + +groupshared u32 ldsHistogram[16][256]; + +[numthreads(WG_SIZE, 1, 1)] +void LocalCountKernel( DEFAULT_ARGS ) +{ + int lIdx = GET_LOCAL_IDX; + int gIdx = GET_GLOBAL_IDX; + + for(int i=0; i<16; i++) + { + ldsHistogram[i][lIdx] = 0.f; + ldsHistogram[i][lIdx+128] = 0.f; + } + + GROUP_LDS_BARRIER; + + SortData datas[NUM_PER_WI]; + datas[0] = sortData[gIdx*NUM_PER_WI+0]; + datas[1] = sortData[gIdx*NUM_PER_WI+1]; + datas[2] = sortData[gIdx*NUM_PER_WI+2]; + datas[3] = sortData[gIdx*NUM_PER_WI+3]; + + datas[0].m_key = (datas[0].m_key >> m_startBit) & 0xff; + datas[1].m_key = (datas[1].m_key >> m_startBit) & 0xff; + datas[2].m_key = (datas[2].m_key >> m_startBit) & 0xff; + datas[3].m_key = (datas[3].m_key >> m_startBit) & 0xff; + + int tableIdx = lIdx%16; + + AtomInc(ldsHistogram[tableIdx][datas[0].m_key]); + AtomInc(ldsHistogram[tableIdx][datas[1].m_key]); + AtomInc(ldsHistogram[tableIdx][datas[2].m_key]); + AtomInc(ldsHistogram[tableIdx][datas[3].m_key]); + + GROUP_LDS_BARRIER; + + u32 sum0, sum1; + sum0 = sum1 = 0; + for(int i=0; i<16; i++) + { + sum0 += ldsHistogram[i][lIdx]; + sum1 += ldsHistogram[i][lIdx+128]; + } + + ldsHistogramOut[lIdx*m_numGroups+GET_GROUP_IDX] = sum0; + ldsHistogramOut[(lIdx+128)*m_numGroups+GET_GROUP_IDX] = sum1; +} + + +RWStructuredBuffer sortDataOut : register( u0 ); +RWStructuredBuffer scannedHistogram : register( u1 ); + +groupshared u32 ldsCurrentLocation[256]; + +[numthreads(WG_SIZE, 1, 1)] +void ScatterKernel( DEFAULT_ARGS ) +{ + int lIdx = GET_LOCAL_IDX; + int gIdx = GET_GLOBAL_IDX; + + { + ldsCurrentLocation[lIdx] = scannedHistogram[lIdx*m_numGroups+GET_GROUP_IDX]; + ldsCurrentLocation[lIdx+128] = scannedHistogram[(lIdx+128)*m_numGroups+GET_GROUP_IDX]; + } + + GROUP_LDS_BARRIER; + + SortData datas[NUM_PER_WI]; + int keys[NUM_PER_WI]; + datas[0] = sortData[gIdx*NUM_PER_WI+0]; + datas[1] = sortData[gIdx*NUM_PER_WI+1]; + datas[2] = sortData[gIdx*NUM_PER_WI+2]; + datas[3] = sortData[gIdx*NUM_PER_WI+3]; + + keys[0] = (datas[0].m_key >> m_startBit) & 0xff; + keys[1] = (datas[1].m_key >> m_startBit) & 0xff; + keys[2] = (datas[2].m_key >> m_startBit) & 0xff; + keys[3] = (datas[3].m_key >> m_startBit) & 0xff; + + int dst[NUM_PER_WI]; + for(int i=0; i> cb.m_startBit) & 0xff;\n" +" datas[1].m_key = (datas[1].m_key >> cb.m_startBit) & 0xff;\n" +" datas[2].m_key = (datas[2].m_key >> cb.m_startBit) & 0xff;\n" +" datas[3].m_key = (datas[3].m_key >> cb.m_startBit) & 0xff;\n" +"\n" +" int tableIdx = lIdx%16;\n" +" \n" +" AtomInc(ldsHistogram[tableIdx][datas[0].m_key]);\n" +" AtomInc(ldsHistogram[tableIdx][datas[1].m_key]);\n" +" AtomInc(ldsHistogram[tableIdx][datas[2].m_key]);\n" +" AtomInc(ldsHistogram[tableIdx][datas[3].m_key]);\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" u32 sum0, sum1;\n" +" sum0 = sum1 = 0;\n" +" for(int i=0; i<16; i++)\n" +" {\n" +" sum0 += ldsHistogram[i][lIdx];\n" +" sum1 += ldsHistogram[i][lIdx+128];\n" +" }\n" +"\n" +" ldsHistogramOut[lIdx*cb.m_numGroups+GET_GROUP_IDX] = sum0;\n" +" ldsHistogramOut[(lIdx+128)*cb.m_numGroups+GET_GROUP_IDX] = sum1;\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void ScatterKernel(__global SortData* sortData,\n" +" __global SortData* sortDataOut,\n" +" __global u32* scannedHistogram, \n" +" ConstBuffer cb)\n" +"{\n" +" __local u32 ldsCurrentLocation[256];\n" +"\n" +" int lIdx = GET_LOCAL_IDX;\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" \n" +" {\n" +" ldsCurrentLocation[lIdx] = scannedHistogram[lIdx*cb.m_numGroups+GET_GROUP_IDX];\n" +" ldsCurrentLocation[lIdx+128] = scannedHistogram[(lIdx+128)*cb.m_numGroups+GET_GROUP_IDX];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" SortData datas[NUM_PER_WI];\n" +" int keys[NUM_PER_WI];\n" +" datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" +" datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" +" datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" +" datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" +"\n" +" keys[0] = (datas[0].m_key >> cb.m_startBit) & 0xff;\n" +" keys[1] = (datas[1].m_key >> cb.m_startBit) & 0xff;\n" +" keys[2] = (datas[2].m_key >> cb.m_startBit) & 0xff;\n" +" keys[3] = (datas[3].m_key >> cb.m_startBit) & 0xff;\n" +"\n" +" int dst[NUM_PER_WI];\n" +" for(int i=0; i sortData : register( t0 );\n" +"RWStructuredBuffer ldsHistogramOut : register( u0 );\n" +"\n" +"groupshared u32 ldsHistogram[16][256];\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void LocalCountKernel( DEFAULT_ARGS )\n" +"{\n" +" int lIdx = GET_LOCAL_IDX;\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" \n" +" for(int i=0; i<16; i++)\n" +" {\n" +" ldsHistogram[i][lIdx] = 0.f;\n" +" ldsHistogram[i][lIdx+128] = 0.f;\n" +" }\n" +" \n" +" GROUP_LDS_BARRIER;\n" +" \n" +" SortData datas[NUM_PER_WI];\n" +" datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" +" datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" +" datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" +" datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" +"\n" +" datas[0].m_key = (datas[0].m_key >> m_startBit) & 0xff;\n" +" datas[1].m_key = (datas[1].m_key >> m_startBit) & 0xff;\n" +" datas[2].m_key = (datas[2].m_key >> m_startBit) & 0xff;\n" +" datas[3].m_key = (datas[3].m_key >> m_startBit) & 0xff;\n" +"\n" +" int tableIdx = lIdx%16;\n" +" \n" +" AtomInc(ldsHistogram[tableIdx][datas[0].m_key]);\n" +" AtomInc(ldsHistogram[tableIdx][datas[1].m_key]);\n" +" AtomInc(ldsHistogram[tableIdx][datas[2].m_key]);\n" +" AtomInc(ldsHistogram[tableIdx][datas[3].m_key]);\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" u32 sum0, sum1;\n" +" sum0 = sum1 = 0;\n" +" for(int i=0; i<16; i++)\n" +" {\n" +" sum0 += ldsHistogram[i][lIdx];\n" +" sum1 += ldsHistogram[i][lIdx+128];\n" +" }\n" +"\n" +" ldsHistogramOut[lIdx*m_numGroups+GET_GROUP_IDX] = sum0;\n" +" ldsHistogramOut[(lIdx+128)*m_numGroups+GET_GROUP_IDX] = sum1;\n" +"}\n" +"\n" +"\n" +"RWStructuredBuffer sortDataOut : register( u0 );\n" +"RWStructuredBuffer scannedHistogram : register( u1 );\n" +"\n" +"groupshared u32 ldsCurrentLocation[256];\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void ScatterKernel( DEFAULT_ARGS )\n" +"{\n" +" int lIdx = GET_LOCAL_IDX;\n" +" int gIdx = GET_GLOBAL_IDX;\n" +" \n" +" {\n" +" ldsCurrentLocation[lIdx] = scannedHistogram[lIdx*m_numGroups+GET_GROUP_IDX];\n" +" ldsCurrentLocation[lIdx+128] = scannedHistogram[(lIdx+128)*m_numGroups+GET_GROUP_IDX];\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +" \n" +" SortData datas[NUM_PER_WI];\n" +" int keys[NUM_PER_WI];\n" +" datas[0] = sortData[gIdx*NUM_PER_WI+0];\n" +" datas[1] = sortData[gIdx*NUM_PER_WI+1];\n" +" datas[2] = sortData[gIdx*NUM_PER_WI+2];\n" +" datas[3] = sortData[gIdx*NUM_PER_WI+3];\n" +"\n" +" keys[0] = (datas[0].m_key >> m_startBit) & 0xff;\n" +" keys[1] = (datas[1].m_key >> m_startBit) & 0xff;\n" +" keys[2] = (datas[2].m_key >> m_startBit) & 0xff;\n" +" keys[3] = (datas[3].m_key >> m_startBit) & 0xff;\n" +"\n" +" int dst[NUM_PER_WI];\n" +" for(int i=0; i +#include + +template +class RadixSortStandard : public RadixSortBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + enum + { + WG_SIZE = 128, + NUM_PER_WI = 4, + + BITS_PER_PASS = 4, + }; + + struct Data : public RadixSort::Data + { + Kernel* m_localSortKernel; + Kernel* m_scatterKernel; + Kernel* m_copyKernel; + + Buffer* m_workBuffer0; + Buffer* m_workBuffer1; + Buffer* m_workBuffer2; + Buffer* m_workBuffer3; + Buffer* m_constBuffer[32/BITS_PER_PASS]; + }; + + + static + Data* allocate(const Device* deviceData, int maxSize, Option option = SORT_NORMAL); + + static + void deallocate(void* data); + + static + void execute(void* data, Buffer& inout, int n, int sortBits); +}; + +template +typename RadixSortStandard::Data* RadixSortStandard::allocate(const Device* deviceData, int maxSize, Option option) +{ + ADLASSERT( type == deviceData->m_type ); + + u32 maxNumGroups = (maxSize+WG_SIZE*NUM_PER_WI-1)/(WG_SIZE*NUM_PER_WI); + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {radixSortStandardKernelsCL,radixSortStandardKernelsDX11}; +// ADLASSERT(0); +#else + {0,0}; +#endif + + Data* data = new Data; + data->m_option = option; + data->m_deviceData = deviceData; + + data->m_localSortKernel = deviceData->getKernel( PATH, KERNEL0, 0, src[type] ); + data->m_scatterKernel = deviceData->getKernel( PATH, KERNEL1, 0, src[type] ); + data->m_copyKernel = deviceData->getKernel( PATH, KERNEL2, 0, src[type] ); + + // is this correct? + data->m_scanData = PrefixScan::allocate( deviceData, maxNumGroups*(1<m_workBuffer0 = new Buffer( deviceData, maxNumGroups*(1<m_workBuffer1 = new Buffer( deviceData, maxNumGroups*(1<m_workBuffer2 = new Buffer( deviceData, maxNumGroups*(1<m_workBuffer3 = new Buffer( deviceData, maxSize ); + for(int i=0; i<32/BITS_PER_PASS; i++) + data->m_constBuffer[i] = new Buffer( deviceData, 1, BufferBase::BUFFER_CONST ); + data->m_maxSize = maxSize; + + return data; +} + +template +void RadixSortStandard::deallocate(void* rawData) +{ + Data* data = (Data*)rawData; + + delete data->m_workBuffer0; + delete data->m_workBuffer1; + delete data->m_workBuffer2; + delete data->m_workBuffer3; + for(int i=0; i<32/BITS_PER_PASS; i++) + delete data->m_constBuffer[i]; + + PrefixScan::deallocate( data->m_scanData ); + + delete data; +} + +template +void RadixSortStandard::execute(void* rawData, Buffer& inout, int n, int sortBits) +{ + Data* data = (Data*)rawData; + + ADLASSERT( n%512 == 0 ); + ADLASSERT( n <= data->m_maxSize ); + ADLASSERT( NUM_PER_WI == 4 ); + + Buffer* src = BufferUtils::map( data->m_deviceData, &inout ); + Buffer* dst = data->m_workBuffer3; + + const Device* deviceData = data->m_deviceData; + + int numGroups = (n+WG_SIZE*NUM_PER_WI-1)/(WG_SIZE*NUM_PER_WI); + + int4 constBuffer; + + int iPass = 0; + for(int startBit=0; startBitm_workBuffer0 ), BufferInfo( data->m_workBuffer1 ) }; + + Launcher launcher( deviceData, data->m_localSortKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE*numGroups, WG_SIZE ); + } + + PrefixScan::execute( data->m_scanData, *data->m_workBuffer0, *data->m_workBuffer2, numGroups*(1<m_workBuffer2, true ), BufferInfo( data->m_workBuffer1, true ), + BufferInfo( dst ) }; + + Launcher launcher( deviceData, data->m_scatterKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE*numGroups, WG_SIZE ); + } + + if(0) + { + BufferInfo bInfo[] = { BufferInfo( dst, true ), BufferInfo( src ) }; + + Launcher launcher( deviceData, data->m_copyKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.launch1D( n, WG_SIZE ); + } + swap2( src, dst ); + } + + if( src != &inout ) + { + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( dst ) }; + + Launcher launcher( deviceData, data->m_copyKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.launch1D( n, WG_SIZE ); + } + + BufferUtils::unmap( src, &inout ); +} + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 +#undef KERNEL2 diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.cl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.cl new file mode 100644 index 000000000..c79348d60 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.cl @@ -0,0 +1,345 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2011 Advanced Micro Devices, Inc. http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Author Takahiro Harada + + +#pragma OPENCL EXTENSION cl_amd_printf : enable +#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable + +typedef unsigned int u32; +#define GET_GROUP_IDX get_group_id(0) +#define GET_LOCAL_IDX get_local_id(0) +#define GET_GLOBAL_IDX get_global_id(0) +#define GET_GROUP_SIZE get_local_size(0) +#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE) +#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE) +#define AtomInc(x) atom_inc(&(x)) +#define AtomInc1(x, out) out = atom_inc(&(x)) + +#define make_uint4 (uint4) +#define make_uint2 (uint2) + +#define SELECT_UINT4( b, a, condition ) select( b,a,condition ) + +#define WG_SIZE 128 +#define NUM_PER_WI 4 + + +typedef struct +{ + u32 m_key; + u32 m_value; +}SortData; + + +typedef struct +{ + u32 m_startBit; + u32 m_numGroups; + u32 m_padding[2]; +} ConstBuffer; + +#define BITS_PER_PASS 4 + + + +uint4 prefixScanVector( uint4 data ) +{ + data.y += data.x; + data.w += data.z; + data.z += data.y; + data.w += data.y; + return data; +} + +uint prefixScanVectorEx( uint4* data ) +{ + uint4 backup = data[0]; + data[0].y += data[0].x; + data[0].w += data[0].z; + data[0].z += data[0].y; + data[0].w += data[0].y; + uint sum = data[0].w; + *data -= backup; + return sum; +} + +uint4 localPrefixSum128V( uint4 pData, uint lIdx, uint* totalSum, __local u32 sorterSharedMemory[] ) +{ + { // Set data + sorterSharedMemory[lIdx] = 0; + sorterSharedMemory[lIdx+WG_SIZE] = prefixScanVectorEx( &pData ); + } + + GROUP_LDS_BARRIER; + + { // Prefix sum + int idx = 2*lIdx + (WG_SIZE+1); + if( lIdx < 64 ) + { + sorterSharedMemory[idx] += sorterSharedMemory[idx-1]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-2]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-4]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-8]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-16]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-32]; + GROUP_MEM_FENCE; + sorterSharedMemory[idx] += sorterSharedMemory[idx-64]; + GROUP_MEM_FENCE; + + sorterSharedMemory[idx-1] += sorterSharedMemory[idx-2]; + GROUP_MEM_FENCE; + } + } + + GROUP_LDS_BARRIER; + + *totalSum = sorterSharedMemory[WG_SIZE*2-1]; + uint addValue = sorterSharedMemory[lIdx+127]; + return pData + make_uint4(addValue, addValue, addValue, addValue); +} + + +void generateHistogram(u32 lIdx, u32 wgIdx, + uint4 sortedData, + __local u32 *histogram) +{ + if( lIdx < (1<>cb.m_startBit, sortData[1].m_key>>cb.m_startBit, + sortData[2].m_key>>cb.m_startBit, sortData[3].m_key>>cb.m_startBit ); + + generateHistogram( lIdx, wgIdx, localKeys, ldsSortData ); + + GROUP_LDS_BARRIER; + + int nBins = (1<>cb.m_startBit)&cmpValue, (sortData[1].m_key>>cb.m_startBit)&cmpValue, + (sortData[2].m_key>>cb.m_startBit)&cmpValue, (sortData[3].m_key>>cb.m_startBit)&cmpValue );; + + // data is already sorted. So simply subtract local prefix sum + uint4 dstAddr; + dstAddr.x = ldsGlobalHistogram[radix.x] + (localAddr.x - ldsLocalHistogram[radix.x]); + dstAddr.y = ldsGlobalHistogram[radix.y] + (localAddr.y - ldsLocalHistogram[radix.y]); + dstAddr.z = ldsGlobalHistogram[radix.z] + (localAddr.z - ldsLocalHistogram[radix.z]); + dstAddr.w = ldsGlobalHistogram[radix.w] + (localAddr.w - ldsLocalHistogram[radix.w]); + + dst[dstAddr.x] = sortData[0]; + dst[dstAddr.y] = sortData[1]; + dst[dstAddr.z] = sortData[2]; + dst[dstAddr.w] = sortData[3]; +} + +__kernel +__attribute__((reqd_work_group_size(WG_SIZE,1,1))) +void CopyKernel(__global SortData *src, __global SortData *dst) +{ + dst[ GET_GLOBAL_IDX ] = src[ GET_GLOBAL_IDX ]; +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.hlsl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.hlsl new file mode 100644 index 000000000..55a6a1ca7 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernels.hlsl @@ -0,0 +1,322 @@ +/* + 2011 Takahiro Harada +*/ + +typedef uint u32; + +#define GET_GROUP_IDX groupIdx.x +#define GET_LOCAL_IDX localIdx.x +#define GET_GLOBAL_IDX globalIdx.x +#define GROUP_LDS_BARRIER GroupMemoryBarrierWithGroupSync() +#define GROUP_MEM_FENCE +#define DEFAULT_ARGS uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID +#define AtomInc(x) InterlockedAdd(x, 1) +#define AtomInc1(x, out) InterlockedAdd(x, 1, out) + +#define make_uint4 uint4 +#define make_uint2 uint2 + +uint4 SELECT_UINT4(uint4 b,uint4 a,uint4 condition ){ return make_uint4( ((condition).x)?a.x:b.x, ((condition).y)?a.y:b.y, ((condition).z)?a.z:b.z, ((condition).w)?a.w:b.w ); } + +// takahiro end +#define WG_SIZE 128 +#define NUM_PER_WI 4 + +#define GET_GROUP_SIZE WG_SIZE + +typedef struct +{ + u32 m_key; + u32 m_value; +}SortData; + +cbuffer SortCB : register( b0 ) +{ + u32 m_startBit; + u32 m_numGroups; + u32 m_padding[2]; +}; + +#define BITS_PER_PASS 4 + + +uint4 prefixScanVector( uint4 data ) +{ + data.y += data.x; + data.w += data.z; + data.z += data.y; + data.w += data.y; + return data; +} + +uint prefixScanVectorEx( inout uint4 data ) +{ + uint4 backup = data; + data.y += data.x; + data.w += data.z; + data.z += data.y; + data.w += data.y; + uint sum = data.w; + data -= backup; + return sum; +} + + + +RWStructuredBuffer sortDataIn : register( u0 ); +RWStructuredBuffer ldsHistogramOut0 : register( u1 ); +RWStructuredBuffer ldsHistogramOut1 : register( u2 ); + +groupshared u32 ldsSortData[ WG_SIZE*NUM_PER_WI + 16 ]; + + +uint4 localPrefixSum128V( uint4 pData, uint lIdx, inout uint totalSum ) +{ + { // Set data + ldsSortData[lIdx] = 0; + ldsSortData[lIdx+WG_SIZE] = prefixScanVectorEx( pData ); + } + + GROUP_LDS_BARRIER; + + { // Prefix sum + int idx = 2*lIdx + (WG_SIZE+1); + if( lIdx < 64 ) + { + ldsSortData[idx] += ldsSortData[idx-1]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-2]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-4]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-8]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-16]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-32]; + GROUP_MEM_FENCE; + ldsSortData[idx] += ldsSortData[idx-64]; + GROUP_MEM_FENCE; + + ldsSortData[idx-1] += ldsSortData[idx-2]; + GROUP_MEM_FENCE; + } + } + + GROUP_LDS_BARRIER; + + totalSum = ldsSortData[WG_SIZE*2-1]; + uint addValue = ldsSortData[lIdx+127]; + return pData + make_uint4(addValue, addValue, addValue, addValue); +} + +void generateHistogram(u32 lIdx, u32 wgIdx, + uint4 sortedData) +{ + if( lIdx < (1<>m_startBit, sortData[1].m_key>>m_startBit, + sortData[2].m_key>>m_startBit, sortData[3].m_key>>m_startBit ); + + generateHistogram( lIdx, wgIdx, localKeys ); + + GROUP_LDS_BARRIER; + + int nBins = (1< src : register( t0 ); +StructuredBuffer histogramGlobalRadixMajor : register( t1 ); +StructuredBuffer histogramLocalGroupMajor : register( t2 ); + +RWStructuredBuffer dst : register( u0 ); + +groupshared u32 ldsLocalHistogram[ 2*(1<>m_startBit)&cmpValue, (sortData[1].m_key>>m_startBit)&cmpValue, + (sortData[2].m_key>>m_startBit)&cmpValue, (sortData[3].m_key>>m_startBit)&cmpValue );; + + // data is already sorted. So simply subtract local prefix sum + uint4 dstAddr; + dstAddr.x = ldsGlobalHistogram[radix.x] + (localAddr.x - ldsLocalHistogram[radix.x]); + dstAddr.y = ldsGlobalHistogram[radix.y] + (localAddr.y - ldsLocalHistogram[radix.y]); + dstAddr.z = ldsGlobalHistogram[radix.z] + (localAddr.z - ldsLocalHistogram[radix.z]); + dstAddr.w = ldsGlobalHistogram[radix.w] + (localAddr.w - ldsLocalHistogram[radix.w]); + + dst[dstAddr.x] = sortData[0]; + dst[dstAddr.y] = sortData[1]; + dst[dstAddr.z] = sortData[2]; + dst[dstAddr.w] = sortData[3]; +} + +[numthreads(WG_SIZE, 1, 1)] +void CopyKernel( DEFAULT_ARGS ) +{ + dst[ GET_GLOBAL_IDX ] = src[ GET_GLOBAL_IDX ]; +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsCL.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsCL.h new file mode 100644 index 000000000..e793c7e94 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsCL.h @@ -0,0 +1,347 @@ +static const char* radixSortStandardKernelsCL= \ +"/*\n" +"Bullet Continuous Collision Detection and Physics Library\n" +"Copyright (c) 2011 Advanced Micro Devices, Inc. http://bulletphysics.org\n" +"\n" +"This software is provided 'as-is', without any express or implied warranty.\n" +"In no event will the authors be held liable for any damages arising from the use of this software.\n" +"Permission is granted to anyone to use this software for any purpose, \n" +"including commercial applications, and to alter it and redistribute it freely, \n" +"subject to the following restrictions:\n" +"\n" +"1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.\n" +"2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.\n" +"3. This notice may not be removed or altered from any source distribution.\n" +"*/\n" +"//Author Takahiro Harada\n" +"\n" +"\n" +"#pragma OPENCL EXTENSION cl_amd_printf : enable\n" +"#pragma OPENCL EXTENSION cl_khr_local_int32_base_atomics : enable\n" +"\n" +"typedef unsigned int u32;\n" +"#define GET_GROUP_IDX get_group_id(0)\n" +"#define GET_LOCAL_IDX get_local_id(0)\n" +"#define GET_GLOBAL_IDX get_global_id(0)\n" +"#define GET_GROUP_SIZE get_local_size(0)\n" +"#define GROUP_LDS_BARRIER barrier(CLK_LOCAL_MEM_FENCE)\n" +"#define GROUP_MEM_FENCE mem_fence(CLK_LOCAL_MEM_FENCE)\n" +"#define AtomInc(x) atom_inc(&(x))\n" +"#define AtomInc1(x, out) out = atom_inc(&(x))\n" +"\n" +"#define make_uint4 (uint4)\n" +"#define make_uint2 (uint2)\n" +"\n" +"#define SELECT_UINT4( b, a, condition ) select( b,a,condition )\n" +"\n" +"#define WG_SIZE 128\n" +"#define NUM_PER_WI 4\n" +"\n" +"\n" +"typedef struct\n" +"{\n" +" u32 m_key; \n" +" u32 m_value;\n" +"}SortData;\n" +"\n" +"\n" +"typedef struct\n" +"{\n" +" u32 m_startBit;\n" +" u32 m_numGroups;\n" +" u32 m_padding[2];\n" +"} ConstBuffer;\n" +"\n" +"#define BITS_PER_PASS 4\n" +"\n" +"\n" +"\n" +"uint4 prefixScanVector( uint4 data )\n" +"{\n" +" data.y += data.x;\n" +" data.w += data.z;\n" +" data.z += data.y;\n" +" data.w += data.y;\n" +" return data;\n" +"}\n" +"\n" +"uint prefixScanVectorEx( uint4* data )\n" +"{\n" +" uint4 backup = data[0];\n" +" data[0].y += data[0].x;\n" +" data[0].w += data[0].z;\n" +" data[0].z += data[0].y;\n" +" data[0].w += data[0].y;\n" +" uint sum = data[0].w;\n" +" *data -= backup;\n" +" return sum;\n" +"}\n" +"\n" +"uint4 localPrefixSum128V( uint4 pData, uint lIdx, uint* totalSum, __local u32 sorterSharedMemory[] )\n" +"{\n" +" { // Set data\n" +" sorterSharedMemory[lIdx] = 0;\n" +" sorterSharedMemory[lIdx+WG_SIZE] = prefixScanVectorEx( &pData );\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" { // Prefix sum\n" +" int idx = 2*lIdx + (WG_SIZE+1);\n" +" if( lIdx < 64 )\n" +" {\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-1];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-2]; \n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-4];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-8];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-16];\n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-32]; \n" +" GROUP_MEM_FENCE;\n" +" sorterSharedMemory[idx] += sorterSharedMemory[idx-64];\n" +" GROUP_MEM_FENCE;\n" +"\n" +" sorterSharedMemory[idx-1] += sorterSharedMemory[idx-2];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" *totalSum = sorterSharedMemory[WG_SIZE*2-1];\n" +" uint addValue = sorterSharedMemory[lIdx+127];\n" +" return pData + make_uint4(addValue, addValue, addValue, addValue);\n" +"}\n" +"\n" +"\n" +"void generateHistogram(u32 lIdx, u32 wgIdx, \n" +" uint4 sortedData,\n" +" __local u32 *histogram)\n" +"{\n" +" if( lIdx < (1<>cb.m_startBit, sortData[1].m_key>>cb.m_startBit, \n" +" sortData[2].m_key>>cb.m_startBit, sortData[3].m_key>>cb.m_startBit );\n" +"\n" +" generateHistogram( lIdx, wgIdx, localKeys, ldsSortData );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" int nBins = (1<>cb.m_startBit)&cmpValue, (sortData[1].m_key>>cb.m_startBit)&cmpValue, \n" +" (sortData[2].m_key>>cb.m_startBit)&cmpValue, (sortData[3].m_key>>cb.m_startBit)&cmpValue );;\n" +"\n" +" // data is already sorted. So simply subtract local prefix sum\n" +" uint4 dstAddr;\n" +" dstAddr.x = ldsGlobalHistogram[radix.x] + (localAddr.x - ldsLocalHistogram[radix.x]);\n" +" dstAddr.y = ldsGlobalHistogram[radix.y] + (localAddr.y - ldsLocalHistogram[radix.y]);\n" +" dstAddr.z = ldsGlobalHistogram[radix.z] + (localAddr.z - ldsLocalHistogram[radix.z]);\n" +" dstAddr.w = ldsGlobalHistogram[radix.w] + (localAddr.w - ldsLocalHistogram[radix.w]);\n" +"\n" +" dst[dstAddr.x] = sortData[0];\n" +" dst[dstAddr.y] = sortData[1];\n" +" dst[dstAddr.z] = sortData[2];\n" +" dst[dstAddr.w] = sortData[3];\n" +"}\n" +"\n" +"__kernel\n" +"__attribute__((reqd_work_group_size(WG_SIZE,1,1)))\n" +"void CopyKernel(__global SortData *src, __global SortData *dst)\n" +"{\n" +" dst[ GET_GLOBAL_IDX ] = src[ GET_GLOBAL_IDX ];\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsDX11.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsDX11.h new file mode 100644 index 000000000..1a919ed18 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/RadixSortStandardKernelsDX11.h @@ -0,0 +1,324 @@ +static const char* radixSortStandardKernelsDX11= \ +"/*\n" +" 2011 Takahiro Harada\n" +"*/\n" +"\n" +"typedef uint u32;\n" +"\n" +"#define GET_GROUP_IDX groupIdx.x\n" +"#define GET_LOCAL_IDX localIdx.x\n" +"#define GET_GLOBAL_IDX globalIdx.x\n" +"#define GROUP_LDS_BARRIER GroupMemoryBarrierWithGroupSync()\n" +"#define GROUP_MEM_FENCE\n" +"#define DEFAULT_ARGS uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID, uint3 groupIdx : SV_GroupID\n" +"#define AtomInc(x) InterlockedAdd(x, 1)\n" +"#define AtomInc1(x, out) InterlockedAdd(x, 1, out)\n" +"\n" +"#define make_uint4 uint4\n" +"#define make_uint2 uint2\n" +"\n" +"uint4 SELECT_UINT4(uint4 b,uint4 a,uint4 condition ){ return make_uint4( ((condition).x)?a.x:b.x, ((condition).y)?a.y:b.y, ((condition).z)?a.z:b.z, ((condition).w)?a.w:b.w ); }\n" +"\n" +"// takahiro end\n" +"#define WG_SIZE 128\n" +"#define NUM_PER_WI 4\n" +"\n" +"#define GET_GROUP_SIZE WG_SIZE\n" +"\n" +"typedef struct\n" +"{\n" +" u32 m_key; \n" +" u32 m_value;\n" +"}SortData;\n" +"\n" +"cbuffer SortCB : register( b0 )\n" +"{\n" +" u32 m_startBit;\n" +" u32 m_numGroups;\n" +" u32 m_padding[2];\n" +"};\n" +"\n" +"#define BITS_PER_PASS 4\n" +"\n" +"\n" +"uint4 prefixScanVector( uint4 data )\n" +"{\n" +" data.y += data.x;\n" +" data.w += data.z;\n" +" data.z += data.y;\n" +" data.w += data.y;\n" +" return data;\n" +"}\n" +"\n" +"uint prefixScanVectorEx( inout uint4 data )\n" +"{\n" +" uint4 backup = data;\n" +" data.y += data.x;\n" +" data.w += data.z;\n" +" data.z += data.y;\n" +" data.w += data.y;\n" +" uint sum = data.w;\n" +" data -= backup;\n" +" return sum;\n" +"}\n" +"\n" +"\n" +"\n" +"RWStructuredBuffer sortDataIn : register( u0 );\n" +"RWStructuredBuffer ldsHistogramOut0 : register( u1 );\n" +"RWStructuredBuffer ldsHistogramOut1 : register( u2 );\n" +"\n" +"groupshared u32 ldsSortData[ WG_SIZE*NUM_PER_WI + 16 ];\n" +"\n" +"\n" +"uint4 localPrefixSum128V( uint4 pData, uint lIdx, inout uint totalSum )\n" +"{\n" +" { // Set data\n" +" ldsSortData[lIdx] = 0;\n" +" ldsSortData[lIdx+WG_SIZE] = prefixScanVectorEx( pData );\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" { // Prefix sum\n" +" int idx = 2*lIdx + (WG_SIZE+1);\n" +" if( lIdx < 64 )\n" +" {\n" +" ldsSortData[idx] += ldsSortData[idx-1];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-2]; \n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-4];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-8];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-16];\n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-32]; \n" +" GROUP_MEM_FENCE;\n" +" ldsSortData[idx] += ldsSortData[idx-64];\n" +" GROUP_MEM_FENCE;\n" +"\n" +" ldsSortData[idx-1] += ldsSortData[idx-2];\n" +" GROUP_MEM_FENCE;\n" +" }\n" +" }\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" totalSum = ldsSortData[WG_SIZE*2-1];\n" +" uint addValue = ldsSortData[lIdx+127];\n" +" return pData + make_uint4(addValue, addValue, addValue, addValue);\n" +"}\n" +"\n" +"void generateHistogram(u32 lIdx, u32 wgIdx, \n" +" uint4 sortedData)\n" +"{\n" +" if( lIdx < (1<>m_startBit, sortData[1].m_key>>m_startBit, \n" +" sortData[2].m_key>>m_startBit, sortData[3].m_key>>m_startBit );\n" +"\n" +" generateHistogram( lIdx, wgIdx, localKeys );\n" +"\n" +" GROUP_LDS_BARRIER;\n" +"\n" +" int nBins = (1< src : register( t0 );\n" +"StructuredBuffer histogramGlobalRadixMajor : register( t1 );\n" +"StructuredBuffer histogramLocalGroupMajor : register( t2 );\n" +"\n" +"RWStructuredBuffer dst : register( u0 );\n" +"\n" +"groupshared u32 ldsLocalHistogram[ 2*(1<>m_startBit)&cmpValue, (sortData[1].m_key>>m_startBit)&cmpValue, \n" +" (sortData[2].m_key>>m_startBit)&cmpValue, (sortData[3].m_key>>m_startBit)&cmpValue );;\n" +"\n" +" // data is already sorted. So simply subtract local prefix sum\n" +" uint4 dstAddr;\n" +" dstAddr.x = ldsGlobalHistogram[radix.x] + (localAddr.x - ldsLocalHistogram[radix.x]);\n" +" dstAddr.y = ldsGlobalHistogram[radix.y] + (localAddr.y - ldsLocalHistogram[radix.y]);\n" +" dstAddr.z = ldsGlobalHistogram[radix.z] + (localAddr.z - ldsLocalHistogram[radix.z]);\n" +" dstAddr.w = ldsGlobalHistogram[radix.w] + (localAddr.w - ldsLocalHistogram[radix.w]);\n" +"\n" +" dst[dstAddr.x] = sortData[0];\n" +" dst[dstAddr.y] = sortData[1];\n" +" dst[dstAddr.z] = sortData[2];\n" +" dst[dstAddr.w] = sortData[3];\n" +"}\n" +"\n" +"[numthreads(WG_SIZE, 1, 1)]\n" +"void CopyKernel( DEFAULT_ARGS )\n" +"{\n" +" dst[ GET_GLOBAL_IDX ] = src[ GET_GLOBAL_IDX ];\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/SortData.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/SortData.h new file mode 100644 index 000000000..3d88ebdfd --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/SortData.h @@ -0,0 +1,31 @@ +/* + 2011 Takahiro Harada +*/ + +#pragma once + +#include + +namespace adl +{ + +struct SortData +{ + SortData(){} + SortData( u32 key, u32 value ) : m_key(key), m_value(value) {} + + union + { + u32 m_key; + struct { u16 m_key16[2]; }; + }; + u32 m_value; + + friend bool operator <(const SortData& a, const SortData& b) + { + return a.m_key < b.m_key; + } +}; + + +}; diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortadvanced.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortadvanced.inl new file mode 100644 index 000000000..210b5dd6b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortadvanced.inl @@ -0,0 +1,146 @@ +/* + 2011 Takahiro Harada +*/ + +#define PATH "..\\..\\AdlPrimitives\\Sort\\RadixSortAdvancedKernels" +#define KERNEL0 "StreamCountKernel" +#define KERNEL1 "SortAndScatterKernel1" +#define KERNEL2 "PrefixScanKernel" + +template +class RadixSortAdvanced : public RadixSortBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + enum + { + WG_SIZE = 128, + NUM_PER_WI = 4, + MAX_NUM_WORKGROUPS = 60, + }; + + struct Data : public RadixSort::Data + { + Kernel* m_localCountKernel; + Kernel* m_scatterKernel; + Kernel* m_scanKernel; + + Buffer* m_workBuffer0; + Buffer* m_workBuffer1; + Buffer* m_constBuffer[32/4]; + }; + + + static + Data* allocate(const Device* deviceData, int maxSize, Option option = SORT_NORMAL); + + static + void deallocate(void* data); + + static + void execute(void* data, Buffer& inout, int n, int sortBits); +}; + +template +typename RadixSortAdvanced::Data* RadixSortAdvanced::allocate(const Device* deviceData, int maxSize, Option option) +{ + ADLASSERT( type == deviceData->m_type ); + + const char* src[] = { 0, 0, 0 }; + + Data* data = new Data; + data->m_option = option; + data->m_deviceData = deviceData; + + data->m_localCountKernel = deviceData->getKernel( PATH, KERNEL0, 0, src[type] ); + data->m_scatterKernel = deviceData->getKernel( PATH, KERNEL1, 0, src[type] ); + data->m_scanKernel = deviceData->getKernel( PATH, KERNEL2, 0, src[type] ); + + data->m_workBuffer0 = new Buffer( deviceData, MAX_NUM_WORKGROUPS*16 ); + data->m_workBuffer1 = new Buffer( deviceData, maxSize ); + for(int i=0; i<32/4; i++) + data->m_constBuffer[i] = new Buffer( deviceData, 1, BufferBase::BUFFER_CONST ); + data->m_maxSize = maxSize; + + return data; +} + +template +void RadixSortAdvanced::deallocate(void* rawData) +{ + Data* data = (Data*)rawData; + + delete data->m_workBuffer0; + delete data->m_workBuffer1; + for(int i=0; i<32/4; i++) + delete data->m_constBuffer[i]; + + delete data; +} + +template +void RadixSortAdvanced::execute(void* rawData, Buffer& inout, int n, int sortBits) +{ + Data* data = (Data*)rawData; + + ADLASSERT( sortBits == 32 ); + + ADLASSERT( NUM_PER_WI == 4 ); + ADLASSERT( n%(WG_SIZE*NUM_PER_WI) == 0 ); + ADLASSERT( MAX_NUM_WORKGROUPS < 128*8/16 ); + + Buffer* src = &inout; + Buffer* dst = data->m_workBuffer1; + + const Device* deviceData = data->m_deviceData; + + int nBlocks = n/(NUM_PER_WI*WG_SIZE); + const int nWorkGroupsToExecute = min2((int)MAX_NUM_WORKGROUPS, nBlocks); + int nBlocksPerGroup = (nBlocks+nWorkGroupsToExecute-1)/nWorkGroupsToExecute; + ADLASSERT( nWorkGroupsToExecute <= MAX_NUM_WORKGROUPS ); + + int4 constBuffer = make_int4(0, nBlocks, nWorkGroupsToExecute, nBlocksPerGroup); + + int iPass = 0; + int startBit = 0; + for(int startBit=0; startBit<32; startBit+=4, iPass++) + { + constBuffer.x = startBit; + + { + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( data->m_workBuffer0 ) }; + + Launcher launcher( deviceData, data->m_localCountKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE* nWorkGroupsToExecute, WG_SIZE ); + } + + + { + BufferInfo bInfo[] = { BufferInfo( data->m_workBuffer0 ) }; + + Launcher launcher( deviceData, data->m_scanKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE, WG_SIZE ); + } + + { + BufferInfo bInfo[] = { BufferInfo( data->m_workBuffer0, true ), BufferInfo( src ), BufferInfo( dst ) }; + + Launcher launcher( deviceData, data->m_scatterKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE*nWorkGroupsToExecute, WG_SIZE ); + } + + swap2( src, dst ); + } +} + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 +#undef KERNEL2 diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortsimple.inl b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortsimple.inl new file mode 100644 index 000000000..3fcab75fa --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/Sort/radixsortsimple.inl @@ -0,0 +1,149 @@ +/* + 2011 Takahiro Harada +*/ + +#define PATH "..\\..\\opencl\\primitives\\AdlPrimitives\\Sort\\RadixSortSimpleKernels" +#define KERNEL0 "LocalCountKernel" +#define KERNEL1 "ScatterKernel" + +#include +#include + +template +class RadixSortSimple : public RadixSortBase +{ + public: + typedef Launcher::BufferInfo BufferInfo; + + enum + { + WG_SIZE = 128, + NUM_PER_WI = 4, + }; + + struct Data : public RadixSort::Data + { + Kernel* m_localCountKernel; + Kernel* m_scatterKernel; + + Buffer* m_workBuffer0; + Buffer* m_workBuffer1; + Buffer* m_workBuffer2; + Buffer* m_constBuffer[4]; + }; + + + static + Data* allocate(const Device* deviceData, int maxSize, Option option = SORT_NORMAL); + + static + void deallocate(void* data); + + static + void execute(void* data, Buffer& inout, int n, int sortBits); +}; + +template +typename RadixSortSimple::Data* RadixSortSimple::allocate(const Device* deviceData, int maxSize, Option option) +{ + ADLASSERT( type == deviceData->m_type ); + + const char* src[] = +#if defined(ADL_LOAD_KERNEL_FROM_STRING) + {radixSortSimpleKernelsCL, radixSortSimpleKernelsDX11}; +#else + { 0, 0 }; +#endif + u32 maxNumGroups = (maxSize+WG_SIZE*NUM_PER_WI-1)/(WG_SIZE*NUM_PER_WI); + + Data* data = new Data; + data->m_option = option; + data->m_deviceData = deviceData; + + data->m_localCountKernel = deviceData->getKernel( PATH, KERNEL0, 0, src[type] ); + data->m_scatterKernel = deviceData->getKernel( PATH, KERNEL1, 0, src[type] ); + + data->m_scanData = PrefixScan::allocate( deviceData, maxSize ); + + data->m_workBuffer0 = new Buffer( deviceData, maxNumGroups*256 ); + data->m_workBuffer1 = new Buffer( deviceData, maxNumGroups*256 ); + data->m_workBuffer2 = new Buffer( deviceData, maxSize ); + data->m_constBuffer[0] = new Buffer( deviceData, 1, BufferBase::BUFFER_CONST ); + data->m_constBuffer[1] = new Buffer( deviceData, 1, BufferBase::BUFFER_CONST ); + data->m_constBuffer[2] = new Buffer( deviceData, 1, BufferBase::BUFFER_CONST ); + data->m_constBuffer[3] = new Buffer( deviceData, 1, BufferBase::BUFFER_CONST ); + data->m_maxSize = maxSize; + + return data; +} + +template +void RadixSortSimple::deallocate(void* rawData) +{ + Data* data = (Data*)rawData; + + delete data->m_workBuffer0; + delete data->m_workBuffer1; + delete data->m_workBuffer2; + delete data->m_constBuffer[0]; + delete data->m_constBuffer[1]; + delete data->m_constBuffer[2]; + delete data->m_constBuffer[3]; + + PrefixScan::deallocate( data->m_scanData ); + + delete data; +} + +template +void RadixSortSimple::execute(void* rawData, Buffer& inout, int n, int sortBits) +{ + Data* data = (Data*)rawData; + + ADLASSERT( sortBits == 32 ); + ADLASSERT( n%512 == 0 ); + ADLASSERT( n <= data->m_maxSize ); + + Buffer* src = &inout; + Buffer* dst = data->m_workBuffer2; + + const Device* deviceData = data->m_deviceData; + + int numGroups = (n+WG_SIZE*NUM_PER_WI-1)/(WG_SIZE*NUM_PER_WI); + + int4 constBuffer; + + int iPass = 0; + for(int startBit=0; startBit<32; startBit+=8, iPass++) + { + constBuffer.x = startBit; + constBuffer.y = numGroups; + constBuffer.z = WG_SIZE; + + { + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( data->m_workBuffer0 ) }; + + Launcher launcher( deviceData, data->m_localCountKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE*numGroups, WG_SIZE ); + } + + PrefixScan::execute( data->m_scanData, *data->m_workBuffer0, *data->m_workBuffer1, numGroups*256 ); + + { + BufferInfo bInfo[] = { BufferInfo( src, true ), BufferInfo( dst ), BufferInfo( data->m_workBuffer1 ) }; + + Launcher launcher( deviceData, data->m_scatterKernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( *data->m_constBuffer[iPass], constBuffer ); + launcher.launch1D( WG_SIZE*numGroups, WG_SIZE ); + } + + swap2( src, dst ); + } +} + +#undef PATH +#undef KERNEL0 +#undef KERNEL1 diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringify.py b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringify.py new file mode 100644 index 000000000..e79e281e4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringify.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import sys +import os +import shutil + +arg = sys.argv[1] +fh = open(arg) + +print 'static const char* '+sys.argv[2]+'= \\' +for line in fh.readlines(): + a = line.strip('\n') + print '"'+a+'\\n"' +print ';' diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringifykernels.bat b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringifykernels.bat new file mode 100644 index 000000000..3a2aa63f1 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlPrimitives/stringifykernels.bat @@ -0,0 +1,22 @@ +stringify.py Fill/FillKernels.cl fillKernelsCL >Fill/FillKernelsCL.h +stringify.py Fill/FillKernels.hlsl fillKernelsDX11 >Fill/FillKernelsDX11.h +stringify.py Scan/PrefixScanKernels.cl prefixScanKernelsCL >Scan/PrefixScanKernelsCL.h +stringify.py Scan/PrefixScanKernels.hlsl prefixScanKernelsDX11 >Scan/PrefixScanKernelsDX11.h +stringify.py Search/BoundSearchKernels.cl boundSearchKernelsCL >Search/BoundSearchKernelsCL.h +stringify.py Search/BoundSearchKernels.hlsl boundSearchKernelsDX11 >Search/BoundSearchKernelsDX11.h +stringify.py Sort/RadixSortSimpleKernels.cl radixSortSimpleKernelsCL >Sort/RadixSortSimpleKernelsCL.h +stringify.py Sort/RadixSortSimpleKernels.hlsl radixSortSimpleKernelsDX11 >Sort/RadixSortSimpleKernelsDX11.h +stringify.py Sort/RadixSortStandardKernels.cl radixSortStandardKernelsCL >Sort/RadixSortStandardKernelsCL.h + +stringify.py Sort/RadixSort32Kernels.cl radixSort32KernelsCL >Sort/RadixSort32KernelsCL.h +stringify.py Sort/RadixSort32Kernels.hlsl radixSort32KernelsDX11 >Sort/RadixSort32KernelsDX11.h + +stringify.py Copy/CopyKernels.cl copyKernelsCL >Copy/CopyKernelsCL.h +stringify.py Copy/CopyKernels.hlsl copyKernelsDX11 >Copy/CopyKernelsDX11.h + +stringify.py Sort/RadixSortStandardKernels.hlsl radixSortStandardKernelsDX11 >Sort/RadixSortStandardKernelsDX11.h +stringify.py Sort/RadixSortAdvancedKernels.hlsl radixSortAdvancedKernelsDX11 >Sort/RadixSortAdvancedKernelsDX11.h + + + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/AMD/premake4.lua new file mode 100644 index 000000000..d47cec9fa --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/AMD/premake4.lua @@ -0,0 +1,31 @@ + + hasCL = findOpenCL_AMD() + hasDX11 = findDirectX11() + + if (hasCL) then + + project "OpenCL_DX11_primitives_test_AMD" + + initOpenCL_AMD() + + if (hasDX11) then + initDirectX11() + end + + language "C++" + + kind "ConsoleApp" + targetdir "../../../../bin" + includedirs {"..","../.."} + + links { + "OpenCL" + } + + files { + "../main.cpp", + "../RadixSortBenchmark.h", + "../UnitTests.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/Intel/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/Intel/premake4.lua new file mode 100644 index 000000000..157405f90 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/Intel/premake4.lua @@ -0,0 +1,31 @@ + + hasCL = findOpenCL_Intel() + hasDX11 = findDirectX11() + + if (hasCL) then + + project "OpenCL_DX11_primitives_test_Intel" + + initOpenCL_Intel() + + if (hasDX11) then + initDirectX11() + end + + language "C++" + + kind "ConsoleApp" + targetdir "../../../../bin" + includedirs {"..","../.."} + + links { + "OpenCL" + } + + files { + "../main.cpp", + "../RadixSortBenchmark.h", + "../UnitTests.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/LaunchOverheadBenchmark.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/LaunchOverheadBenchmark.h new file mode 100644 index 000000000..4b1ae7d0f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/LaunchOverheadBenchmark.h @@ -0,0 +1,103 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#include + + + +template +__inline +void copyTest( Device* device ) +{ + int size = 65*1024; + + Buffer buf0( device, size ); + Buffer buf1( device, size ); + + Stopwatch sw( device ); + + Copy::Data* data = Copy::allocate( device ); + + for(int i=0; i<10; i++) + Copy::execute( data, buf1, buf0, size, CopyBase::PER_WI_1 ); + DeviceUtils::waitForCompletion( device ); + + { + const int nTests = 12; + + float t[nTests]; + + for(int ii=0; ii::execute( data, buf1, buf0, size, CopyBase::PER_WI_1 ); + } + DeviceUtils::waitForCompletion( device ); + sw.stop(); + + t[ii] = sw.getMs()/(float)iter; + } + + for(int ii=0; ii::deallocate( data ); +} + +void launchOverheadBenchmark() +{ + printf("LaunchOverheadBenchmark\n"); + + + Device* ddcl; +#if defined(ADL_ENABLE_DX11) + Device* dddx; +#endif + { + DeviceUtils::Config cfg; + ddcl = DeviceUtils::allocate( TYPE_CL, cfg ); +#if defined(ADL_ENABLE_DX11) + dddx = DeviceUtils::allocate( TYPE_DX11, cfg ); +#endif + } + + { + printf("CL\n"); + copyTest( ddcl ); + } +#ifdef ADL_ENABLE_DX11 + { + printf("DX11\n"); + copyTest( dddx ); + } +#endif + + +} + + +//1, 2, 4, 8, 16, 32, 64, 128, 256, + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/NVIDIA/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/NVIDIA/premake4.lua new file mode 100644 index 000000000..e4d5cea98 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/NVIDIA/premake4.lua @@ -0,0 +1,31 @@ + + hasCL = findOpenCL_NVIDIA() + hasDX11 = findDirectX11() + + if (hasCL) then + + project "OpenCL_DX11_primitives_test_NVIDIA" + + initOpenCL_NVIDIA() + + if (hasDX11) then + initDirectX11() + end + + language "C++" + + kind "ConsoleApp" + targetdir "../../../../bin" + includedirs {"..","../.."} + + links { + "OpenCL" + } + + files { + "../main.cpp", + "../RadixSortBenchmark.h", + "../UnitTests.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/RadixSortBenchmark.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/RadixSortBenchmark.h new file mode 100644 index 000000000..35404dd06 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/RadixSortBenchmark.h @@ -0,0 +1,121 @@ + +template +void run( Device* device, int minSize = 512, int maxSize = 64*1024 )//, int increment = 512 ) +{ + ADLASSERT( TYPE == device->m_type ); + + Stopwatch sw( device ); + +// RadixSort::Data* data0 = RadixSort::allocate( device, maxSize, RadixSortBase::SORT_SIMPLE ); + RadixSort::Data* data0 = RadixSort::allocate( device, maxSize, RadixSortBase::SORT_STANDARD ); + RadixSort::Data* data1 = RadixSort::allocate( device, maxSize, RadixSortBase::SORT_STANDARD ); + RadixSort::Data* data2 = RadixSort::allocate( device, maxSize, RadixSortBase::SORT_ADVANCED ); + + Buffer buf0( device, maxSize ); + Buffer buf1( device, maxSize ); + Buffer buf2( device, maxSize ); + + SortData* input = new SortData[ maxSize ]; + +// for(int iter = minSize; iter<=maxSize; iter+=increment) + for(int iter = minSize; iter<=maxSize; iter*=2) + { + int size = NEXTMULTIPLEOF( iter, 512 ); + + for(int i=0; i::execute( data0, buf0, size ); + + sw.split(); + + RadixSort::execute( data1, buf1, size ); + + sw.split(); + + RadixSort::execute( data2, buf2, size ); + + sw.stop(); + + + float t[3]; + sw.getMs( t, 3 ); +// printf(" %d %3.2f %3.2f %3.2f\n", size, t[0], t[1], t[2]); + printf(" %d %3.2f %3.2f\n", size, t[1], t[2]); + } + + RadixSort::deallocate( data0 ); + RadixSort::deallocate( data1 ); + RadixSort::deallocate( data2 ); + + delete [] input; +} + +template +void run32( Device* device, int size ) +{ + //Cayman: 4194.30Keys: 373.05MKeys/s + //Cypress: 4194.30Keys: 315.13MKeys/s + ADLASSERT( TYPE == device->m_type ); + + Stopwatch sw( device ); + + RadixSort32::Data* data = RadixSort32::allocate( device, size ); + Copy::Data* copyData = Copy::allocate( device ); + + Buffer inputMaster( device, size ); + Buffer input( device, size ); + Buffer output( device, size ); + { + u32* host = new u32[size]; + for(int i=0; i::execute( copyData, (Buffer&)input, (Buffer&)inputMaster, size ); +// RadixSort32::execute( data, input, size ); + RadixSort32::execute( data, input, output, size ); + } + sw.stop(); + + { + float tInS = sw.getMs()/1000.f/(float)nIter; + float mKeysPerS = size/1000.f/1000.f/tInS; + printf("%3.2fMKeys: %3.2fMKeys/s\n", size/1000.f, mKeysPerS); + } + + RadixSort32::deallocate( data ); + Copy::deallocate( copyData ); +} + +template +void radixSortBenchmark() +{ + + Device* device; + { + DeviceUtils::Config cfg; + device = DeviceUtils::allocate( TYPE, cfg ); + } + + run32( device, 256*1024*8*2 ); +// run32( device, 256*20*6 ); + +// run( device, 512, 1024*128*4 ); + + DeviceUtils::deallocate( device ); + +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/UnitTests.h b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/UnitTests.h new file mode 100644 index 000000000..a1ab2e417 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/UnitTests.h @@ -0,0 +1,801 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#include +#include +#include +#include +#include +#include + +#include + +using namespace adl; + +#define NUM_TESTS 10 + +int g_nPassed = 0; +int g_nFailed = 0; +bool g_testFailed = 0; + +//#define TEST_INIT bool g_testFailed = 0; +#define TEST_INIT g_testFailed = 0; +#define TEST_ASSERT(x) if( !(x) ){g_testFailed = 1;} +//#define TEST_ASSERT(x) if( !(x) ){g_testFailed = 1;ADLASSERT(x);} +#define TEST_REPORT(testName) printf("[%s] %s\n",(g_testFailed)?"X":"O", testName); if(g_testFailed) g_nFailed++; else g_nPassed++; + +void memCpyTest( Device* deviceData ) +{ + TEST_INIT; + int maxSize = 64*1024; + Buffer buff( deviceData, maxSize ); + + u32* hostBuff = new u32[maxSize]; + + for(int iter=0; iterquery(deviceData, ".\\Kernel", "VectorAddKernel" ); + + { + int size = 1024; + Buffer buf0( deviceData, size ); + Buffer buf1( deviceData, size ); + Buffer cBuf( deviceData, 1, BufferBase::BUFFER_CONST ); + int* hostBuf0 = new int[size]; + int* hostBuf1 = new int[size]; + for(int i=0; i*)&buf0 ), Launcher::BufferInfo( (Buffer*)&buf1, true ) }; + + Launcher launcher( deviceData, kernel ); + launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(Launcher::BufferInfo) ); + launcher.setConst( (Buffer&)cBuf, constBuffer ); + launcher.launch1D( size ); + + buf0.read( hostBuf0, size ); + buf1.read( hostBuf1, size ); + DeviceUtils::waitForCompletion( deviceData ); + } + + for(int i=0; i +void scanTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + Buffer buf3( deviceGPU, maxSize ); + + PrefixScan::Data* data0 = PrefixScan::allocate( deviceGPU, maxSize ); + PrefixScan::Data* data1 = PrefixScan::allocate( deviceHost, maxSize ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data1, buf0, buf1, size, &sumHost ); + PrefixScan::execute( data0, buf2, buf3, size, &sumGPU ); + + buf3.read( buf0.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + TEST_ASSERT( sumHost == sumGPU ); + for(int i=0; i::deallocate( data1 ); + PrefixScan::deallocate( data0 ); + + TEST_REPORT( "scanTest" ); +} + +template +bool radixSortTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + + RadixSort::Data* dataH = RadixSort::allocate( deviceHost, maxSize, RadixSortBase::SORT_SIMPLE ); + RadixSort::Data* dataC = RadixSort::allocate( deviceGPU, maxSize, SORT_TYPE ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( dataH, buf0, size ); + RadixSort::execute( dataC, buf2, size ); + + buf2.read( buf1.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( dataH ); + RadixSort::deallocate( dataC ); + + return g_testFailed; +} + +template +void radixSortSimpleTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + g_testFailed = radixSortTest(deviceGPU, deviceHost); + TEST_REPORT( "radixSortSimpleTest" ); +} + +template +void radixSortStandardTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + g_testFailed = radixSortTest(deviceGPU, deviceHost); + TEST_REPORT( "radixSortStandardTest" ); +} + +template +void radixSortAdvancedTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + g_testFailed = radixSortTest(deviceGPU, deviceHost); + TEST_REPORT( "radixSortAdvancedTest" ); +} + +template +void boundSearchTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + int bucketSize = 256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer lowerH( deviceHost, maxSize ); + HostBuffer upperH( deviceHost, maxSize ); + + Buffer buf( deviceGPU, maxSize ); + Buffer lower( deviceGPU, maxSize ); + Buffer upper( deviceGPU, maxSize ); + + BoundSearch::Data* dataH = BoundSearch::allocate( deviceGPU ); + RadixSort::Data* dataHSort = RadixSort::allocate( deviceHost, maxSize, RadixSortBase::SORT_SIMPLE ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( dataHSort, buf0, size ); + buf.write( buf0.m_ptr, size ); + { + u32* host = new u32[size]; + for(int i=0; i::execute( dataH, buf, size, lower, bucketSize, BoundSearchBase::BOUND_LOWER ); + BoundSearch::execute( dataH, buf, size, upper, bucketSize, BoundSearchBase::BOUND_UPPER ); + + lower.read( lowerH.m_ptr, bucketSize ); + upper.read( upperH.m_ptr, bucketSize ); + DeviceUtils::waitForCompletion( deviceGPU ); +/* + for(u32 i=1; i<(u32)bucketSize; i++) + { + for(u32 j=lowerH[i-1]; j::deallocate( dataH ); + RadixSort::deallocate( dataHSort ); + + TEST_REPORT( "boundSearchTest" ); +} + +template +void fillIntTest( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + + Fill::Data* data0 = Fill::allocate( deviceHost ); + Fill::Data* data1 = Fill::allocate( deviceGPU ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data0, buf0, 12, size ); + Fill::execute( data1, buf2, 12, size ); + + buf2.read( buf1.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( data0 ); + Fill::deallocate( data1 ); + + TEST_REPORT( "fillIntTest" ); +} + +template +void fillInt2Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + + Fill::Data* data0 = Fill::allocate( deviceHost ); + Fill::Data* data1 = Fill::allocate( deviceGPU ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data0, buf0, make_int2( 12, 12 ), size ); + Fill::execute( data1, buf2, make_int2( 12, 12 ), size ); + + buf2.read( buf1.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( data0 ); + Fill::deallocate( data1 ); + + TEST_REPORT( "fillInt2Test" ); +} + +template +void fillInt4Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + + Fill::Data* data0 = Fill::allocate( deviceHost ); + Fill::Data* data1 = Fill::allocate( deviceGPU ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data0, buf0, make_int4( 12 ), size ); + Fill::execute( data1, buf2, make_int4( 12 ), size ); + + buf2.read( buf1.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( data0 ); + Fill::deallocate( data1 ); + + TEST_REPORT( "fillInt4Test" ); +} + + +template +bool CopyF4Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + Buffer buf3( deviceGPU, maxSize ); + HostBuffer devResult( deviceHost, maxSize ); + + Copy::Data* data0 = Copy::allocate( deviceHost ); + Copy::Data* data1 = Copy::allocate( deviceGPU ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data0, buf1, buf0, size, OPTION ); + Copy::execute( data1, buf3, buf2, size, OPTION ); + + buf3.read( devResult.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( data0 ); + Copy::deallocate( data1 ); + + return g_testFailed; +} + +template +void Copy1F4Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + g_testFailed = CopyF4Test( deviceGPU, deviceHost ); + TEST_REPORT( "Copy1F4Test" ); +} + +template +void Copy2F4Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + g_testFailed = CopyF4Test( deviceGPU, deviceHost ); + TEST_REPORT( "Copy2F4Test" ); +} + +template +void Copy4F4Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + g_testFailed = CopyF4Test( deviceGPU, deviceHost ); + TEST_REPORT( "Copy4F4Test" ); +} + + +template +void CopyF1Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + Buffer buf3( deviceGPU, maxSize ); + HostBuffer devResult( deviceHost, maxSize ); + + Copy::Data* data0 = Copy::allocate( deviceHost ); + Copy::Data* data1 = Copy::allocate( deviceGPU ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data0, buf1, buf0, size ); + Copy::execute( data1, buf3, buf2, size ); + + buf3.read( devResult.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( data0 ); + Copy::deallocate( data1 ); + + TEST_REPORT( "CopyF1Test" ); +} + +template +void CopyF2Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + Buffer buf3( deviceGPU, maxSize ); + HostBuffer devResult( deviceHost, maxSize ); + + Copy::Data* data0 = Copy::allocate( deviceHost ); + Copy::Data* data1 = Copy::allocate( deviceGPU ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( data0, buf1, buf0, size ); + Copy::execute( data1, buf3, buf2, size ); + + buf3.read( devResult.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( data0 ); + Copy::deallocate( data1 ); + + TEST_REPORT( "CopyF2Test" ); +} + +template +void radixSort32Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + + RadixSort32::Data* dataH = RadixSort32::allocate( deviceHost, maxSize ); + RadixSort32::Data* dataC = RadixSort32::allocate( deviceGPU, maxSize ); + + int dx = maxSize/NUM_TESTS; + for(int iter=0; iter::execute( dataH, buf0, size, 32 ); + RadixSort32::execute( dataC, buf2, size, 32 ); + + buf2.read( buf1.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); +// for(int i=0; i::deallocate( dataH ); + RadixSort32::deallocate( dataC ); + + TEST_REPORT( "RadixSort32Test" ); +} + +template +void radixSortKeyValue32Test( Device* deviceGPU, Device* deviceHost ) +{ + TEST_INIT; + ADLASSERT( type == deviceGPU->m_type ); + + int maxSize = 1024*256; + + // Host buffers + HostBuffer buf0( deviceHost, maxSize ); // Buffer for keys in host and will be sorted by host. + HostBuffer buf1( deviceHost, maxSize ); // Buffer for keys in host and will be saved by device after sorting in device. + HostBuffer buf2( deviceHost, maxSize ); // Buffer for values in host. This buffer is paired with buf0. + HostBuffer buf3( deviceHost, maxSize ); // Buffer for values in host and will be saved by device after sorting. It is paired with buf1. + + // Device buffers + Buffer buf4( deviceGPU, maxSize ); // Buffer for input keys for device. + Buffer buf5( deviceGPU, maxSize ); // Buffer for output keys from device and will be sorted by device. This key data will be saved to buf1 to be compared with a result(buf0) from host. + Buffer buf6( deviceGPU, maxSize ); // Buffer for input values in device. + Buffer buf7( deviceGPU, maxSize ); // Buffer for output values in device. + + RadixSort32::Data* dataH = RadixSort32::allocate( deviceHost, maxSize ); + RadixSort32::Data* dataC = RadixSort32::allocate( deviceGPU, maxSize ); + + int dx = maxSize/NUM_TESTS; + + for(int iter=0; iter::execute( dataH, buf0, buf2, size, 32 ); + RadixSort32::execute( dataC, buf4, buf5, buf6, buf7, size, 32 ); + buf5.read( buf1.m_ptr, size ); + buf7.read( buf3.m_ptr, size ); + + DeviceUtils::waitForCompletion( deviceGPU ); + + for(int i=0; i::deallocate( dataH ); + RadixSort32::deallocate( dataC ); + + TEST_REPORT( "RadixSortKeyValue32Test" ); +} + +#if defined(ADL_ENABLE_DX11) + #define RUN_GPU( func ) func(ddcl); func(dddx); + #define RUN_GPU_TEMPLATE( func ) func( ddcl, ddhost ); func( dddx, ddhost ); + #define RUN_CL_TEMPLATE( func ) func( ddcl, ddhost ); +#else + #define RUN_GPU( func ) func(ddcl); + #define RUN_GPU_TEMPLATE( func ) func( ddcl, ddhost ); +#endif +#define RUN_ALL( func ) RUN_GPU( func ); func(ddhost); + +void runAllTest() +{ + g_nPassed = 0; + g_nFailed = 0; + + + Device* ddcl; + Device* ddhost; +#if defined(ADL_ENABLE_DX11) + Device* dddx; +#endif + + { + DeviceUtils::Config cfg; + + // Choose AMD or NVidia +#ifdef CL_PLATFORM_AMD + cfg.m_vendor = adl::DeviceUtils::Config::VD_AMD; +#endif + +#ifdef CL_PLATFORM_INTEL + cfg.m_vendor = adl::DeviceUtils::Config::VD_INTEL; + cfg.m_type = DeviceUtils::Config::DEVICE_CPU; +#endif + + +#ifdef CL_PLATFORM_NVIDIA + cfg.m_vendor = adl::DeviceUtils::Config::VD_NV; +#endif + + + ddcl = DeviceUtils::allocate( TYPE_CL, cfg ); + ddhost = DeviceUtils::allocate( TYPE_HOST, cfg ); +// cfg.m_type = DeviceUtils::Config::DEVICE_GPU; +#if defined(ADL_ENABLE_DX11) + dddx = DeviceUtils::allocate( TYPE_DX11, cfg ); +#endif + } + + { + char name[128]; + ddcl->getDeviceName( name ); + printf("CL: %s\n", name); +#ifdef ADL_ENABLE_DX11 + dddx->getDeviceName( name ); + printf("DX11: %s\n", name); +#endif + } + + RUN_GPU_TEMPLATE( radixSort32Test ); + RUN_GPU_TEMPLATE( radixSortKeyValue32Test ); + + if (1) + { + RUN_GPU_TEMPLATE( CopyF1Test ); + RUN_GPU_TEMPLATE( CopyF2Test ); + + boundSearchTest( ddhost, ddhost ); +// fillTest( ddhost, ddhost ); +// fillTest( ddcl, ddhost ); + + + + + RUN_GPU_TEMPLATE( boundSearchTest ); + + RUN_GPU_TEMPLATE( fillIntTest ); + RUN_GPU_TEMPLATE( fillInt2Test ); + RUN_GPU_TEMPLATE( fillInt4Test ); + + RUN_ALL( stopwatchTest ); + RUN_ALL( memCpyTest ); +// RUN_GPU( kernelTest ); + RUN_GPU_TEMPLATE( scanTest ); + RUN_GPU_TEMPLATE( radixSortSimpleTest ); + + RUN_GPU_TEMPLATE( radixSortStandardTest ); + + RUN_GPU_TEMPLATE( radixSort32Test ); + +// RUN_GPU_TEMPLATE( boundSearchTest ); + RUN_GPU_TEMPLATE( Copy1F4Test ); + RUN_GPU_TEMPLATE( Copy2F4Test ); + RUN_GPU_TEMPLATE( Copy4F4Test ); + } + + DeviceUtils::deallocate( ddcl ); + DeviceUtils::deallocate( ddhost ); +#if defined(ADL_ENABLE_DX11) + DeviceUtils::deallocate( dddx ); +#endif + + printf("=========\n%d Passed\n%d Failed\n", g_nPassed, g_nFailed); + + +} \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/main.cpp new file mode 100644 index 000000000..2f9eaa16b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/main.cpp @@ -0,0 +1,118 @@ +/* +Copyright (c) 2012 Advanced Micro Devices, Inc. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +//Originally written by Takahiro Harada + + +#include + +#include +#include + +#include "UnitTests.h" +#include "RadixSortBenchmark.h" +#include "LaunchOverheadBenchmark.h" + + +#undef NUM_TESTS + + +struct ConstBuffer +{ + float4 m_a; + float4 m_b; + float4 m_c; +}; + +int main() +{ + if(0) + { // radix sort test + Device* deviceHost; + Device* deviceGPU; + { + DeviceUtils::Config cfg; + + // Choose AMD or NVidia +#ifdef CL_PLATFORM_AMD + cfg.m_vendor = DeviceUtils::Config::VD_AMD; +#endif + +#ifdef CL_PLATFORM_INTEL + cfg.m_vendor = DeviceUtils::Config::VD_INTEL; +#endif + +#ifdef CL_PLATFORM_NVIDIA + cfg.m_vendor = adl::DeviceUtils::Config::VD_NV; +#endif + deviceGPU = DeviceUtils::allocate( TYPE_DX11, cfg ); + deviceHost = DeviceUtils::allocate( TYPE_HOST, cfg ); + } + + { + int maxSize = 512*20; + int size = maxSize; + + HostBuffer buf0( deviceHost, maxSize ); + HostBuffer buf1( deviceHost, maxSize ); + Buffer buf2( deviceGPU, maxSize ); + + RadixSort::Data* dataH = RadixSort::allocate( deviceHost, maxSize, RadixSortBase::SORT_STANDARD ); + RadixSort::Data* dataC = RadixSort::allocate( deviceGPU, maxSize, RadixSortBase::SORT_ADVANCED ); + + { + size = NEXTMULTIPLEOF( size, 512 ); + + for(int i=0; i::execute( dataH, buf0, size ); + RadixSort::execute( dataC, buf2, size ); + + buf2.read( buf1.m_ptr, size ); + DeviceUtils::waitForCompletion( deviceGPU ); + for(int i=0; i::deallocate( dataH ); + RadixSort::deallocate( dataC ); + } + + DeviceUtils::deallocate( deviceHost ); + DeviceUtils::deallocate( deviceGPU ); + } + + if(0) + { + launchOverheadBenchmark(); + } + + if(0) + { + radixSortBenchmark(); + } + + if(0) + { + radixSortBenchmark(); + } + + if(1) + { + runAllTest(); + } + printf("End, press \n"); + getchar(); +} + diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/premake4.lua new file mode 100644 index 000000000..2c16f4ba4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/AdlTest/premake4.lua @@ -0,0 +1,4 @@ + +include "AMD" +include "NVIDIA" +include "Intel" \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/AMD/premake4.lua new file mode 100644 index 000000000..370403738 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/AMD/premake4.lua @@ -0,0 +1,29 @@ + + hasCL = findOpenCL_AMD() + hasDX11 = findDirectX11() + + if (hasCL) then + + project "OpenCL_DX11_radixsort_benchmark_AMD" + + initOpenCL_AMD() + + if (hasDX11) then + initDirectX11() + end + + language "C++" + + kind "ConsoleApp" + targetdir "../../../../bin" + includedirs {"..","../.."} + + links { + "OpenCL" + } + + files { + "../test_large_problem_sorting.cpp" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/NVIDIA/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/NVIDIA/premake4.lua new file mode 100644 index 000000000..b959d13fc --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/NVIDIA/premake4.lua @@ -0,0 +1,29 @@ + + hasCL = findOpenCL_NVIDIA() + hasDX11 = findDirectX11() + + if (hasCL) then + + project "OpenCL_DX11_radixsort_benchmark_NVIDIA" + + initOpenCL_NVIDIA() + + if (hasDX11) then + initDirectX11() + end + + language "C++" + + kind "ConsoleApp" + targetdir "../../../../bin" + includedirs {"..","../.."} + + links { + "OpenCL" + } + + files { + "../test_large_problem_sorting.cpp" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/premake4.lua new file mode 100644 index 000000000..e3cf35221 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/premake4.lua @@ -0,0 +1,2 @@ +include "AMD" +include "NVIDIA" diff --git a/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/test_large_problem_sorting.cpp b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/test_large_problem_sorting.cpp new file mode 100644 index 000000000..b1673012d --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/primitives/benchmark/test_large_problem_sorting.cpp @@ -0,0 +1,705 @@ +/****************************************************************************** + * Copyright 2010 Duane Merrill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * + * + * AUTHORS' REQUEST: + * + * If you use|reference|benchmark this code, please cite our Technical + * Report (http://www.cs.virginia.edu/~dgm4d/papers/RadixSortTR.pdf): + * + * @TechReport{ Merrill:Sorting:2010, + * author = "Duane Merrill and Andrew Grimshaw", + * title = "Revisiting Sorting for GPGPU Stream Architectures", + * year = "2010", + * institution = "University of Virginia, Department of Computer Science", + * address = "Charlottesville, VA, USA", + * number = "CS2010-03" + * } + * + * For more information, see our Google Code project site: + * http://code.google.com/p/back40computing/ + * + * Thanks! + ******************************************************************************/ + +/****************************************************************************** + * Simple test driver program for *large-problem* radix sorting. + * + * Useful for demonstrating how to integrate radix sorting into + * your application + ******************************************************************************/ + +/****************************************************************************** + * Converted from CUDA to OpenCL/DirectCompute by Erwin Coumans + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#define BUFFERSIZE_WORKAROUND + +//#include +#include +/********************** +* +*/ + +#include "Adl/Adl.h" +#include "AdlPrimitives/Sort/RadixSort32.h" +#include "AdlPrimitives/Sort/SortData.h" + +using namespace adl; + + +/*********************** +* +*/ + +bool g_verbose; + + +/****************************************************************************** + * Routines + ******************************************************************************/ + + +/** + * Keys-only sorting. Uses the GPU to sort the specified vector of elements for the given + * number of iterations, displaying runtime information. + * + * @param[in] num_elements + * Size in elements of the vector to sort + * @param[in] h_keys + * Vector of keys to sort + * @param[in] iterations + * Number of times to invoke the GPU sorting primitive + * @param[in] cfg + * Config + */ +template +void TimedSort( + unsigned int num_elements, + K *h_keys, + unsigned int iterations, const DeviceUtils::Config& cfg) +{ + std::string sType = "No type selected"; + + if (type == TYPE_CL) + sType = "OpenCL"; + else if (type == TYPE_DX11) + sType = "DX11"; + + printf("Keys-only, %s, %d iterations, %d elements\n", sType.c_str(), iterations, num_elements); + + int max_elements = num_elements; + +#ifdef BUFFERSIZE_WORKAROUND + if (max_elements < 1024*256) + max_elements = 1024*256; +#endif + + // Allocate device storage + Device* deviceData = NULL; + + if ( type == TYPE_CL ) + deviceData = new DeviceCL(); +#ifdef ADL_ENABLE_DX11 + else if ( type == TYPE_DX11 ) + deviceData = new DeviceDX11(); +#endif //ADL_ENABLE_DX11 + + deviceData->initialize(cfg); + + RadixSort32::Data* planData = RadixSort32::allocate( deviceData, max_elements); + + { + Buffer keysInOut(deviceData,max_elements); + + // Create sorting enactor + keysInOut.write(h_keys,num_elements); + DeviceUtils::waitForCompletion( deviceData); + + RadixSort32::execute( planData,keysInOut,num_elements, 32); + DeviceUtils::waitForCompletion( deviceData); + + // Perform the timed number of sorting iterations + double elapsed = 0; + float duration = 0; + StopwatchHost watch; + watch.init(deviceData); + + watch.start(); + + for (int i = 0; i < iterations; i++) + { + + // Move a fresh copy of the problem into device storage + keysInOut.write(h_keys,num_elements); + DeviceUtils::waitForCompletion( deviceData); + + // Start GPU timing record + watch.start(); + + // Call the sorting API routine + RadixSort32::execute( planData,keysInOut,num_elements, 32); + DeviceUtils::waitForCompletion( deviceData); + + watch.stop(); + duration = watch.getMs(); + + // End GPU timing record + elapsed += (double) duration; + } + + // Display timing information + double avg_runtime = elapsed / iterations; + // double throughput = ((double) num_elements) / avg_runtime / 1000.0 / 1000.0; + // printf(", %f GPU ms, %f x10^9 elts/sec\n", avg_runtime, throughput); + double throughput = ((double) num_elements) / avg_runtime / 1000.0 ; + printf(", %f GPU ms, %f x10^6 elts/sec\n", avg_runtime, throughput); + + // Copy out data + keysInOut.read(h_keys,num_elements); + + DeviceUtils::waitForCompletion( deviceData); + + } + // Free allocated memory + RadixSort32::deallocate( planData); + delete deviceData; + // Clean up events +} + +/** + * Key-value sorting. Uses the GPU to sort the specified vector of elements for the given + * number of iterations, displaying runtime information. + * + * @param[in] num_elements + * Size in elements of the vector to sort + * @param[in] h_keys + * Vector of keys to sort + * @param[in,out] h_values + * Vector of values to sort + * @param[in] iterations + * Number of times to invoke the GPU sorting primitive + * @param[in] cfg + * Config + */ +template +void TimedSort( + unsigned int num_elements, + K *h_keys, + V *h_values, + unsigned int iterations, const DeviceUtils::Config& cfg) +{ + std::string sType = "No type selected"; + + if (type == TYPE_CL) + sType = "OpenCL"; + else if (type == TYPE_DX11) + sType = "DX11"; + + printf("Key-values, %s, %d iterations, %d elements\n", sType.c_str(), iterations, num_elements); + + int max_elements = num_elements; + +#ifdef BUFFERSIZE_WORKAROUND + if (max_elements < 1024*256) + max_elements = 1024*256; +#endif + + // Allocate device storage + Device* deviceData = NULL; + + if ( type == TYPE_CL ) + deviceData = new DeviceCL(); +#ifdef ADL_ENABLE_DX11 + else if ( type == TYPE_DX11 ) + deviceData = new DeviceDX11(); +#endif //ADL_ENABLE_DX11 + + deviceData->initialize(cfg); + RadixSort32::Data* planData = RadixSort32::allocate( deviceData, max_elements); + { + Buffer keysIn(deviceData,max_elements); + Buffer valuesIn(deviceData,max_elements); + + Buffer keysOut(deviceData,max_elements); + Buffer valuesOut(deviceData,max_elements); + + //printf("Key-values, %d iterations, %d elements", iterations, num_elements); + + // Create sorting enactor + keysIn.write(h_keys,num_elements); + DeviceUtils::waitForCompletion( deviceData); + valuesIn.write(h_values,num_elements); + DeviceUtils::waitForCompletion( deviceData); + + + // Perform a single sorting iteration to allocate memory, prime code caches, etc. + //RadixSort::execute( planData, buffer, num_elements ); + + //RadixSort32::execute( planData, keysIn,keysOut, valuesIn,valuesOut, num_elements, 32); + RadixSort32::execute( planData, keysIn,keysOut, valuesIn,valuesOut, num_elements, 32); + DeviceUtils::waitForCompletion( deviceData); + + // Perform the timed number of sorting iterations + double elapsed = 0; + float duration = 0; + StopwatchHost watch; + watch.init(deviceData); + + watch.start(); + + for (int i = 0; i < iterations; i++) + { + + // Move a fresh copy of the problem into device storage + keysIn.write(h_keys,num_elements); + valuesIn.write(h_values,num_elements); + + DeviceUtils::waitForCompletion( deviceData); + + // Start GPU timing record + watch.start(); + + // Call the sorting API routine + + RadixSort32::execute( planData, keysIn,keysOut, valuesIn,valuesOut, num_elements, 32); + + DeviceUtils::waitForCompletion( deviceData); + + watch.stop(); + duration = watch.getMs(); + + // End GPU timing record + elapsed += (double) duration; + } + + // Display timing information + double avg_runtime = elapsed / iterations; + // double throughput = ((double) num_elements) / avg_runtime / 1000.0 / 1000.0; + // printf(", %f GPU ms, %f x10^9 elts/sec\n", avg_runtime, throughput); + double throughput = ((double) num_elements) / avg_runtime / 1000.0 ; + printf(", %f GPU ms, %f x10^6 elts/sec\n", avg_runtime, throughput); + + //memset(h_keys,1,num_elements); + //memset(h_values,1,num_elements); + // Copy out data + keysOut.read(h_keys,num_elements); + valuesOut.read(h_values,num_elements); + + DeviceUtils::waitForCompletion( deviceData); + } + + // Free allocated memory + RadixSort32::deallocate( planData); + delete deviceData; + // Clean up events + +} + + + +/** + * Generates random 32-bit keys. + * + * We always take the second-order byte from rand() because the higher-order + * bits returned by rand() are commonly considered more uniformly distributed + * than the lower-order bits. + * + * We can decrease the entropy level of keys by adopting the technique + * of Thearling and Smith in which keys are computed from the bitwise AND of + * multiple random samples: + * + * entropy_reduction | Effectively-unique bits per key + * ----------------------------------------------------- + * -1 | 0 + * 0 | 32 + * 1 | 25.95 + * 2 | 17.41 + * 3 | 10.78 + * 4 | 6.42 + * ... | ... + * + */ +template +void RandomBits(K &key, int entropy_reduction = 0, int lower_key_bits = sizeof(K) * 8) +{ + const unsigned int NUM_UCHARS = (sizeof(K) + sizeof(unsigned char) - 1) / sizeof(unsigned char); + unsigned char key_bits[NUM_UCHARS]; + + do { + + for (int j = 0; j < NUM_UCHARS; j++) { + unsigned char quarterword = 0xff; + for (int i = 0; i <= entropy_reduction; i++) { + quarterword &= (rand() >> 7); + } + key_bits[j] = quarterword; + } + + if (lower_key_bits < sizeof(K) * 8) { + unsigned long long base = 0; + memcpy(&base, key_bits, sizeof(K)); + base &= (1 << lower_key_bits) - 1; + memcpy(key_bits, &base, sizeof(K)); + } + + memcpy(&key, key_bits, sizeof(K)); + + } while (key != key); // avoids NaNs when generating random floating point numbers +} + + +/****************************************************************************** + * Templated routines for printing keys/values to the console + ******************************************************************************/ + +template +void PrintValue(T val) { + printf("%d", val); +} + +template<> +void PrintValue(float val) { + printf("%f", val); +} + +template<> +void PrintValue(double val) { + printf("%f", val); +} + +template<> +void PrintValue(unsigned char val) { + printf("%u", val); +} + +template<> +void PrintValue(unsigned short val) { + printf("%u", val); +} + +template<> +void PrintValue(unsigned int val) { + printf("%u", val); +} + +template<> +void PrintValue(long val) { + printf("%ld", val); +} + +template<> +void PrintValue(unsigned long val) { + printf("%lu", val); +} + +template<> +void PrintValue(long long val) { + printf("%lld", val); +} + +template<> +void PrintValue(unsigned long long val) { + printf("%llu", val); +} + + + +/** + * Compares the equivalence of two arrays + */ +template +int CompareResults(T* computed, T* reference, SizeT len, bool verbose = true) +{ + printf("\n"); + for (SizeT i = 0; i < len; i++) { + + if (computed[i] != reference[i]) { + printf("INCORRECT: [%lu]: ", (unsigned long) i); + PrintValue(computed[i]); + printf(" != "); + PrintValue(reference[i]); + + if (verbose) { + printf("\nresult[..."); + for (size_t j = (i >= 5) ? i - 5 : 0; (j < i + 5) && (j < len); j++) { + PrintValue(computed[j]); + printf(", "); + } + printf("...]"); + printf("\nreference[..."); + for (size_t j = (i >= 5) ? i - 5 : 0; (j < i + 5) && (j < len); j++) { + PrintValue(reference[j]); + printf(", "); + } + printf("...]"); + } + + return 1; + } + } + + printf("CORRECT\n"); + return 0; +} + +/** + * Creates an example sorting problem whose keys is a vector of the specified + * number of K elements, values of V elements, and then dispatches the problem + * to the GPU for the given number of iterations, displaying runtime information. + * + * @param[in] iterations + * Number of times to invoke the GPU sorting primitive + * @param[in] num_elements + * Size in elements of the vector to sort + * @param[in] cfg + * Config + */ +template +void TestSort( + unsigned int iterations, + int num_elements, + bool keys_only, const DeviceUtils::Config& cfg) +{ + // Allocate the sorting problem on the host and fill the keys with random bytes + + K *h_keys = NULL; + K *h_reference_keys = NULL; + V *h_values = NULL; + h_keys = (K*) malloc(num_elements * sizeof(K)); + h_reference_keys = (K*) malloc(num_elements * sizeof(K)); + if (!keys_only) h_values = (V*) malloc(num_elements * sizeof(V)); + + + // Use random bits + for (unsigned int i = 0; i < num_elements; ++i) { + RandomBits(h_keys[i], 0); + //h_keys[i] = 0xffffffffu-i; + if (!keys_only) + h_values[i] = h_keys[i];//0xffffffffu-i; + + h_reference_keys[i] = h_keys[i]; + } + + // Run the timing test + if (keys_only) { + TimedSort(num_elements, h_keys, iterations, cfg); + } else { + TimedSort(num_elements, h_keys, h_values, iterations, cfg); + } + +// cudaThreadSynchronize(); + + // Display sorted key data + if (g_verbose) { + printf("\n\nKeys:\n"); + for (int i = 0; i < num_elements; i++) { + PrintValue(h_keys[i]); + printf(", "); + } + printf("\n\n"); + } + + // Verify solution + std::sort(h_reference_keys, h_reference_keys + num_elements); + CompareResults(h_keys, h_reference_keys, num_elements, true); + printf("\n"); + fflush(stdout); + + // Free our allocated host memory + if (h_keys != NULL) free(h_keys); + if (h_values != NULL) free(h_values); +} + + + +/** + * Displays the commandline usage for this tool + */ +void Usage() +{ + printf("\ntest_large_problem_sorting [--device=] [--v] [--i=] [--n=] [--keys-only]\n"); + printf("\n"); + printf("\t--v\tDisplays sorted results to the console.\n"); + printf("\n"); + printf("\t--i\tPerforms the sorting operation times\n"); + printf("\t\t\ton the device. Re-copies original input each time. Default = 1\n"); + printf("\n"); + printf("\t--n\tThe number of elements to comprise the sample problem\n"); + printf("\t\t\tDefault = 512\n"); + printf("\n"); + printf("\t--keys-only\tSpecifies that keys are not accommodated by value pairings\n"); + printf("\n"); +} + + +/****************************************************************************** + * Command-line parsing + ******************************************************************************/ +#include +#include +#include + +class CommandLineArgs +{ +protected: + + std::map pairs; + +public: + + // Constructor + CommandLineArgs(int argc, char **argv) + { + using namespace std; + + for (int i = 1; i < argc; i++) + { + string arg = argv[i]; + + if ((arg[0] != '-') || (arg[1] != '-')) { + continue; + } + + string::size_type pos; + string key, val; + if ((pos = arg.find( '=')) == string::npos) { + key = string(arg, 2, arg.length() - 2); + val = ""; + } else { + key = string(arg, 2, pos - 2); + val = string(arg, pos + 1, arg.length() - 1); + } + pairs[key] = val; + } + } + + bool CheckCmdLineFlag(const char* arg_name) + { + using namespace std; + map::iterator itr; + if ((itr = pairs.find(arg_name)) != pairs.end()) { + return true; + } + return false; + } + + template + void GetCmdLineArgument(const char *arg_name, T &val); + + int ParsedArgc() + { + return pairs.size(); + } +}; + +template +void CommandLineArgs::GetCmdLineArgument(const char *arg_name, T &val) +{ + using namespace std; + map::iterator itr; + if ((itr = pairs.find(arg_name)) != pairs.end()) { + istringstream strstream(itr->second); + strstream >> val; + } +} + +template <> +void CommandLineArgs::GetCmdLineArgument(const char* arg_name, char* &val) +{ + using namespace std; + map::iterator itr; + if ((itr = pairs.find(arg_name)) != pairs.end()) { + + string s = itr->second; + val = (char*) malloc(sizeof(char) * (s.length() + 1)); + strcpy(val, s.c_str()); + + } else { + val = NULL; + } +} + + + + + +/****************************************************************************** + * Main + ******************************************************************************/ + +int main( int argc, char** argv) +{ + + //srand(time(NULL)); + srand(0); // presently deterministic + + unsigned int num_elements = 1024*1024*12;//16*1024;//8*524288;//2048;//512;//524288; + unsigned int iterations = 10; + bool keys_only; + + // + // Check command line arguments + // + + CommandLineArgs args(argc,argv); + + if (args.CheckCmdLineFlag("help")) + { + Usage(); + return 0; + } + + args.GetCmdLineArgument("i", iterations); + args.GetCmdLineArgument("n", num_elements); + keys_only = args.CheckCmdLineFlag("keys-only"); + g_verbose = args.CheckCmdLineFlag("v"); + + DeviceUtils::Config cfg; + + // Choose AMD or NVidia +#ifdef CL_PLATFORM_AMD + cfg.m_vendor = DeviceUtils::Config::VD_AMD; +#endif + +#ifdef CL_PLATFORM_NVIDIA + cfg.m_vendor = DeviceUtils::Config::VD_NV; +#endif + + TestSort( + iterations, + num_elements, + keys_only, cfg); + +#ifdef ADL_ENABLE_DX11 + TestSort( + iterations, + num_elements, + keys_only, cfg); +#endif //ADL_ENABLE_DX11 +} + + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/AMD/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/vector_add/AMD/premake4.lua new file mode 100644 index 000000000..4b5e39b9b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/AMD/premake4.lua @@ -0,0 +1,21 @@ + + hasCL = findOpenCL_AMD() + + if (hasCL) then + + project "OpenCL_VectorAdd_AMD" + + initOpenCL_AMD() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + + files { + "../main.cpp", + "../../basic_initialize/btOpenCLUtils.cpp", + "../../basic_initialize/btOpenCLUtils.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/Intel/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/vector_add/Intel/premake4.lua new file mode 100644 index 000000000..0f1be986b --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/Intel/premake4.lua @@ -0,0 +1,23 @@ + + hasCL = findOpenCL_Intel() + + if (hasCL) then + + project "OpenCL_intialize_Intel" + + initOpenCL_Intel() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + +-- includedirs {"..","../../../../include/gpu_research"} + + files { + "../main.cpp", + "../btOpenCLUtils.cpp", + "../btOpenCLUtils.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/NVIDIA/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/vector_add/NVIDIA/premake4.lua new file mode 100644 index 000000000..e7c1d156f --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/NVIDIA/premake4.lua @@ -0,0 +1,23 @@ + + hasCL = findOpenCL_NVIDIA() + + if (hasCL) then + + project "OpenCL_intialize_NVIDIA" + + initOpenCL_NVIDIA() + + language "C++" + + kind "ConsoleApp" + targetdir "../../../bin" + +-- includedirs {"..","../../../../include/gpu_research"} + + files { + "../main.cpp", + "../btOpenCLUtils.cpp", + "../btOpenCLUtils.h" + } + + end \ No newline at end of file diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.cl b/Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.cl new file mode 100644 index 000000000..2ff17826a --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.cl @@ -0,0 +1,16 @@ + + +__kernel void VectorAdd(__global const float8* a, __global const float8* b, __global float8* c, int numElements) +{ + // get oct-float index into global data array + int iGID = get_global_id(0); + if (iGID>=numElements) + return; + + float8 aGID = a[iGID]; + float8 bGID = b[iGID]; + + float8 result = aGID + bGID; + // write back out to GMEM + c[iGID] = result; +} diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.h b/Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.h new file mode 100644 index 000000000..fb7e2fc21 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/VectorAddKernels.h @@ -0,0 +1,18 @@ +static const char* vectorAddCL= \ +"\n" +"\n" +"__kernel void VectorAdd(__global const float8* a, __global const float8* b, __global float8* c, int numElements)\n" +"{\n" +" // get oct-float index into global data array\n" +" int iGID = get_global_id(0);\n" +" if (iGID>=numElements)\n" +" return;\n" +"\n" +" float8 aGID = a[iGID];\n" +" float8 bGID = b[iGID];\n" +"\n" +" float8 result = aGID + bGID;\n" +" // write back out to GMEM\n" +" c[iGID] = result;\n" +"}\n" +; diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/main.cpp b/Extras/RigidBodyGpuPipeline/opencl/vector_add/main.cpp new file mode 100644 index 000000000..54afa4ee8 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/main.cpp @@ -0,0 +1,367 @@ + +///VectorAdd sample, from the NVidia JumpStart Guide +///http://developer.download.nvidia.com/OpenCL/NVIDIA_OpenCL_JumpStart_Guide.pdf + +///Instead of #include we include +///Apart from this include file, all other code should compile and work on OpenCL compliant implementation + + +//#define LOAD_FROM_FILE + +#ifdef __APPLE__ + #include +#else + #include +#endif //__APPLE__ + +#include +#include +#include +#include + +#define GRID3DOCL_CHECKERROR(a, b) if((a)!=(b)) { printf("3D GRID OCL Error : %d\n", (a)); btAssert((a) == (b)); } +size_t wgSize; + +#include "VectorAddKernels.h" + + + +char* loadProgSource(const char* cFilename, const char* cPreamble, size_t* szFinalLength) +{ + // locals + FILE* pFileStream = NULL; + size_t szSourceLength; + + // open the OpenCL source code file + pFileStream = fopen(cFilename, "rb"); + if(pFileStream == 0) + { + return NULL; + } + + size_t szPreambleLength = strlen(cPreamble); + + // get the length of the source code + fseek(pFileStream, 0, SEEK_END); + szSourceLength = ftell(pFileStream); + fseek(pFileStream, 0, SEEK_SET); + + // allocate a buffer for the source code string and read it in + char* cSourceString = (char *)malloc(szSourceLength + szPreambleLength + 1); + memcpy(cSourceString, cPreamble, szPreambleLength); + fread((cSourceString) + szPreambleLength, szSourceLength, 1, pFileStream); + + // close the file and return the total length of the combined (preamble + source) string + fclose(pFileStream); + if(szFinalLength != 0) + { + *szFinalLength = szSourceLength + szPreambleLength; + } + cSourceString[szSourceLength + szPreambleLength] = '\0'; + + return cSourceString; +} + +size_t workitem_size[3]; + +void printDevInfo(cl_device_id device) +{ + char device_string[1024]; + + clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(device_string), &device_string, NULL); + printf( " Device %s:\n", device_string); + + // CL_DEVICE_INFO + cl_device_type type; + clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(type), &type, NULL); + if( type & CL_DEVICE_TYPE_CPU ) + printf(" CL_DEVICE_TYPE:\t\t%s\n", "CL_DEVICE_TYPE_CPU"); + if( type & CL_DEVICE_TYPE_GPU ) + printf( " CL_DEVICE_TYPE:\t\t%s\n", "CL_DEVICE_TYPE_GPU"); + if( type & CL_DEVICE_TYPE_ACCELERATOR ) + printf( " CL_DEVICE_TYPE:\t\t%s\n", "CL_DEVICE_TYPE_ACCELERATOR"); + if( type & CL_DEVICE_TYPE_DEFAULT ) + printf( " CL_DEVICE_TYPE:\t\t%s\n", "CL_DEVICE_TYPE_DEFAULT"); + + // CL_DEVICE_MAX_COMPUTE_UNITS + cl_uint compute_units; + clGetDeviceInfo(device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(compute_units), &compute_units, NULL); + printf( " CL_DEVICE_MAX_COMPUTE_UNITS:\t%d\n", compute_units); + + // CL_DEVICE_MAX_WORK_GROUP_SIZE + + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(workitem_size), &workitem_size, NULL); + printf( " CL_DEVICE_MAX_WORK_ITEM_SIZES:\t%zu / %zu / %zu \n", workitem_size[0], workitem_size[1], workitem_size[2]); + +} + + + + +// Main function +// ********************************************************************* +int main(int argc, char **argv) +{ + void *srcA, *srcB, *dst; // Host buffers for OpenCL test + cl_context cxGPUContext; // OpenCL context + cl_command_queue cqCommandQue; // OpenCL command que + cl_device_id* cdDevices; // OpenCL device list + cl_program cpProgram; // OpenCL program + cl_kernel ckKernel; // OpenCL kernel + cl_mem cmMemObjs[3]; // OpenCL memory buffer objects: 3 for device + size_t szGlobalWorkSize[1]; // 1D var for Total # of work items + size_t szLocalWorkSize[1]; // 1D var for # of work items in the work group + size_t szParmDataBytes; // Byte size of context information + cl_int ciErr1, ciErr2; // Error code var + + + int iTestN = 100000 * 8; // Size of Vectors to process + + int actualGlobalSize = iTestN / 8; + + + // set Global and Local work size dimensions + szGlobalWorkSize[0] = iTestN >> 3; // do 8 computations per work item + szLocalWorkSize[0]= iTestN>>3; + + + // Allocate and initialize host arrays + srcA = (void *)malloc (sizeof(cl_float) * iTestN); + srcB = (void *)malloc (sizeof(cl_float) * iTestN); + dst = (void *)malloc (sizeof(cl_float) * iTestN); + + int i; + + // Initialize arrays with some values + for (i=0;i processing outside of the buffer + //make sure to check kernel + } + + size_t globalThreads[] = {num_t * workgroupSize}; + size_t localThreads[] = {workgroupSize}; + + + localWorkSize[0] = workgroupSize; + globalWorkSize[0] = num_t * workgroupSize; + localWorkSize[1] = 1; + globalWorkSize[1] = 1; + + // Copy input data from host to GPU and launch kernel + ciErr1 |= clEnqueueNDRangeKernel(cqCommandQue, ckKernel, 1, NULL, globalThreads, localThreads, 0, NULL, NULL); + + } + + if (ciErrNum != CL_SUCCESS) + { + printf("cannot clEnqueueNDRangeKernel\n"); + exit(0); + } + + clFinish(cqCommandQue); + // Read back results and check accumulated errors + ciErr1 |= clEnqueueReadBuffer(cqCommandQue, cmMemObjs[2], CL_TRUE, 0, sizeof(cl_float8) * szGlobalWorkSize[0], dst, 0, NULL, NULL); + + // Release kernel, program, and memory objects + // NOTE: Most properly this should be done at any of the exit points above, but it is omitted elsewhere for clarity. + free(cdDevices); + clReleaseKernel(ckKernel); + clReleaseProgram(cpProgram); + clReleaseCommandQueue(cqCommandQue); + clReleaseContext(cxGPUContext); + + + // print the results + int iErrorCount = 0; + for (i = 0; i < iTestN; i++) + { + if (((float*)dst)[i] != ((float*)srcA)[i]+((float*)srcB)[i]) + iErrorCount++; + } + + if (iErrorCount) + { + printf("MiniCL validation FAILED\n"); + } else + { + printf("MiniCL validation SUCCESSFULL\n"); + } + // Free host memory, close log and return success + for (i = 0; i < 3; i++) + { + clReleaseMemObject(cmMemObjs[i]); + } + + free(srcA); + free(srcB); + free (dst); + printf("Press ENTER to quit\n"); + getchar(); +} + + diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/premake4.lua b/Extras/RigidBodyGpuPipeline/opencl/vector_add/premake4.lua new file mode 100644 index 000000000..56a16eed5 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/premake4.lua @@ -0,0 +1,4 @@ + + include "AMD" +-- include "Intel" +-- include "NVIDIA" diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/stringify.py b/Extras/RigidBodyGpuPipeline/opencl/vector_add/stringify.py new file mode 100644 index 000000000..e79e281e4 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/stringify.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import sys +import os +import shutil + +arg = sys.argv[1] +fh = open(arg) + +print 'static const char* '+sys.argv[2]+'= \\' +for line in fh.readlines(): + a = line.strip('\n') + print '"'+a+'\\n"' +print ';' diff --git a/Extras/RigidBodyGpuPipeline/opencl/vector_add/stringifyVectorAddKernel.bat b/Extras/RigidBodyGpuPipeline/opencl/vector_add/stringifyVectorAddKernel.bat new file mode 100644 index 000000000..9b3913db1 --- /dev/null +++ b/Extras/RigidBodyGpuPipeline/opencl/vector_add/stringifyVectorAddKernel.bat @@ -0,0 +1,8 @@ +stringify.py VectorAddKernels.cl vectorAddCL >VectorAddKernels.h + + + + +@echo Warning: +@echo You might still need to find/replace for \\n (due to macros) and replace #include statements by their content +pause