@bloomengine/engine 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +20 -0
- package/native/android/src/lib.rs +1747 -0
- package/native/ios/Cargo.lock +1688 -0
- package/native/ios/Cargo.toml +19 -0
- package/native/ios/src/lib.rs +2258 -0
- package/native/linux/Cargo.lock +1719 -0
- package/native/linux/Cargo.toml +22 -0
- package/native/linux/src/lib.rs +2236 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +29 -0
- package/native/macos/src/lib.rs +3444 -0
- package/native/shared/Cargo.lock +1898 -0
- package/native/shared/Cargo.toml +42 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +77 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +77 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +53 -0
- package/native/shared/shaders/material_abi.wgsl +360 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/audio.rs +363 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +186 -0
- package/native/shared/src/frame_callbacks.rs +88 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +76 -0
- package/native/shared/src/input.rs +273 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +43 -0
- package/native/shared/src/models.rs +1941 -0
- package/native/shared/src/physics_jolt.rs +1528 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +339 -0
- package/native/shared/src/profiler.rs +416 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/formats.rs +778 -0
- package/native/shared/src/renderer/graph.rs +465 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +455 -0
- package/native/shared/src/renderer/material_pipeline.rs +604 -0
- package/native/shared/src/renderer/material_system.rs +2106 -0
- package/native/shared/src/renderer/mod.rs +13923 -0
- package/native/shared/src/renderer/planar_reflection.rs +458 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +134 -0
- package/native/shared/src/renderer/shaders.rs +5855 -0
- package/native/shared/src/renderer/transient.rs +576 -0
- package/native/shared/src/renderer/types.rs +259 -0
- package/native/shared/src/renderer/util.rs +151 -0
- package/native/shared/src/scene.rs +1066 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +551 -0
- package/native/shared/src/staging.rs +90 -0
- package/native/shared/src/string_header.rs +35 -0
- package/native/shared/src/text_renderer.rs +456 -0
- package/native/shared/src/textures.rs +154 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeBuilder.cpp +242 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeBuilder.h +121 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeToBuffer.h +296 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h +323 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h +555 -0
- package/native/third_party/JoltPhysics/Jolt/ConfigurationString.h +112 -0
- package/native/third_party/JoltPhysics/Jolt/Core/ARMNeon.h +94 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Array.h +713 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Atomics.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Core/BinaryHeap.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Core/ByteBuffer.h +74 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Color.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Color.h +98 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Core.h +652 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FPControlWord.h +143 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FPException.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FPFlushDenormals.h +43 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Factory.cpp +92 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Factory.h +54 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FixedSizeFreeList.h +122 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FixedSizeFreeList.inl +215 -0
- package/native/third_party/JoltPhysics/Jolt/Core/HashCombine.h +234 -0
- package/native/third_party/JoltPhysics/Jolt/Core/HashTable.h +876 -0
- package/native/third_party/JoltPhysics/Jolt/Core/InsertionSort.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Core/IssueReporting.cpp +27 -0
- package/native/third_party/JoltPhysics/Jolt/Core/IssueReporting.h +38 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystem.h +311 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystem.inl +56 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemSingleThreaded.cpp +65 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemSingleThreaded.h +62 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemThreadPool.cpp +364 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemThreadPool.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemWithBarrier.cpp +230 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemWithBarrier.h +85 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LinearCurve.cpp +51 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LinearCurve.h +67 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LockFreeHashMap.h +182 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LockFreeHashMap.inl +351 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Memory.cpp +85 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Memory.h +85 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Mutex.h +223 -0
- package/native/third_party/JoltPhysics/Jolt/Core/MutexArray.h +98 -0
- package/native/third_party/JoltPhysics/Jolt/Core/NonCopyable.h +18 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Profiler.cpp +677 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Profiler.h +301 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Profiler.inl +90 -0
- package/native/third_party/JoltPhysics/Jolt/Core/QuickSort.h +137 -0
- package/native/third_party/JoltPhysics/Jolt/Core/RTTI.cpp +149 -0
- package/native/third_party/JoltPhysics/Jolt/Core/RTTI.h +436 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Reference.h +244 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Result.h +174 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLAlignedAllocator.h +72 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLAllocator.h +127 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLLocalAllocator.h +170 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLTempAllocator.h +80 -0
- package/native/third_party/JoltPhysics/Jolt/Core/ScopeExit.h +49 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Semaphore.cpp +135 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Semaphore.h +68 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StaticArray.h +329 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamIn.h +120 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamOut.h +97 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamUtils.h +168 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamWrapper.h +53 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StridedPtr.h +63 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StringTools.cpp +101 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StringTools.h +38 -0
- package/native/third_party/JoltPhysics/Jolt/Core/TempAllocator.h +209 -0
- package/native/third_party/JoltPhysics/Jolt/Core/TickCounter.cpp +37 -0
- package/native/third_party/JoltPhysics/Jolt/Core/TickCounter.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Core/UnorderedMap.h +80 -0
- package/native/third_party/JoltPhysics/Jolt/Core/UnorderedSet.h +32 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/AABox.h +313 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/AABox4.h +224 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ClipPoly.h +200 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ClosestPoint.h +498 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder.cpp +1467 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder.h +276 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder2D.cpp +335 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder2D.h +105 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexSupport.h +188 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/EPAConvexHullBuilder.h +845 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/EPAPenetrationDepth.h +557 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Ellipse.h +77 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/GJKClosestPoint.h +945 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/IndexedTriangle.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Indexify.cpp +222 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Indexify.h +19 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/MortonCode.h +40 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/OrientedBox.cpp +178 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/OrientedBox.h +39 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Plane.h +104 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayAABox.h +241 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayCapsule.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayCylinder.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RaySphere.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayTriangle.h +158 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Sphere.h +72 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Triangle.h +34 -0
- package/native/third_party/JoltPhysics/Jolt/Jolt.cmake +703 -0
- package/native/third_party/JoltPhysics/Jolt/Jolt.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Jolt.natvis +116 -0
- package/native/third_party/JoltPhysics/Jolt/Math/BVec16.h +99 -0
- package/native/third_party/JoltPhysics/Jolt/Math/BVec16.inl +177 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DMat44.h +158 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DMat44.inl +310 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DVec3.h +291 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DVec3.inl +941 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Double3.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DynMatrix.h +31 -0
- package/native/third_party/JoltPhysics/Jolt/Math/EigenValueSymmetric.h +177 -0
- package/native/third_party/JoltPhysics/Jolt/Math/FindRoot.h +42 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Float2.h +36 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Float3.h +50 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Float4.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Math/GaussianElimination.h +102 -0
- package/native/third_party/JoltPhysics/Jolt/Math/HalfFloat.h +208 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Mat44.h +243 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Mat44.inl +952 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Math.h +208 -0
- package/native/third_party/JoltPhysics/Jolt/Math/MathTypes.h +32 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Matrix.h +259 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Quat.h +268 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Quat.inl +406 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Real.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Swizzle.h +19 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Trigonometry.h +79 -0
- package/native/third_party/JoltPhysics/Jolt/Math/UVec4.h +232 -0
- package/native/third_party/JoltPhysics/Jolt/Math/UVec4.inl +636 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec3.cpp +71 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec3.h +308 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec3.inl +942 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec4.h +320 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec4.inl +1152 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vector.h +211 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/GetPrimitiveTypeOfType.h +54 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStream.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStream.h +337 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryIn.cpp +252 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryIn.h +57 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryOut.cpp +165 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryOut.h +57 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamIn.cpp +635 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamIn.h +148 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamOut.cpp +166 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamOut.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextIn.cpp +418 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextIn.h +55 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextOut.cpp +255 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextOut.h +62 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTypes.h +26 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttribute.h +111 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttributeEnum.h +67 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttributeTyped.h +60 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableObject.cpp +15 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableObject.h +170 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/TypeDeclarations.cpp +70 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/TypeDeclarations.h +45 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/AllowedDOFs.h +68 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.cpp +426 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.h +452 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.inl +197 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyAccess.h +68 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyActivationListener.h +28 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyCreationSettings.cpp +234 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyCreationSettings.h +124 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyFilter.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyID.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyInterface.cpp +1099 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyInterface.h +324 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLock.h +111 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLockInterface.h +134 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLockMulti.h +120 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyManager.cpp +1220 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyManager.h +403 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyPair.h +36 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyType.h +19 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MassProperties.cpp +185 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MassProperties.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.cpp +92 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.h +308 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.inl +178 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionQuality.h +31 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionType.h +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/Character.cpp +354 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/Character.h +159 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterBase.cpp +59 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterBase.h +157 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterID.h +98 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterVirtual.cpp +1933 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterVirtual.h +752 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/AABoxCast.h +20 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ActiveEdgeMode.h +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ActiveEdges.h +114 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BackFaceMode.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhase.cpp +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h +109 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp +313 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h +38 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h +148 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h +92 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h +64 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp +629 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h +108 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuery.h +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h +66 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp +1768 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/QuadTree.h +389 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastConvexVsTriangles.cpp +107 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastConvexVsTriangles.h +46 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastResult.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp +223 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastSphereVsTriangles.h +49 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollectFacesMode.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp +155 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideConvexVsTriangles.h +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollidePointResult.h +25 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideShape.h +106 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h +94 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h +110 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h +102 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp +121 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSphereVsTriangles.h +50 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionCollector.h +109 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionCollectorImpl.h +219 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionDispatch.cpp +107 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionDispatch.h +97 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionGroup.cpp +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionGroup.h +97 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ContactListener.h +143 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/EstimateCollisionResponse.cpp +213 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/EstimateCollisionResponse.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilter.cpp +32 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilter.h +46 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilterTable.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilterTable.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h +279 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp +271 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseQuery.cpp +448 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseQuery.h +77 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseStats.cpp +62 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseStats.h +110 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayer.h +111 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h +52 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h +78 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterial.cpp +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterial.h +57 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterialSimple.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterialSimple.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/RayCast.h +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/BoxShape.cpp +318 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/BoxShape.h +115 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CapsuleShape.cpp +438 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CapsuleShape.h +129 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShape.cpp +433 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShape.h +354 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h +461 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp +1311 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexHullShape.h +202 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexShape.cpp +566 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexShape.h +150 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CylinderShape.cpp +418 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CylinderShape.h +126 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/DecoratedShape.cpp +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/DecoratedShape.h +80 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/EmptyShape.cpp +64 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/EmptyShape.h +75 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/GetTrianglesContext.h +248 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp +2754 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/HeightFieldShape.h +380 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MeshShape.cpp +1305 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MeshShape.h +228 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp +596 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MutableCompoundShape.h +176 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp +217 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h +140 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PlaneShape.cpp +541 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PlaneShape.h +147 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PolyhedronSubmergedVolumeCalculator.h +319 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp +333 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h +161 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaleHelpers.h +83 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaledShape.cpp +238 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaledShape.h +145 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/Shape.cpp +325 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/Shape.h +466 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SphereShape.cpp +347 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SphereShape.h +125 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/StaticCompoundShape.cpp +674 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/StaticCompoundShape.h +139 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SubShapeID.h +138 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h +65 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp +453 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.gliffy +1 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h +135 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp +691 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.h +132 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TriangleShape.cpp +430 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TriangleShape.h +143 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ShapeCast.h +173 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ShapeFilter.h +73 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SimShapeFilter.h +40 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SimShapeFilterWrapper.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SortReverseAndStore.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/TransformedShape.cpp +180 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/TransformedShape.h +194 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/CalculateSolverSteps.h +70 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConeConstraint.cpp +246 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConeConstraint.h +133 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/Constraint.cpp +73 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/Constraint.h +243 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintManager.cpp +289 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintManager.h +100 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h +257 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h +682 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h +276 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/GearConstraintPart.h +195 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/HingeRotationConstraintPart.h +222 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/IndependentAxisConstraintPart.h +246 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/PointConstraintPart.h +239 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h +196 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h +283 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h +246 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h +169 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h +597 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ContactConstraintManager.cpp +1804 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ContactConstraintManager.h +524 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/DistanceConstraint.cpp +266 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/DistanceConstraint.h +120 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/FixedConstraint.cpp +215 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/FixedConstraint.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/GearConstraint.cpp +188 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/GearConstraint.h +116 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/HingeConstraint.cpp +443 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/HingeConstraint.h +205 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/MotorSettings.cpp +43 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/MotorSettings.h +66 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraint.cpp +458 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraint.h +191 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPath.cpp +85 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPath.h +76 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp +308 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPathHermite.h +54 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PointConstraint.cpp +157 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PointConstraint.h +94 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PulleyConstraint.cpp +253 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PulleyConstraint.h +137 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp +189 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/RackAndPinionConstraint.h +118 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SixDOFConstraint.cpp +900 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SixDOFConstraint.h +289 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SliderConstraint.cpp +501 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SliderConstraint.h +198 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SpringSettings.cpp +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SpringSettings.h +70 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SwingTwistConstraint.cpp +524 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SwingTwistConstraint.h +197 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/TwoBodyConstraint.cpp +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/TwoBodyConstraint.h +65 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/DeterminismLog.cpp +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/DeterminismLog.h +159 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/EActivation.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/EPhysicsUpdateError.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/IslandBuilder.cpp +492 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/IslandBuilder.h +144 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/LargeIslandSplitter.cpp +582 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/LargeIslandSplitter.h +187 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsLock.h +169 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsScene.cpp +261 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsScene.h +104 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSettings.h +125 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsStepListener.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSystem.cpp +2915 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSystem.h +391 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsUpdateContext.cpp +25 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsUpdateContext.h +176 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Ragdoll/Ragdoll.cpp +744 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Ragdoll/Ragdoll.h +245 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyContactListener.h +55 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyCreationSettings.cpp +128 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h +75 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyManifold.h +74 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp +1501 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h +333 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyShape.cpp +354 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyShape.h +73 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp +1487 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h +390 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h +63 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyVertex.h +36 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorder.h +136 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorderImpl.cpp +90 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorderImpl.h +50 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/MotorcycleController.cpp +306 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/MotorcycleController.h +119 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/TrackedVehicleController.cpp +547 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/TrackedVehicleController.h +169 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleAntiRollBar.cpp +33 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleAntiRollBar.h +33 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp +376 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleCollisionTester.h +146 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleConstraint.cpp +703 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleConstraint.h +252 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleController.cpp +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleController.h +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleDifferential.cpp +81 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleDifferential.h +39 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleEngine.cpp +122 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleEngine.h +93 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTrack.cpp +52 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTrack.h +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTransmission.cpp +159 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTransmission.h +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/Wheel.cpp +93 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/Wheel.h +148 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/WheeledVehicleController.cpp +866 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/WheeledVehicleController.h +205 -0
- package/native/third_party/JoltPhysics/Jolt/RegisterTypes.cpp +204 -0
- package/native/third_party/JoltPhysics/Jolt/RegisterTypes.h +29 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRenderer.cpp +1107 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRenderer.h +383 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererPlayback.cpp +168 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererPlayback.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererRecorder.cpp +158 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererRecorder.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererSimple.cpp +80 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererSimple.h +88 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletalAnimation.cpp +165 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletalAnimation.h +91 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/Skeleton.cpp +82 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/Skeleton.h +72 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonMapper.cpp +237 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonMapper.h +145 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonPose.cpp +87 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonPose.h +82 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitter.cpp +73 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitter.h +84 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterBinning.cpp +139 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterBinning.h +52 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterMean.cpp +43 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterMean.h +28 -0
- package/native/third_party/JoltPhysics/LICENSE +7 -0
- package/native/third_party/JoltPhysics/README.md +173 -0
- package/native/third_party/bloom_jolt/CMakeLists.txt +78 -0
- package/native/third_party/bloom_jolt/include/bloom_jolt.h +519 -0
- package/native/third_party/bloom_jolt/src/bloom_jolt.cpp +1780 -0
- package/native/tvos/Cargo.lock +1692 -0
- package/native/tvos/Cargo.toml +22 -0
- package/native/tvos/src/lib.rs +3179 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1236 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +223 -0
- package/native/watchos/src/ffi_stubs.rs +454 -0
- package/native/watchos/src/lib.rs +1013 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +91 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1656 -0
- package/native/web/Cargo.toml +38 -0
- package/native/web/bloom_glue.js +218 -0
- package/native/web/build.sh +101 -0
- package/native/web/index.html +390 -0
- package/native/web/jolt_bridge.js +1311 -0
- package/native/web/src/lib.rs +2739 -0
- package/native/windows/Cargo.lock +1813 -0
- package/native/windows/Cargo.toml +31 -0
- package/native/windows/src/lib.rs +1933 -0
- package/package.json +558 -0
- package/src/audio/index.ts +151 -0
- package/src/core/colors.ts +56 -0
- package/src/core/index.ts +903 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +102 -0
- package/src/index.ts +158 -0
- package/src/math/index.ts +502 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +859 -0
- package/src/physics/index.ts +1072 -0
- package/src/scene/index.ts +570 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +173 -0
- package/src/world/index.ts +22 -0
- package/src/world/loader.ts +385 -0
- package/src/world/prefab.ts +205 -0
- package/src/world/saver.ts +61 -0
- package/src/world/terrain.ts +254 -0
- package/src/world/types.ts +136 -0
- package/src/world/validate.ts +202 -0
- package/src/world/version.ts +47 -0
|
@@ -0,0 +1,2106 @@
|
|
|
1
|
+
// Material system — the runtime state behind Phase 1c.
|
|
2
|
+
//
|
|
3
|
+
// Owns the compiled `MaterialPipeline`s, the per-frame / per-view /
|
|
4
|
+
// per-material / per-draw uniform buffers, the bind groups wiring
|
|
5
|
+
// them to the ABI layouts, and the per-frame draw command list.
|
|
6
|
+
//
|
|
7
|
+
// The Renderer owns one `MaterialSystem` instance. Games interact via
|
|
8
|
+
// three methods: `compile_material`, `submit_draw`, and (internally)
|
|
9
|
+
// `dispatch` which runs inside the main HDR pass.
|
|
10
|
+
|
|
11
|
+
use wgpu::util::DeviceExt;
|
|
12
|
+
|
|
13
|
+
use super::material_pipeline::{
|
|
14
|
+
MaterialAbiLayouts, MaterialPipeline, MaterialCompileDesc, FragmentProfile,
|
|
15
|
+
Bucket, compile_material, MaterialCompileError,
|
|
16
|
+
};
|
|
17
|
+
use super::types::Vertex3D;
|
|
18
|
+
|
|
19
|
+
// =====================================================================
|
|
20
|
+
// Uniform structs — repr(C), bytemuck-Pod, mirror the WGSL in
|
|
21
|
+
// material_abi.wgsl. Kept local to this module so changes to ABI
|
|
22
|
+
// struct layouts happen in exactly one place per language.
|
|
23
|
+
// =====================================================================
|
|
24
|
+
|
|
25
|
+
#[repr(C)]
|
|
26
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
27
|
+
pub struct PerFrameUniforms {
|
|
28
|
+
pub time: f32,
|
|
29
|
+
pub delta_time: f32,
|
|
30
|
+
pub frame_index: u32,
|
|
31
|
+
pub _pad0: u32,
|
|
32
|
+
pub screen_resolution: [f32; 2],
|
|
33
|
+
pub render_resolution: [f32; 2],
|
|
34
|
+
pub taa_jitter: [f32; 2],
|
|
35
|
+
pub _pad1: [f32; 2],
|
|
36
|
+
/// Global wind: x=dir_x, y=dir_z, z=amplitude, w=frequency.
|
|
37
|
+
pub wind: [f32; 4],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[repr(C)]
|
|
41
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
42
|
+
pub struct PerViewDirLight {
|
|
43
|
+
pub direction: [f32; 4], // xyz + intensity
|
|
44
|
+
pub color: [f32; 4],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#[repr(C)]
|
|
48
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
49
|
+
pub struct PerViewPointLight {
|
|
50
|
+
pub position: [f32; 4],
|
|
51
|
+
pub color: [f32; 4],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[repr(C)]
|
|
55
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
56
|
+
pub struct PerViewUniforms {
|
|
57
|
+
pub view: [[f32; 4]; 4],
|
|
58
|
+
pub proj: [[f32; 4]; 4],
|
|
59
|
+
pub view_proj: [[f32; 4]; 4],
|
|
60
|
+
pub prev_view_proj: [[f32; 4]; 4],
|
|
61
|
+
pub inv_proj: [[f32; 4]; 4],
|
|
62
|
+
pub camera_pos: [f32; 4],
|
|
63
|
+
pub camera_dir: [f32; 4],
|
|
64
|
+
pub ambient: [f32; 4],
|
|
65
|
+
pub fog: [f32; 4],
|
|
66
|
+
pub sun_dir: [f32; 4],
|
|
67
|
+
pub sun_color: [f32; 4],
|
|
68
|
+
pub dir_light_count: [f32; 4],
|
|
69
|
+
pub dir_lights: [PerViewDirLight; 4],
|
|
70
|
+
pub point_light_count: [f32; 4],
|
|
71
|
+
pub point_lights: [PerViewPointLight; 16],
|
|
72
|
+
pub shadow_splits: [f32; 4],
|
|
73
|
+
pub shadow_view: [[f32; 4]; 4],
|
|
74
|
+
pub shadow_cascades: [[[f32; 4]; 4]; 3],
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[repr(C)]
|
|
78
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
79
|
+
pub struct MaterialFactorsUniforms {
|
|
80
|
+
pub metal_rough: [f32; 4],
|
|
81
|
+
pub emissive: [f32; 4],
|
|
82
|
+
pub base_color: [f32; 4],
|
|
83
|
+
/// EN-012 — shading-model selector + foliage transmission tint.
|
|
84
|
+
/// x = shading_model enum (0 = default lit, 1 = foliage,
|
|
85
|
+
/// 2 = subsurface — V2 stub),
|
|
86
|
+
/// yzw = transmission_color (rgb tint for back-lit foliage; ignored
|
|
87
|
+
/// when shading_model == 0).
|
|
88
|
+
pub shading_model: [f32; 4],
|
|
89
|
+
/// EN-012 — foliage shading parameters. Only consumed when
|
|
90
|
+
/// `shading_model.x == 1.0`.
|
|
91
|
+
/// x = transmission_amount (0..1),
|
|
92
|
+
/// y = wrap_factor (0..1),
|
|
93
|
+
/// zw = reserved.
|
|
94
|
+
pub foliage_params: [f32; 4],
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl Default for MaterialFactorsUniforms {
|
|
98
|
+
fn default() -> Self {
|
|
99
|
+
Self {
|
|
100
|
+
metal_rough: [0.0, 1.0, 0.0, 0.0], // non-metal, rough, no MR tex, no cutoff
|
|
101
|
+
emissive: [0.0, 0.0, 0.0, 0.0],
|
|
102
|
+
base_color: [1.0, 1.0, 1.0, 1.0],
|
|
103
|
+
// EN-012 — default lit, white transmission tint. Materials
|
|
104
|
+
// that never call `setMaterialShadingModel` get standard PBR
|
|
105
|
+
// (shading_model.x == 0.0).
|
|
106
|
+
shading_model: [0.0, 1.0, 1.0, 1.0],
|
|
107
|
+
// EN-012 — moderate defaults so a freshly-flagged foliage
|
|
108
|
+
// material (shading_model = 1) looks reasonable before any
|
|
109
|
+
// tuning. Wrap=0.5 + transmission=0.5 gives soft back-face
|
|
110
|
+
// shading + a noticeable halo against the sun.
|
|
111
|
+
foliage_params: [0.5, 0.5, 0.0, 0.0],
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[repr(C)]
|
|
117
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
118
|
+
pub struct PerDrawUniforms {
|
|
119
|
+
pub mvp: [[f32; 4]; 4],
|
|
120
|
+
pub model: [[f32; 4]; 4],
|
|
121
|
+
pub prev_mvp: [[f32; 4]; 4],
|
|
122
|
+
pub model_tint: [f32; 4],
|
|
123
|
+
pub skin_info: [u32; 4],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =====================================================================
|
|
127
|
+
// Handles + draw commands
|
|
128
|
+
// =====================================================================
|
|
129
|
+
|
|
130
|
+
pub type MaterialHandle = u32;
|
|
131
|
+
|
|
132
|
+
pub struct MaterialDrawCommand {
|
|
133
|
+
pub material: MaterialHandle,
|
|
134
|
+
pub mesh_handle: u64, // matches model_gpu_cache keys
|
|
135
|
+
pub mesh_idx: usize, // sub-mesh index within that cached model
|
|
136
|
+
pub draw_slot: usize, // which slot in per_draw_buffers to bind
|
|
137
|
+
/// EN-001 — when set, the engine binds vertex slot 1 to this
|
|
138
|
+
/// instance buffer and emits draw_indexed with `0..count` instances.
|
|
139
|
+
/// `None` means a single-instance draw (the legacy path).
|
|
140
|
+
pub instance: Option<InstanceDrawInfo>,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Reference to an instance buffer for an instanced draw command.
|
|
144
|
+
/// `buffer_handle` is 1-based into `MaterialSystem::instance_buffers`;
|
|
145
|
+
/// `count` is the number of GPU instances to emit. Created via
|
|
146
|
+
/// `MaterialSystem::create_instance_buffer`, consumed by
|
|
147
|
+
/// `submit_draw_instanced`.
|
|
148
|
+
#[derive(Copy, Clone, Debug)]
|
|
149
|
+
pub struct InstanceDrawInfo {
|
|
150
|
+
pub buffer_handle: u32,
|
|
151
|
+
pub count: u32,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// EN-001 — owned wgpu::Buffer + element count for an instance buffer.
|
|
155
|
+
/// Lives in the `MaterialSystem::instance_buffers` registry, indexed
|
|
156
|
+
/// by 1-based handle (0 = invalid).
|
|
157
|
+
pub struct InstanceBuffer {
|
|
158
|
+
pub buffer: wgpu::Buffer,
|
|
159
|
+
pub count: u32,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// EN-014 — owned wgpu::Texture + view + layer-count for a texture
|
|
163
|
+
/// array. Lives in the `MaterialSystem::texture_arrays` registry,
|
|
164
|
+
/// indexed by 1-based handle (0 = invalid). The view is a D2Array
|
|
165
|
+
/// view over all `layer_count` layers (so `textureSample(arr, samp,
|
|
166
|
+
/// uv, layer_idx)` resolves layers 0..layer_count-1).
|
|
167
|
+
pub struct TextureArray {
|
|
168
|
+
pub texture: wgpu::Texture,
|
|
169
|
+
pub view: wgpu::TextureView,
|
|
170
|
+
pub layer_count: u32,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// EN-014 — V1 cap on layers per texture array. The ticket calls this
|
|
174
|
+
/// "enough for any landscape shader" (UE5's typical landscape uses
|
|
175
|
+
/// 4–8 layers; 16 leaves headroom for foliage/debris splats).
|
|
176
|
+
pub const MAX_TEXTURE_ARRAY_LAYERS: u32 = 16;
|
|
177
|
+
|
|
178
|
+
/// EN-014 V2 — texture-array format codes, as exposed to the TS API
|
|
179
|
+
/// via `TEX_ARRAY_FORMAT_*`. Anything unrecognised falls back to sRGB
|
|
180
|
+
/// since albedo is the most common splat layer.
|
|
181
|
+
pub const TEX_ARRAY_FORMAT_SRGB: u32 = 0;
|
|
182
|
+
pub const TEX_ARRAY_FORMAT_LINEAR: u32 = 1;
|
|
183
|
+
|
|
184
|
+
/// Map a TS-side format code to a wgpu format. sRGB suits albedo; linear
|
|
185
|
+
/// is mandatory for normal/MR data textures (sRGB decoding would corrupt
|
|
186
|
+
/// the encoded normals or rough/metal channels).
|
|
187
|
+
pub fn map_texture_array_format(code: u32) -> wgpu::TextureFormat {
|
|
188
|
+
match code {
|
|
189
|
+
TEX_ARRAY_FORMAT_SRGB => wgpu::TextureFormat::Rgba8UnormSrgb,
|
|
190
|
+
TEX_ARRAY_FORMAT_LINEAR => wgpu::TextureFormat::Rgba8Unorm,
|
|
191
|
+
_ => wgpu::TextureFormat::Rgba8UnormSrgb,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =====================================================================
|
|
196
|
+
// The system
|
|
197
|
+
// =====================================================================
|
|
198
|
+
|
|
199
|
+
pub struct MaterialSystem {
|
|
200
|
+
pub layouts: MaterialAbiLayouts,
|
|
201
|
+
|
|
202
|
+
// Compiled pipelines, indexed by MaterialHandle (1-based; 0 = invalid).
|
|
203
|
+
pub pipelines: Vec<Option<MaterialPipeline>>,
|
|
204
|
+
|
|
205
|
+
// Per-frame UBO + bind group (rewritten at the start of every frame).
|
|
206
|
+
pub per_frame_buffer: wgpu::Buffer,
|
|
207
|
+
pub per_frame_bg: wgpu::BindGroup,
|
|
208
|
+
|
|
209
|
+
// Per-view UBO — one for now (single camera). Phase 2 may add more
|
|
210
|
+
// for split-screen / shadow cascades.
|
|
211
|
+
pub per_view_buffer: wgpu::Buffer,
|
|
212
|
+
pub per_view_bg: wgpu::BindGroup,
|
|
213
|
+
|
|
214
|
+
// Default per-material bind group: all white 1×1 textures, default
|
|
215
|
+
// factors, zero-initialised user-params. Materials that don't
|
|
216
|
+
// provide their own share this.
|
|
217
|
+
pub default_per_material_bg: wgpu::BindGroup,
|
|
218
|
+
/// Kept alive so the BG it backs doesn't dangle.
|
|
219
|
+
_default_material_factors_buffer: wgpu::Buffer,
|
|
220
|
+
_default_user_params_buffer: wgpu::Buffer,
|
|
221
|
+
_default_white_tex: wgpu::Texture,
|
|
222
|
+
_default_white_view: wgpu::TextureView,
|
|
223
|
+
_default_sampler: wgpu::Sampler,
|
|
224
|
+
/// EN-011 — 1×1 black texture bound at @group(2) @binding(12) for
|
|
225
|
+
/// materials that don't have a planar reflection probe linked.
|
|
226
|
+
/// Lets shaders unconditionally `textureSample(planar_reflection_tex,
|
|
227
|
+
/// …)` without branching on probe presence.
|
|
228
|
+
_default_black_tex: wgpu::Texture,
|
|
229
|
+
pub default_black_view: wgpu::TextureView,
|
|
230
|
+
/// EN-014 — 1×1×1 transparent-black texture-array stub bound to
|
|
231
|
+
/// bindings 14/15/16 (@group(2)) for materials that don't declare
|
|
232
|
+
/// their own array. Has to be a real D2Array view (not a 2D
|
|
233
|
+
/// texture cast) so the layout's `view_dimension: D2Array`
|
|
234
|
+
/// matches at bind time.
|
|
235
|
+
_default_array_tex: wgpu::Texture,
|
|
236
|
+
pub default_array_view: wgpu::TextureView,
|
|
237
|
+
|
|
238
|
+
/// Phase 5 — per-material `user_params` UBOs. Indexed by
|
|
239
|
+
/// `MaterialHandle - 1`; `None` means the material uses the default
|
|
240
|
+
/// (zero-initialised) bind group. Created lazily on first
|
|
241
|
+
/// `set_user_params` call.
|
|
242
|
+
pub material_params_buffers: Vec<Option<wgpu::Buffer>>,
|
|
243
|
+
pub material_per_material_bgs: Vec<Option<wgpu::BindGroup>>,
|
|
244
|
+
|
|
245
|
+
/// EN-011 — link from material → planar reflection probe handle
|
|
246
|
+
/// (1-based; `None` → default 1×1 black texture at binding 12).
|
|
247
|
+
/// Indexed by `MaterialHandle - 1`. Set via
|
|
248
|
+
/// `Renderer::set_material_reflection_probe`; the per-material
|
|
249
|
+
/// bind group is rebuilt at that call so subsequent draws see
|
|
250
|
+
/// the probe's texture even though the engine repaints the
|
|
251
|
+
/// texture each frame (the wgpu Texture identity stays stable).
|
|
252
|
+
pub material_reflection_probe: Vec<Option<u32>>,
|
|
253
|
+
|
|
254
|
+
// Per-draw UBO pool. Buffers grow 1-by-1 as draws pile up.
|
|
255
|
+
// Each entry is `(PerDraw UBO, bind group binding it + the global
|
|
256
|
+
// joint buffer at binding 1)`.
|
|
257
|
+
pub per_draw_buffers: Vec<wgpu::Buffer>,
|
|
258
|
+
pub per_draw_bgs: Vec<wgpu::BindGroup>,
|
|
259
|
+
|
|
260
|
+
// Phase 4b — group 4 (SceneInputs) bind group. Rebuilt per-frame
|
|
261
|
+
// when any Refractive material is submitted and a scene-colour
|
|
262
|
+
// snapshot is available. `None` means "no material needs this
|
|
263
|
+
// group this frame" — translucent dispatch skips group 4
|
|
264
|
+
// binding entirely.
|
|
265
|
+
pub scene_inputs_bg: Option<wgpu::BindGroup>,
|
|
266
|
+
/// Linear sampler for scene-colour sampling (group 4 binding 1).
|
|
267
|
+
_scene_color_sampler: wgpu::Sampler,
|
|
268
|
+
/// Non-filtering sampler for scene-depth sampling (binding 3).
|
|
269
|
+
_scene_depth_sampler: wgpu::Sampler,
|
|
270
|
+
/// 1×1 default texture for impulse / motion-vectors slots when
|
|
271
|
+
/// no Phase 7 impulse system is wired up yet.
|
|
272
|
+
_scene_stub_tex: wgpu::Texture,
|
|
273
|
+
_scene_stub_view: wgpu::TextureView,
|
|
274
|
+
/// 1×1 stub depth texture — bound to scene_depth_tex in Phase 4b
|
|
275
|
+
/// because the live depth buffer can't be simultaneously sampled
|
|
276
|
+
/// and used as a depth-stencil attachment. Phase 4c will add a
|
|
277
|
+
/// copy-to-sample depth snapshot for shoreline-fade materials.
|
|
278
|
+
_scene_stub_depth: wgpu::Texture,
|
|
279
|
+
_scene_stub_depth_view: wgpu::TextureView,
|
|
280
|
+
|
|
281
|
+
// Frame state — commands split by bucket so the graph can
|
|
282
|
+
// schedule them into the right pass. Phase 4a keeps them in
|
|
283
|
+
// parallel lists; Phase 4b dispatches the translucent lists in
|
|
284
|
+
// their own sub-pass.
|
|
285
|
+
pub commands: Vec<MaterialDrawCommand>, // Bucket::Opaque + Bucket::Cutout
|
|
286
|
+
pub translucent_commands: Vec<MaterialDrawCommand>, // Transparent + Refractive + Additive
|
|
287
|
+
next_draw_slot: usize,
|
|
288
|
+
|
|
289
|
+
/// EN-001 — instance buffers, indexed by InstanceBufferHandle
|
|
290
|
+
/// (1-based; 0 = invalid). Each entry owns a wgpu Buffer + element
|
|
291
|
+
/// count. Created via `create_instance_buffer`, consumed by
|
|
292
|
+
/// `submit_draw_instanced` commands. Slots remain `None` after
|
|
293
|
+
/// `destroy_instance_buffer` so existing handles never collide
|
|
294
|
+
/// with re-issued ones.
|
|
295
|
+
pub instance_buffers: Vec<Option<InstanceBuffer>>,
|
|
296
|
+
|
|
297
|
+
/// EN-014 — texture arrays, indexed by 1-based handle (0 = invalid).
|
|
298
|
+
/// Each entry owns a wgpu D2Array texture + view + layer-count.
|
|
299
|
+
/// Created via `create_texture_array`, linked to a material's
|
|
300
|
+
/// per-material BG via `set_material_texture_array`.
|
|
301
|
+
pub texture_arrays: Vec<Option<TextureArray>>,
|
|
302
|
+
|
|
303
|
+
/// EN-014 — per-material → array-handle link, one slot for each
|
|
304
|
+
/// of the 3 array bindings (0 = albedo, 1 = normal, 2 = MR).
|
|
305
|
+
/// `None` means "use the default 1×1×1 stub array". Indexed by
|
|
306
|
+
/// `MaterialHandle - 1`, parallel to `material_per_material_bgs`
|
|
307
|
+
/// / `material_reflection_probe`.
|
|
308
|
+
pub material_texture_arrays: Vec<[Option<u32>; 3]>,
|
|
309
|
+
|
|
310
|
+
/// EN-012 — per-material `MaterialFactorsUniforms` UBO. `None` means
|
|
311
|
+
/// the material uses the shared `_default_material_factors_buffer`
|
|
312
|
+
/// (uniform default) — lazily allocated on the first
|
|
313
|
+
/// `set_material_shading_model` / `set_material_foliage` call.
|
|
314
|
+
/// Indexed by `MaterialHandle - 1`. The CPU-side mirror in
|
|
315
|
+
/// `material_factors_data` lets us partial-update one field at a
|
|
316
|
+
/// time without losing the others.
|
|
317
|
+
pub material_factors_buffers: Vec<Option<wgpu::Buffer>>,
|
|
318
|
+
pub material_factors_data: Vec<MaterialFactorsUniforms>,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
impl MaterialSystem {
|
|
322
|
+
pub fn new(
|
|
323
|
+
device: &wgpu::Device,
|
|
324
|
+
queue: &wgpu::Queue,
|
|
325
|
+
// Joint-buffer plumbing is per-draw (used by ensure_draw_slot,
|
|
326
|
+
// not at construction time). Kept on the constructor's
|
|
327
|
+
// signature for symmetry with the per-draw path.
|
|
328
|
+
_joint_buffer: &wgpu::Buffer,
|
|
329
|
+
) -> Self {
|
|
330
|
+
let layouts = MaterialAbiLayouts::create(device);
|
|
331
|
+
|
|
332
|
+
// Per-frame UBO.
|
|
333
|
+
let per_frame_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
334
|
+
label: Some("material_per_frame"),
|
|
335
|
+
size: std::mem::size_of::<PerFrameUniforms>() as u64,
|
|
336
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
337
|
+
mapped_at_creation: false,
|
|
338
|
+
});
|
|
339
|
+
let per_frame_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
340
|
+
label: Some("material_per_frame_bg"),
|
|
341
|
+
layout: &layouts.per_frame,
|
|
342
|
+
entries: &[wgpu::BindGroupEntry {
|
|
343
|
+
binding: 0, resource: per_frame_buffer.as_entire_binding(),
|
|
344
|
+
}],
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Default white 1×1 texture + sampler shared across optional PBR bindings.
|
|
348
|
+
let default_white_tex = device.create_texture_with_data(
|
|
349
|
+
queue,
|
|
350
|
+
&wgpu::TextureDescriptor {
|
|
351
|
+
label: Some("material_default_white"),
|
|
352
|
+
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
|
353
|
+
mip_level_count: 1,
|
|
354
|
+
sample_count: 1,
|
|
355
|
+
dimension: wgpu::TextureDimension::D2,
|
|
356
|
+
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
|
357
|
+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
|
358
|
+
view_formats: &[],
|
|
359
|
+
},
|
|
360
|
+
Default::default(),
|
|
361
|
+
&[255, 255, 255, 255],
|
|
362
|
+
);
|
|
363
|
+
let default_white_view = default_white_tex.create_view(&Default::default());
|
|
364
|
+
|
|
365
|
+
// EN-011 — Default 1×1 black HDR texture for the planar
|
|
366
|
+
// reflection slot (binding 12). Format is Rgba16Float to match
|
|
367
|
+
// the actual probe RT so the bind-group layout is identical
|
|
368
|
+
// whether a probe is bound or the default.
|
|
369
|
+
let black_pixel: [u16; 4] = [0, 0, 0, 0];
|
|
370
|
+
let default_black_tex = device.create_texture_with_data(
|
|
371
|
+
queue,
|
|
372
|
+
&wgpu::TextureDescriptor {
|
|
373
|
+
label: Some("material_default_black_reflection"),
|
|
374
|
+
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
|
375
|
+
mip_level_count: 1,
|
|
376
|
+
sample_count: 1,
|
|
377
|
+
dimension: wgpu::TextureDimension::D2,
|
|
378
|
+
format: super::formats::HDR_FORMAT,
|
|
379
|
+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
|
380
|
+
view_formats: &[],
|
|
381
|
+
},
|
|
382
|
+
Default::default(),
|
|
383
|
+
bytemuck::cast_slice(&black_pixel),
|
|
384
|
+
);
|
|
385
|
+
let default_black_view = default_black_tex.create_view(&Default::default());
|
|
386
|
+
|
|
387
|
+
// EN-014 — Default 1×1×1 transparent-black texture-array stub
|
|
388
|
+
// for bindings 14/15/16. Has to be a real D2Array (depth_or_
|
|
389
|
+
// _array_layers = 1, viewed as D2Array) so the bind-group
|
|
390
|
+
// layout's view_dimension matches at bind time. Format is
|
|
391
|
+
// Rgba8Unorm — same as a typical albedo source so a game
|
|
392
|
+
// dropping in albedo data without thinking gets the expected
|
|
393
|
+
// sRGB-vs-linear behaviour. Materials that need linear
|
|
394
|
+
// (normal/MR) typically encode that into the layer they upload.
|
|
395
|
+
let default_array_tex = device.create_texture_with_data(
|
|
396
|
+
queue,
|
|
397
|
+
&wgpu::TextureDescriptor {
|
|
398
|
+
label: Some("material_default_array_stub"),
|
|
399
|
+
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
|
400
|
+
mip_level_count: 1,
|
|
401
|
+
sample_count: 1,
|
|
402
|
+
dimension: wgpu::TextureDimension::D2,
|
|
403
|
+
format: wgpu::TextureFormat::Rgba8Unorm,
|
|
404
|
+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
|
405
|
+
view_formats: &[],
|
|
406
|
+
},
|
|
407
|
+
Default::default(),
|
|
408
|
+
&[0, 0, 0, 0],
|
|
409
|
+
);
|
|
410
|
+
let default_array_view = default_array_tex.create_view(&wgpu::TextureViewDescriptor {
|
|
411
|
+
label: Some("material_default_array_stub_view"),
|
|
412
|
+
dimension: Some(wgpu::TextureViewDimension::D2Array),
|
|
413
|
+
..Default::default()
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
let default_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
417
|
+
label: Some("material_default_samp"),
|
|
418
|
+
address_mode_u: wgpu::AddressMode::Repeat,
|
|
419
|
+
address_mode_v: wgpu::AddressMode::Repeat,
|
|
420
|
+
mag_filter: wgpu::FilterMode::Linear,
|
|
421
|
+
min_filter: wgpu::FilterMode::Linear,
|
|
422
|
+
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
|
423
|
+
..Default::default()
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Per-view UBO — zero init; write the real data each frame in `begin_frame`.
|
|
427
|
+
let per_view_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
428
|
+
label: Some("material_per_view"),
|
|
429
|
+
size: std::mem::size_of::<PerViewUniforms>() as u64,
|
|
430
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
431
|
+
mapped_at_creation: false,
|
|
432
|
+
});
|
|
433
|
+
// We don't have the env / BRDF LUT / shadow textures plumbed through
|
|
434
|
+
// the material-system layout yet — Phase 2 wires them from the
|
|
435
|
+
// existing renderer state. For now the PerView bind group uses the
|
|
436
|
+
// default white texture + sampler for the env / BRDF / shadow slots
|
|
437
|
+
// as placeholders so draws validate.
|
|
438
|
+
let white_samp = &default_sampler;
|
|
439
|
+
let white_view = &default_white_view;
|
|
440
|
+
let cmp_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
441
|
+
label: Some("material_default_cmp_samp"),
|
|
442
|
+
compare: Some(wgpu::CompareFunction::LessEqual),
|
|
443
|
+
..Default::default()
|
|
444
|
+
});
|
|
445
|
+
// PerView layout wants a depth texture for shadow bindings 6-8.
|
|
446
|
+
// Use a 1×1 depth as a stub.
|
|
447
|
+
let stub_depth = device.create_texture(&wgpu::TextureDescriptor {
|
|
448
|
+
label: Some("material_stub_depth"),
|
|
449
|
+
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
|
450
|
+
mip_level_count: 1,
|
|
451
|
+
sample_count: 1,
|
|
452
|
+
dimension: wgpu::TextureDimension::D2,
|
|
453
|
+
format: wgpu::TextureFormat::Depth32Float,
|
|
454
|
+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
455
|
+
view_formats: &[],
|
|
456
|
+
});
|
|
457
|
+
let stub_depth_view = stub_depth.create_view(&Default::default());
|
|
458
|
+
let per_view_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
459
|
+
label: Some("material_per_view_bg"),
|
|
460
|
+
layout: &layouts.per_view,
|
|
461
|
+
entries: &[
|
|
462
|
+
wgpu::BindGroupEntry { binding: 0, resource: per_view_buffer.as_entire_binding() },
|
|
463
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(white_view) },
|
|
464
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(white_samp) },
|
|
465
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(white_view) },
|
|
466
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(white_view) },
|
|
467
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(white_samp) },
|
|
468
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&stub_depth_view) },
|
|
469
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::TextureView(&stub_depth_view) },
|
|
470
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&stub_depth_view) },
|
|
471
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&cmp_sampler) },
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
// The stub_depth texture and cmp_sampler outlive the bind group via
|
|
475
|
+
// wgpu internal Arc; we don't need to hold them in the struct.
|
|
476
|
+
std::mem::forget(stub_depth);
|
|
477
|
+
std::mem::forget(stub_depth_view);
|
|
478
|
+
std::mem::forget(cmp_sampler);
|
|
479
|
+
|
|
480
|
+
// Default MaterialFactors UBO.
|
|
481
|
+
let default_mf = MaterialFactorsUniforms::default();
|
|
482
|
+
let default_material_factors_buffer = device.create_buffer_init(
|
|
483
|
+
&wgpu::util::BufferInitDescriptor {
|
|
484
|
+
label: Some("material_default_factors"),
|
|
485
|
+
contents: bytemuck::bytes_of(&default_mf),
|
|
486
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
487
|
+
},
|
|
488
|
+
);
|
|
489
|
+
// Default user-params UBO — 256 bytes of zeros (ABI §1.4).
|
|
490
|
+
let default_user_params_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
491
|
+
label: Some("material_default_user_params"),
|
|
492
|
+
size: 256,
|
|
493
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
494
|
+
mapped_at_creation: false,
|
|
495
|
+
});
|
|
496
|
+
// Need a SECOND sampler here because wgpu requires distinct sampler
|
|
497
|
+
// handles when binding the same logical sampler to multiple slots
|
|
498
|
+
// within one BG — actually it doesn't, the same handle works fine.
|
|
499
|
+
// We reuse default_sampler for every material-tex sampler slot.
|
|
500
|
+
let default_per_material_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
501
|
+
label: Some("material_default_per_material_bg"),
|
|
502
|
+
layout: &layouts.per_material,
|
|
503
|
+
entries: &[
|
|
504
|
+
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&default_white_view) },
|
|
505
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
506
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&default_white_view) },
|
|
507
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
508
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&default_white_view) },
|
|
509
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
510
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&default_white_view) },
|
|
511
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
512
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&default_white_view) },
|
|
513
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
514
|
+
wgpu::BindGroupEntry { binding: 10, resource: default_material_factors_buffer.as_entire_binding() },
|
|
515
|
+
wgpu::BindGroupEntry { binding: 11, resource: default_user_params_buffer.as_entire_binding() },
|
|
516
|
+
// EN-011 — default black 1×1 reflection texture +
|
|
517
|
+
// shared linear sampler. Replaced per-material when a
|
|
518
|
+
// game calls `set_material_reflection_probe`.
|
|
519
|
+
wgpu::BindGroupEntry { binding: 12, resource: wgpu::BindingResource::TextureView(&default_black_view) },
|
|
520
|
+
wgpu::BindGroupEntry { binding: 13, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
521
|
+
// EN-014 — default 1×1×1 stub array bound to all 3
|
|
522
|
+
// texture-array slots, with the shared default
|
|
523
|
+
// sampler at binding 17. Replaced per-slot when a
|
|
524
|
+
// game calls `set_material_texture_array`.
|
|
525
|
+
wgpu::BindGroupEntry { binding: 14, resource: wgpu::BindingResource::TextureView(&default_array_view) },
|
|
526
|
+
wgpu::BindGroupEntry { binding: 15, resource: wgpu::BindingResource::TextureView(&default_array_view) },
|
|
527
|
+
wgpu::BindGroupEntry { binding: 16, resource: wgpu::BindingResource::TextureView(&default_array_view) },
|
|
528
|
+
wgpu::BindGroupEntry { binding: 17, resource: wgpu::BindingResource::Sampler(&default_sampler) },
|
|
529
|
+
],
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Phase 4b — scene-inputs scratch resources. Samplers are
|
|
533
|
+
// stable across frames; the bind group itself is rebuilt
|
|
534
|
+
// when a scene-colour snapshot becomes available.
|
|
535
|
+
let scene_color_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
536
|
+
label: Some("scene_color_samp"),
|
|
537
|
+
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
|
538
|
+
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
|
539
|
+
mag_filter: wgpu::FilterMode::Linear,
|
|
540
|
+
min_filter: wgpu::FilterMode::Linear,
|
|
541
|
+
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
|
542
|
+
..Default::default()
|
|
543
|
+
});
|
|
544
|
+
let scene_depth_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
545
|
+
label: Some("scene_depth_samp"),
|
|
546
|
+
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
|
547
|
+
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
|
548
|
+
mag_filter: wgpu::FilterMode::Nearest,
|
|
549
|
+
min_filter: wgpu::FilterMode::Nearest,
|
|
550
|
+
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
|
551
|
+
..Default::default()
|
|
552
|
+
});
|
|
553
|
+
// 1×1 black texture as the stub impulse / motion-vector slot
|
|
554
|
+
// until Phase 7 wires real sources.
|
|
555
|
+
let scene_stub_tex = device.create_texture_with_data(
|
|
556
|
+
queue,
|
|
557
|
+
&wgpu::TextureDescriptor {
|
|
558
|
+
label: Some("scene_inputs_stub"),
|
|
559
|
+
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
|
560
|
+
mip_level_count: 1,
|
|
561
|
+
sample_count: 1,
|
|
562
|
+
dimension: wgpu::TextureDimension::D2,
|
|
563
|
+
format: wgpu::TextureFormat::Rgba8Unorm,
|
|
564
|
+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
|
565
|
+
view_formats: &[],
|
|
566
|
+
},
|
|
567
|
+
Default::default(),
|
|
568
|
+
&[0, 0, 0, 0],
|
|
569
|
+
);
|
|
570
|
+
let scene_stub_view = scene_stub_tex.create_view(&Default::default());
|
|
571
|
+
|
|
572
|
+
// Stub depth texture — Depth32Float 1×1, cleared to 1.0 (far).
|
|
573
|
+
let scene_stub_depth = device.create_texture(&wgpu::TextureDescriptor {
|
|
574
|
+
label: Some("scene_depth_stub"),
|
|
575
|
+
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
|
576
|
+
mip_level_count: 1,
|
|
577
|
+
sample_count: 1,
|
|
578
|
+
dimension: wgpu::TextureDimension::D2,
|
|
579
|
+
format: wgpu::TextureFormat::Depth32Float,
|
|
580
|
+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
581
|
+
view_formats: &[],
|
|
582
|
+
});
|
|
583
|
+
let scene_stub_depth_view = scene_stub_depth.create_view(&Default::default());
|
|
584
|
+
|
|
585
|
+
Self {
|
|
586
|
+
layouts,
|
|
587
|
+
pipelines: Vec::new(),
|
|
588
|
+
per_frame_buffer,
|
|
589
|
+
per_frame_bg,
|
|
590
|
+
per_view_buffer,
|
|
591
|
+
per_view_bg,
|
|
592
|
+
default_per_material_bg,
|
|
593
|
+
_default_material_factors_buffer: default_material_factors_buffer,
|
|
594
|
+
_default_user_params_buffer: default_user_params_buffer,
|
|
595
|
+
_default_white_tex: default_white_tex,
|
|
596
|
+
_default_white_view: default_white_view,
|
|
597
|
+
_default_sampler: default_sampler,
|
|
598
|
+
_default_black_tex: default_black_tex,
|
|
599
|
+
default_black_view,
|
|
600
|
+
_default_array_tex: default_array_tex,
|
|
601
|
+
default_array_view,
|
|
602
|
+
material_params_buffers: Vec::new(),
|
|
603
|
+
material_per_material_bgs: Vec::new(),
|
|
604
|
+
material_reflection_probe: Vec::new(),
|
|
605
|
+
per_draw_buffers: Vec::new(),
|
|
606
|
+
per_draw_bgs: Vec::new(),
|
|
607
|
+
scene_inputs_bg: None,
|
|
608
|
+
_scene_color_sampler: scene_color_sampler,
|
|
609
|
+
_scene_depth_sampler: scene_depth_sampler,
|
|
610
|
+
_scene_stub_tex: scene_stub_tex,
|
|
611
|
+
_scene_stub_view: scene_stub_view,
|
|
612
|
+
_scene_stub_depth: scene_stub_depth,
|
|
613
|
+
_scene_stub_depth_view: scene_stub_depth_view,
|
|
614
|
+
commands: Vec::new(),
|
|
615
|
+
translucent_commands: Vec::new(),
|
|
616
|
+
next_draw_slot: 0,
|
|
617
|
+
instance_buffers: Vec::new(),
|
|
618
|
+
texture_arrays: Vec::new(),
|
|
619
|
+
material_texture_arrays: Vec::new(),
|
|
620
|
+
// EN-012 — per-material MaterialFactors UBOs are lazy.
|
|
621
|
+
// Until a material calls `set_shading_model` /
|
|
622
|
+
// `set_foliage`, it shares the default factors buffer.
|
|
623
|
+
material_factors_buffers: Vec::new(),
|
|
624
|
+
material_factors_data: Vec::new(),
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// --- Material registry --------------------------------------------
|
|
629
|
+
|
|
630
|
+
/// Compile a material and return its handle. Handles are 1-based;
|
|
631
|
+
/// 0 is reserved for "invalid material".
|
|
632
|
+
pub fn compile(
|
|
633
|
+
&mut self,
|
|
634
|
+
device: &wgpu::Device,
|
|
635
|
+
wgsl_source: &str,
|
|
636
|
+
profile: FragmentProfile,
|
|
637
|
+
bucket: Bucket,
|
|
638
|
+
reads_scene: bool,
|
|
639
|
+
wants_instancing: bool,
|
|
640
|
+
hdr_format: wgpu::TextureFormat,
|
|
641
|
+
material_format: wgpu::TextureFormat,
|
|
642
|
+
velocity_format: wgpu::TextureFormat,
|
|
643
|
+
albedo_format: wgpu::TextureFormat,
|
|
644
|
+
depth_format: wgpu::TextureFormat,
|
|
645
|
+
) -> Result<MaterialHandle, MaterialCompileError> {
|
|
646
|
+
// Inject the user's WGSL under a synthetic path so the
|
|
647
|
+
// preprocessor can resolve `#include "material_abi.wgsl"` etc.
|
|
648
|
+
let entry_path = "__user_material.wgsl";
|
|
649
|
+
let desc = MaterialCompileDesc {
|
|
650
|
+
label: "user_material",
|
|
651
|
+
entry_path,
|
|
652
|
+
extra_sources: &[(entry_path, wgsl_source)],
|
|
653
|
+
profile,
|
|
654
|
+
bucket,
|
|
655
|
+
reads_scene,
|
|
656
|
+
hdr_format,
|
|
657
|
+
material_format,
|
|
658
|
+
velocity_format,
|
|
659
|
+
albedo_format,
|
|
660
|
+
depth_format,
|
|
661
|
+
vertex_buffers: &[Vertex3D::desc()],
|
|
662
|
+
wants_instancing,
|
|
663
|
+
};
|
|
664
|
+
let pipeline = compile_material(device, &self.layouts, &desc)?;
|
|
665
|
+
self.pipelines.push(Some(pipeline));
|
|
666
|
+
Ok(self.pipelines.len() as MaterialHandle)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// --- Frame lifecycle ----------------------------------------------
|
|
670
|
+
|
|
671
|
+
/// Write the per-frame + per-view UBOs. Safe to call any time
|
|
672
|
+
/// before `dispatch`; callers should call exactly once per frame
|
|
673
|
+
/// to keep `PerFrame.time` accurate. Does NOT clear the commands
|
|
674
|
+
/// list — that's `reset_frame`'s job (called at begin_frame, not
|
|
675
|
+
/// end_frame, so draws submitted during the frame survive).
|
|
676
|
+
pub fn update_frame_uniforms(
|
|
677
|
+
&mut self,
|
|
678
|
+
queue: &wgpu::Queue,
|
|
679
|
+
per_frame: &PerFrameUniforms,
|
|
680
|
+
per_view: &PerViewUniforms,
|
|
681
|
+
) {
|
|
682
|
+
queue.write_buffer(&self.per_frame_buffer, 0, bytemuck::bytes_of(per_frame));
|
|
683
|
+
queue.write_buffer(&self.per_view_buffer, 0, bytemuck::bytes_of(per_view));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/// Reset the per-draw slot cursor. Commands lists are cleared by
|
|
687
|
+
/// the Renderer from its own `begin_frame` so the order of reset
|
|
688
|
+
/// vs. submit is deterministic.
|
|
689
|
+
pub fn reset_draw_slot(&mut self) {
|
|
690
|
+
self.next_draw_slot = 0;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/// Phase 5 — set/replace `user_params` for a specific material. The
|
|
694
|
+
/// next dispatch of this handle binds a per-material BindGroup with
|
|
695
|
+
/// the given bytes uploaded to `@group(2) @binding(11)`. Materials
|
|
696
|
+
/// that never receive a `set_user_params` call keep using the
|
|
697
|
+
/// default zero-initialised UBO.
|
|
698
|
+
///
|
|
699
|
+
/// `params.len()` must be ≤ 256 bytes (ABI §1.4 cap). The buffer
|
|
700
|
+
/// is allocated lazily on first call per handle and reused on
|
|
701
|
+
/// subsequent updates. Pass an empty slice to revert to the default.
|
|
702
|
+
pub fn set_user_params(
|
|
703
|
+
&mut self,
|
|
704
|
+
device: &wgpu::Device,
|
|
705
|
+
queue: &wgpu::Queue,
|
|
706
|
+
handle: MaterialHandle,
|
|
707
|
+
params: &[u8],
|
|
708
|
+
) -> Result<(), &'static str> {
|
|
709
|
+
if handle == 0 { return Err("invalid material handle"); }
|
|
710
|
+
let idx = (handle - 1) as usize;
|
|
711
|
+
if idx >= self.pipelines.len() || self.pipelines[idx].is_none() {
|
|
712
|
+
return Err("material handle not registered");
|
|
713
|
+
}
|
|
714
|
+
if params.len() > 256 {
|
|
715
|
+
return Err("user_params exceeds 256-byte cap");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Grow parallel vectors so handle index is valid.
|
|
719
|
+
while self.material_params_buffers.len() <= idx {
|
|
720
|
+
self.material_params_buffers.push(None);
|
|
721
|
+
self.material_per_material_bgs.push(None);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Reverting to default — drop the per-material entries.
|
|
725
|
+
if params.is_empty() {
|
|
726
|
+
self.material_params_buffers[idx] = None;
|
|
727
|
+
self.material_per_material_bgs[idx] = None;
|
|
728
|
+
return Ok(());
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Allocate the per-material UBO + BG on first set. Padded to 256 B
|
|
732
|
+
// so the ABI cap is reflected in the buffer size and write_buffer
|
|
733
|
+
// never partially fills. EN-011 bindings 12/13 default to the
|
|
734
|
+
// shared 1×1 black reflection texture; if a probe was previously
|
|
735
|
+
// linked via `set_reflection_probe`, the reflection BG will be
|
|
736
|
+
// rebuilt by that path the next time the link changes.
|
|
737
|
+
if self.material_params_buffers[idx].is_none() {
|
|
738
|
+
let buf = device.create_buffer(&wgpu::BufferDescriptor {
|
|
739
|
+
label: Some("material_user_params"),
|
|
740
|
+
size: 256,
|
|
741
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
742
|
+
mapped_at_creation: false,
|
|
743
|
+
});
|
|
744
|
+
// EN-012 — resolve the per-material factors UBO so a
|
|
745
|
+
// material that previously called `set_shading_model` /
|
|
746
|
+
// `set_foliage` keeps its custom factors at binding 10
|
|
747
|
+
// when user_params is later set on it.
|
|
748
|
+
let factors_buf: &wgpu::Buffer = self.material_factors_buffers
|
|
749
|
+
.get(idx)
|
|
750
|
+
.and_then(|b| b.as_ref())
|
|
751
|
+
.unwrap_or(&self._default_material_factors_buffer);
|
|
752
|
+
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
753
|
+
label: Some("material_per_material_bg_user"),
|
|
754
|
+
layout: &self.layouts.per_material,
|
|
755
|
+
entries: &[
|
|
756
|
+
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
757
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
758
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
759
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
760
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
761
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
762
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
763
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
764
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
765
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
766
|
+
wgpu::BindGroupEntry { binding: 10, resource: factors_buf.as_entire_binding() },
|
|
767
|
+
wgpu::BindGroupEntry { binding: 11, resource: buf.as_entire_binding() },
|
|
768
|
+
wgpu::BindGroupEntry { binding: 12, resource: wgpu::BindingResource::TextureView(&self.default_black_view) },
|
|
769
|
+
wgpu::BindGroupEntry { binding: 13, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
770
|
+
// EN-014 — default stub array on all 3 slots.
|
|
771
|
+
wgpu::BindGroupEntry { binding: 14, resource: wgpu::BindingResource::TextureView(&self.default_array_view) },
|
|
772
|
+
wgpu::BindGroupEntry { binding: 15, resource: wgpu::BindingResource::TextureView(&self.default_array_view) },
|
|
773
|
+
wgpu::BindGroupEntry { binding: 16, resource: wgpu::BindingResource::TextureView(&self.default_array_view) },
|
|
774
|
+
wgpu::BindGroupEntry { binding: 17, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
775
|
+
],
|
|
776
|
+
});
|
|
777
|
+
self.material_params_buffers[idx] = Some(buf);
|
|
778
|
+
self.material_per_material_bgs[idx] = Some(bg);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Pad short writes to 256 B so write_buffer doesn't read past `params`.
|
|
782
|
+
let mut padded = [0u8; 256];
|
|
783
|
+
padded[..params.len()].copy_from_slice(params);
|
|
784
|
+
let buf = self.material_params_buffers[idx].as_ref().unwrap();
|
|
785
|
+
queue.write_buffer(buf, 0, &padded);
|
|
786
|
+
Ok(())
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/// Per-material BG when set, otherwise the shared default.
|
|
790
|
+
fn per_material_bg_for(&self, handle: MaterialHandle) -> &wgpu::BindGroup {
|
|
791
|
+
let idx = (handle as usize).wrapping_sub(1);
|
|
792
|
+
self.material_per_material_bgs.get(idx)
|
|
793
|
+
.and_then(|b| b.as_ref())
|
|
794
|
+
.unwrap_or(&self.default_per_material_bg)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/// EN-011 — link a material to a planar reflection probe's RT
|
|
798
|
+
/// view. The per-material bind group is rebuilt so binding 12
|
|
799
|
+
/// resolves to the probe's `color_view` instead of the default
|
|
800
|
+
/// 1×1 black texture. Subsequent draws of `handle` see the
|
|
801
|
+
/// probe's contents.
|
|
802
|
+
///
|
|
803
|
+
/// Pass `probe_view = None` (or revert to the default by passing
|
|
804
|
+
/// `&self.default_black_view` from the caller side) to unlink.
|
|
805
|
+
/// The `probe_handle` is opaque to this method — it's stored
|
|
806
|
+
/// purely so `material_reflection_probe[idx]` reflects the link
|
|
807
|
+
/// for diagnostics + the renderer's per-frame dispatch.
|
|
808
|
+
///
|
|
809
|
+
/// The wgpu Texture identity is stable for the probe's lifetime,
|
|
810
|
+
/// so we DON'T need to rebuild this bind group every frame; the
|
|
811
|
+
/// engine repaints the probe's texture but the same view keeps
|
|
812
|
+
/// pointing at it.
|
|
813
|
+
///
|
|
814
|
+
/// Why we route through here vs direct field access: the BG
|
|
815
|
+
/// also needs to bind a user-params UBO (binding 11) — either
|
|
816
|
+
/// the per-material UBO when one was allocated by
|
|
817
|
+
/// `set_user_params`, or the shared default zero-UBO. This
|
|
818
|
+
/// helper picks the right one.
|
|
819
|
+
pub fn set_reflection_probe(
|
|
820
|
+
&mut self,
|
|
821
|
+
device: &wgpu::Device,
|
|
822
|
+
handle: MaterialHandle,
|
|
823
|
+
probe_handle: u32,
|
|
824
|
+
probe_view: &wgpu::TextureView,
|
|
825
|
+
) -> Result<(), &'static str> {
|
|
826
|
+
if handle == 0 { return Err("invalid material handle"); }
|
|
827
|
+
let idx = (handle - 1) as usize;
|
|
828
|
+
if idx >= self.pipelines.len() || self.pipelines[idx].is_none() {
|
|
829
|
+
return Err("material handle not registered");
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Grow parallel vectors so the index is in bounds. We grow
|
|
833
|
+
// BOTH params + reflection registries together — they share
|
|
834
|
+
// an index domain (MaterialHandle - 1).
|
|
835
|
+
while self.material_params_buffers.len() <= idx {
|
|
836
|
+
self.material_params_buffers.push(None);
|
|
837
|
+
self.material_per_material_bgs.push(None);
|
|
838
|
+
}
|
|
839
|
+
while self.material_reflection_probe.len() <= idx {
|
|
840
|
+
self.material_reflection_probe.push(None);
|
|
841
|
+
}
|
|
842
|
+
self.material_reflection_probe[idx] = Some(probe_handle);
|
|
843
|
+
|
|
844
|
+
// Resolve binding 11 — per-material UBO if one's been
|
|
845
|
+
// allocated, else the shared zero-init default.
|
|
846
|
+
let user_params_buf: &wgpu::Buffer = self.material_params_buffers
|
|
847
|
+
.get(idx)
|
|
848
|
+
.and_then(|b| b.as_ref())
|
|
849
|
+
.unwrap_or(&self._default_user_params_buffer);
|
|
850
|
+
|
|
851
|
+
// EN-012 — resolve binding 10 to the per-material factors UBO
|
|
852
|
+
// when one's been allocated by `set_shading_model` /
|
|
853
|
+
// `set_foliage`. Otherwise the reflection-probe rebind would
|
|
854
|
+
// silently revert a foliage-flagged material back to the
|
|
855
|
+
// default factors at binding 10.
|
|
856
|
+
let factors_buf: &wgpu::Buffer = self.material_factors_buffers
|
|
857
|
+
.get(idx)
|
|
858
|
+
.and_then(|b| b.as_ref())
|
|
859
|
+
.unwrap_or(&self._default_material_factors_buffer);
|
|
860
|
+
|
|
861
|
+
// EN-014 — resolve binding 14/15/16 from any per-material
|
|
862
|
+
// texture-array links so a probe-bound material doesn't lose
|
|
863
|
+
// its splat layers when the BG gets rebuilt.
|
|
864
|
+
let [albedo_view, normal_view, mr_view] = self.resolve_array_views(idx);
|
|
865
|
+
|
|
866
|
+
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
867
|
+
label: Some("material_per_material_bg_reflection"),
|
|
868
|
+
layout: &self.layouts.per_material,
|
|
869
|
+
entries: &[
|
|
870
|
+
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
871
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
872
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
873
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
874
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
875
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
876
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
877
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
878
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
879
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
880
|
+
wgpu::BindGroupEntry { binding: 10, resource: factors_buf.as_entire_binding() },
|
|
881
|
+
wgpu::BindGroupEntry { binding: 11, resource: user_params_buf.as_entire_binding() },
|
|
882
|
+
// EN-011 — probe's color view + a filtering sampler.
|
|
883
|
+
wgpu::BindGroupEntry { binding: 12, resource: wgpu::BindingResource::TextureView(probe_view) },
|
|
884
|
+
wgpu::BindGroupEntry { binding: 13, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
885
|
+
// EN-014 — resolved texture arrays + shared sampler.
|
|
886
|
+
wgpu::BindGroupEntry { binding: 14, resource: wgpu::BindingResource::TextureView(albedo_view) },
|
|
887
|
+
wgpu::BindGroupEntry { binding: 15, resource: wgpu::BindingResource::TextureView(normal_view) },
|
|
888
|
+
wgpu::BindGroupEntry { binding: 16, resource: wgpu::BindingResource::TextureView(mr_view) },
|
|
889
|
+
wgpu::BindGroupEntry { binding: 17, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
890
|
+
],
|
|
891
|
+
});
|
|
892
|
+
self.material_per_material_bgs[idx] = Some(bg);
|
|
893
|
+
Ok(())
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/// EN-014 — resolve the 3 D2Array texture views for a material
|
|
897
|
+
/// (albedo / normal / MR), falling back to the shared 1×1×1 stub
|
|
898
|
+
/// when the slot has no link. Returns borrowed views that share
|
|
899
|
+
/// the lifetime of `self`. Callers use these to populate bindings
|
|
900
|
+
/// 14/15/16 when (re)building a per-material BG.
|
|
901
|
+
fn resolve_array_views(&self, idx: usize) -> [&wgpu::TextureView; 3] {
|
|
902
|
+
let mut out: [&wgpu::TextureView; 3] = [
|
|
903
|
+
&self.default_array_view,
|
|
904
|
+
&self.default_array_view,
|
|
905
|
+
&self.default_array_view,
|
|
906
|
+
];
|
|
907
|
+
if let Some(slots) = self.material_texture_arrays.get(idx) {
|
|
908
|
+
for (slot, link) in slots.iter().enumerate() {
|
|
909
|
+
if let Some(handle) = link {
|
|
910
|
+
let h = *handle as usize;
|
|
911
|
+
if h > 0 {
|
|
912
|
+
if let Some(Some(arr)) = self.texture_arrays.get(h - 1) {
|
|
913
|
+
out[slot] = &arr.view;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
out
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/// EN-012 — resolve the per-material `MaterialFactors` UBO for
|
|
923
|
+
/// `idx`, falling back to the shared default buffer when no
|
|
924
|
+
/// per-material factors UBO has been allocated yet. Used by every
|
|
925
|
+
/// per-material BG-build path so EN-011 / EN-014 rebinds don't
|
|
926
|
+
/// silently revert a material's foliage / shading-model state to
|
|
927
|
+
/// the default factors.
|
|
928
|
+
fn resolve_factors_buffer(&self, idx: usize) -> &wgpu::Buffer {
|
|
929
|
+
self.material_factors_buffers
|
|
930
|
+
.get(idx)
|
|
931
|
+
.and_then(|b| b.as_ref())
|
|
932
|
+
.unwrap_or(&self._default_material_factors_buffer)
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/// EN-012 — ensure a per-material `MaterialFactorsUniforms` UBO
|
|
936
|
+
/// exists for `idx`, allocating + initialising from the default
|
|
937
|
+
/// values on first use. Returns mutable access to the CPU mirror
|
|
938
|
+
/// so the caller can mutate one field then write back via
|
|
939
|
+
/// `flush_material_factors`. Grows `material_factors_buffers` /
|
|
940
|
+
/// `material_factors_data` so `idx` is in bounds.
|
|
941
|
+
fn ensure_material_factors(
|
|
942
|
+
&mut self, device: &wgpu::Device, idx: usize,
|
|
943
|
+
) -> &mut MaterialFactorsUniforms {
|
|
944
|
+
while self.material_factors_buffers.len() <= idx {
|
|
945
|
+
self.material_factors_buffers.push(None);
|
|
946
|
+
self.material_factors_data.push(MaterialFactorsUniforms::default());
|
|
947
|
+
}
|
|
948
|
+
if self.material_factors_buffers[idx].is_none() {
|
|
949
|
+
// Allocate a fresh per-material factors UBO seeded with the
|
|
950
|
+
// current CPU-side data (defaults on first call).
|
|
951
|
+
let init = self.material_factors_data[idx];
|
|
952
|
+
let buf = device.create_buffer_init(
|
|
953
|
+
&wgpu::util::BufferInitDescriptor {
|
|
954
|
+
label: Some("material_factors_per_material"),
|
|
955
|
+
contents: bytemuck::bytes_of(&init),
|
|
956
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
957
|
+
},
|
|
958
|
+
);
|
|
959
|
+
self.material_factors_buffers[idx] = Some(buf);
|
|
960
|
+
}
|
|
961
|
+
&mut self.material_factors_data[idx]
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/// EN-012 — write the current CPU-side `MaterialFactorsUniforms`
|
|
965
|
+
/// for `idx` to the per-material UBO. Caller must have previously
|
|
966
|
+
/// called `ensure_material_factors` so the buffer exists.
|
|
967
|
+
fn flush_material_factors(&self, queue: &wgpu::Queue, idx: usize) {
|
|
968
|
+
if let Some(Some(buf)) = self.material_factors_buffers.get(idx) {
|
|
969
|
+
let data = &self.material_factors_data[idx];
|
|
970
|
+
queue.write_buffer(buf, 0, bytemuck::bytes_of(data));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/// EN-012 — rebuild the per-material BG for `idx` after a
|
|
975
|
+
/// MaterialFactors mutation. Resolves the per-material UBO,
|
|
976
|
+
/// user_params UBO, reflection-probe view, and texture-array
|
|
977
|
+
/// views from the current state so we don't clobber any of the
|
|
978
|
+
/// EN-005/EN-011/EN-014 wiring. Caller is responsible for
|
|
979
|
+
/// passing `probe_view` (the renderer wrapper looks the linked
|
|
980
|
+
/// probe up via `material_reflection_probe_handle` and supplies
|
|
981
|
+
/// either the probe's RT view or the default 1×1 black view).
|
|
982
|
+
fn rebuild_per_material_bg(
|
|
983
|
+
&mut self,
|
|
984
|
+
device: &wgpu::Device,
|
|
985
|
+
idx: usize,
|
|
986
|
+
probe_view: &wgpu::TextureView,
|
|
987
|
+
) {
|
|
988
|
+
// Grow parallel BG vector so `idx` is in bounds.
|
|
989
|
+
while self.material_per_material_bgs.len() <= idx {
|
|
990
|
+
self.material_params_buffers.push(None);
|
|
991
|
+
self.material_per_material_bgs.push(None);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
let factors_buf: &wgpu::Buffer = self.resolve_factors_buffer(idx);
|
|
995
|
+
let user_params_buf: &wgpu::Buffer = self.material_params_buffers
|
|
996
|
+
.get(idx)
|
|
997
|
+
.and_then(|b| b.as_ref())
|
|
998
|
+
.unwrap_or(&self._default_user_params_buffer);
|
|
999
|
+
let [albedo_view, normal_view, mr_view] = self.resolve_array_views(idx);
|
|
1000
|
+
|
|
1001
|
+
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
1002
|
+
label: Some("material_per_material_bg_factors"),
|
|
1003
|
+
layout: &self.layouts.per_material,
|
|
1004
|
+
entries: &[
|
|
1005
|
+
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1006
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1007
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1008
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1009
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1010
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1011
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1012
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1013
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1014
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1015
|
+
wgpu::BindGroupEntry { binding: 10, resource: factors_buf.as_entire_binding() },
|
|
1016
|
+
wgpu::BindGroupEntry { binding: 11, resource: user_params_buf.as_entire_binding() },
|
|
1017
|
+
wgpu::BindGroupEntry { binding: 12, resource: wgpu::BindingResource::TextureView(probe_view) },
|
|
1018
|
+
wgpu::BindGroupEntry { binding: 13, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1019
|
+
wgpu::BindGroupEntry { binding: 14, resource: wgpu::BindingResource::TextureView(albedo_view) },
|
|
1020
|
+
wgpu::BindGroupEntry { binding: 15, resource: wgpu::BindingResource::TextureView(normal_view) },
|
|
1021
|
+
wgpu::BindGroupEntry { binding: 16, resource: wgpu::BindingResource::TextureView(mr_view) },
|
|
1022
|
+
wgpu::BindGroupEntry { binding: 17, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1023
|
+
],
|
|
1024
|
+
});
|
|
1025
|
+
self.material_per_material_bgs[idx] = Some(bg);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/// EN-012 — set the shading model for a material (`0` = default
|
|
1029
|
+
/// lit, `1` = foliage, `2` = subsurface — V2 stub). Lazily
|
|
1030
|
+
/// allocates a per-material `MaterialFactors` UBO on first call,
|
|
1031
|
+
/// then rebuilds the per-material BG so subsequent draws sample
|
|
1032
|
+
/// the new shading model. Caller passes `probe_view` so EN-011
|
|
1033
|
+
/// reflection links survive — see `Renderer::set_material_shading_model`.
|
|
1034
|
+
pub fn set_material_shading_model(
|
|
1035
|
+
&mut self,
|
|
1036
|
+
device: &wgpu::Device,
|
|
1037
|
+
queue: &wgpu::Queue,
|
|
1038
|
+
material: MaterialHandle,
|
|
1039
|
+
model: u32,
|
|
1040
|
+
probe_view: &wgpu::TextureView,
|
|
1041
|
+
) -> Result<(), &'static str> {
|
|
1042
|
+
if material == 0 { return Err("invalid material handle"); }
|
|
1043
|
+
let idx = (material - 1) as usize;
|
|
1044
|
+
if idx >= self.pipelines.len() || self.pipelines[idx].is_none() {
|
|
1045
|
+
return Err("material handle not registered");
|
|
1046
|
+
}
|
|
1047
|
+
{
|
|
1048
|
+
let factors = self.ensure_material_factors(device, idx);
|
|
1049
|
+
factors.shading_model[0] = model as f32;
|
|
1050
|
+
}
|
|
1051
|
+
self.flush_material_factors(queue, idx);
|
|
1052
|
+
self.rebuild_per_material_bg(device, idx, probe_view);
|
|
1053
|
+
Ok(())
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/// EN-012 — set the foliage shading parameters for a material.
|
|
1057
|
+
/// Only takes effect when `shading_model == 1` (foliage). Updates
|
|
1058
|
+
/// `MaterialFactors.shading_model.yzw` (transmission_color) and
|
|
1059
|
+
/// `foliage_params.xy` (transmission_amount, wrap_factor). Lazily
|
|
1060
|
+
/// allocates a per-material UBO on first call.
|
|
1061
|
+
pub fn set_material_foliage(
|
|
1062
|
+
&mut self,
|
|
1063
|
+
device: &wgpu::Device,
|
|
1064
|
+
queue: &wgpu::Queue,
|
|
1065
|
+
material: MaterialHandle,
|
|
1066
|
+
trans_color: [f32; 3],
|
|
1067
|
+
trans_amount: f32,
|
|
1068
|
+
wrap_factor: f32,
|
|
1069
|
+
probe_view: &wgpu::TextureView,
|
|
1070
|
+
) -> Result<(), &'static str> {
|
|
1071
|
+
if material == 0 { return Err("invalid material handle"); }
|
|
1072
|
+
let idx = (material - 1) as usize;
|
|
1073
|
+
if idx >= self.pipelines.len() || self.pipelines[idx].is_none() {
|
|
1074
|
+
return Err("material handle not registered");
|
|
1075
|
+
}
|
|
1076
|
+
{
|
|
1077
|
+
let factors = self.ensure_material_factors(device, idx);
|
|
1078
|
+
factors.shading_model[1] = trans_color[0];
|
|
1079
|
+
factors.shading_model[2] = trans_color[1];
|
|
1080
|
+
factors.shading_model[3] = trans_color[2];
|
|
1081
|
+
factors.foliage_params[0] = trans_amount;
|
|
1082
|
+
factors.foliage_params[1] = wrap_factor;
|
|
1083
|
+
}
|
|
1084
|
+
self.flush_material_factors(queue, idx);
|
|
1085
|
+
self.rebuild_per_material_bg(device, idx, probe_view);
|
|
1086
|
+
Ok(())
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/// EN-014 — create a 2D texture array from a slice of layer data.
|
|
1090
|
+
/// Each `(bytes, w, h)` describes one layer's RGBA8 source. All
|
|
1091
|
+
/// layers must share `w × h` (wgpu requires a uniform extent for
|
|
1092
|
+
/// D2Array). V1 panics on mismatch — V2 may resize. Layer count
|
|
1093
|
+
/// is capped at `MAX_TEXTURE_ARRAY_LAYERS`; extra layers are
|
|
1094
|
+
/// dropped. Returns a 1-based handle (0 on failure: empty layers
|
|
1095
|
+
/// or zero extent).
|
|
1096
|
+
///
|
|
1097
|
+
/// Defaults: `format = 0` (Rgba8UnormSrgb, suitable for albedo),
|
|
1098
|
+
/// `mip_levels = 1` (no mips). For data textures (normal / MR)
|
|
1099
|
+
/// or auto-mip generation, see `create_texture_array_ex`.
|
|
1100
|
+
pub fn create_texture_array(
|
|
1101
|
+
&mut self,
|
|
1102
|
+
device: &wgpu::Device,
|
|
1103
|
+
queue: &wgpu::Queue,
|
|
1104
|
+
layers: &[(&[u8], u32, u32)],
|
|
1105
|
+
) -> u32 {
|
|
1106
|
+
// V1 default: sRGB albedo, no mips. V2 callers use the _ex
|
|
1107
|
+
// variant directly.
|
|
1108
|
+
self.create_texture_array_ex(device, queue, layers, 0, 1)
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/// EN-014 V2 — create a 2D texture array with explicit pixel format
|
|
1112
|
+
/// and mip-level control. Layer extent / count rules match V1.
|
|
1113
|
+
///
|
|
1114
|
+
/// `format`:
|
|
1115
|
+
/// 0 → `Rgba8UnormSrgb` (albedo / colour textures; default)
|
|
1116
|
+
/// 1 → `Rgba8Unorm` (normal / MR / data textures — linear)
|
|
1117
|
+
/// _ → falls back to `Rgba8UnormSrgb`
|
|
1118
|
+
///
|
|
1119
|
+
/// `mip_levels`:
|
|
1120
|
+
/// 1 → no mips (matches V1 behaviour)
|
|
1121
|
+
/// 0 → auto-generate `floor(log2(max(w,h))) + 1` mips, filled
|
|
1122
|
+
/// by point-downsample (`copy_texture_to_texture` halving
|
|
1123
|
+
/// the previous mip). Cheap, correct sized, but aliased
|
|
1124
|
+
/// — a render-pass box filter is the V2.5 follow-up.
|
|
1125
|
+
/// N > 1 → not yet supported (game-supplied per-mip bytes); V2
|
|
1126
|
+
/// treats this as auto-generate.
|
|
1127
|
+
pub fn create_texture_array_ex(
|
|
1128
|
+
&mut self,
|
|
1129
|
+
device: &wgpu::Device,
|
|
1130
|
+
queue: &wgpu::Queue,
|
|
1131
|
+
layers: &[(&[u8], u32, u32)],
|
|
1132
|
+
format: u32,
|
|
1133
|
+
mip_levels: u32,
|
|
1134
|
+
) -> u32 {
|
|
1135
|
+
let layer_count = (layers.len() as u32).min(MAX_TEXTURE_ARRAY_LAYERS);
|
|
1136
|
+
if layer_count == 0 { return 0; }
|
|
1137
|
+
let (_first_bytes, w, h) = layers[0];
|
|
1138
|
+
if w == 0 || h == 0 { return 0; }
|
|
1139
|
+
// Uniform extent check — V1 hard-fail surfaces obvious bugs at
|
|
1140
|
+
// creation rather than during silent GPU truncation later.
|
|
1141
|
+
for (i, (_, lw, lh)) in layers.iter().enumerate().take(layer_count as usize) {
|
|
1142
|
+
if *lw != w || *lh != h {
|
|
1143
|
+
eprintln!(
|
|
1144
|
+
"[texture_array] layer {} extent {}×{} does not match layer 0 ({}×{}); aborting create",
|
|
1145
|
+
i, lw, lh, w, h,
|
|
1146
|
+
);
|
|
1147
|
+
return 0;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
let wgpu_format = map_texture_array_format(format);
|
|
1151
|
+
// Resolve mip count. mip_levels = 1 → single mip. mip_levels = 0
|
|
1152
|
+
// → engine-generated max. Anything else (game-supplied per-mip)
|
|
1153
|
+
// is V2.5; for now treat N > 1 as auto so games opting into mips
|
|
1154
|
+
// don't silently regress to no-mips.
|
|
1155
|
+
let max_mips = (w.max(h) as f32).log2().floor() as u32 + 1;
|
|
1156
|
+
let auto_generate = mip_levels == 0 || mip_levels > 1;
|
|
1157
|
+
let mip_level_count = if mip_levels == 1 { 1 } else { max_mips.max(1) };
|
|
1158
|
+
// Auto-gen needs COPY_SRC on the texture so we can ping-pong each
|
|
1159
|
+
// mip into the next via copy_texture_to_texture.
|
|
1160
|
+
let mut usage = wgpu::TextureUsages::TEXTURE_BINDING
|
|
1161
|
+
| wgpu::TextureUsages::COPY_DST;
|
|
1162
|
+
if auto_generate && mip_level_count > 1 {
|
|
1163
|
+
usage |= wgpu::TextureUsages::COPY_SRC;
|
|
1164
|
+
}
|
|
1165
|
+
let bytes_per_layer = (w as usize) * (h as usize) * 4;
|
|
1166
|
+
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
|
1167
|
+
label: Some("material_texture_array"),
|
|
1168
|
+
size: wgpu::Extent3d {
|
|
1169
|
+
width: w,
|
|
1170
|
+
height: h,
|
|
1171
|
+
depth_or_array_layers: layer_count,
|
|
1172
|
+
},
|
|
1173
|
+
mip_level_count,
|
|
1174
|
+
sample_count: 1,
|
|
1175
|
+
dimension: wgpu::TextureDimension::D2,
|
|
1176
|
+
format: wgpu_format,
|
|
1177
|
+
usage,
|
|
1178
|
+
view_formats: &[],
|
|
1179
|
+
});
|
|
1180
|
+
for (i, (bytes, _, _)) in layers.iter().enumerate().take(layer_count as usize) {
|
|
1181
|
+
// Defensive — a short layer slice would panic in
|
|
1182
|
+
// write_texture; skip and emit a diagnostic instead.
|
|
1183
|
+
if bytes.len() < bytes_per_layer {
|
|
1184
|
+
eprintln!(
|
|
1185
|
+
"[texture_array] layer {} short: {} B < {} B (skipping)",
|
|
1186
|
+
i, bytes.len(), bytes_per_layer,
|
|
1187
|
+
);
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
queue.write_texture(
|
|
1191
|
+
wgpu::TexelCopyTextureInfo {
|
|
1192
|
+
texture: &texture,
|
|
1193
|
+
mip_level: 0,
|
|
1194
|
+
origin: wgpu::Origin3d { x: 0, y: 0, z: i as u32 },
|
|
1195
|
+
aspect: wgpu::TextureAspect::All,
|
|
1196
|
+
},
|
|
1197
|
+
&bytes[..bytes_per_layer],
|
|
1198
|
+
wgpu::TexelCopyBufferLayout {
|
|
1199
|
+
offset: 0,
|
|
1200
|
+
bytes_per_row: Some(w * 4),
|
|
1201
|
+
rows_per_image: Some(h),
|
|
1202
|
+
},
|
|
1203
|
+
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
// EN-014 V2 — auto-generate the mip chain via point-sample copies.
|
|
1207
|
+
// For each mip > 0, copy a half-size region of mip-1 into mip,
|
|
1208
|
+
// for every layer. wgpu's copy_texture_to_texture covers a single
|
|
1209
|
+
// mip level + array layer per call. This is point-filtered (not
|
|
1210
|
+
// box-filtered): correct sizes, sampleable at distance, but
|
|
1211
|
+
// aliased. V2.5 follow-up upgrades this to a render-pass box
|
|
1212
|
+
// filter (one fullscreen draw per (mip, layer)).
|
|
1213
|
+
if auto_generate && mip_level_count > 1 {
|
|
1214
|
+
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
1215
|
+
label: Some("material_texture_array_mipgen"),
|
|
1216
|
+
});
|
|
1217
|
+
for mip in 1..mip_level_count {
|
|
1218
|
+
let src_w = (w >> (mip - 1)).max(1);
|
|
1219
|
+
let src_h = (h >> (mip - 1)).max(1);
|
|
1220
|
+
let dst_w = (w >> mip).max(1);
|
|
1221
|
+
let dst_h = (h >> mip).max(1);
|
|
1222
|
+
// Copy region is dst-sized so we read the top-left
|
|
1223
|
+
// 2x2 reduction implicitly. Truly we'd want a filter,
|
|
1224
|
+
// but copy is the cheapest "mips exist" path.
|
|
1225
|
+
let copy_w = dst_w.min(src_w);
|
|
1226
|
+
let copy_h = dst_h.min(src_h);
|
|
1227
|
+
for layer in 0..layer_count {
|
|
1228
|
+
encoder.copy_texture_to_texture(
|
|
1229
|
+
wgpu::TexelCopyTextureInfo {
|
|
1230
|
+
texture: &texture,
|
|
1231
|
+
mip_level: mip - 1,
|
|
1232
|
+
origin: wgpu::Origin3d { x: 0, y: 0, z: layer },
|
|
1233
|
+
aspect: wgpu::TextureAspect::All,
|
|
1234
|
+
},
|
|
1235
|
+
wgpu::TexelCopyTextureInfo {
|
|
1236
|
+
texture: &texture,
|
|
1237
|
+
mip_level: mip,
|
|
1238
|
+
origin: wgpu::Origin3d { x: 0, y: 0, z: layer },
|
|
1239
|
+
aspect: wgpu::TextureAspect::All,
|
|
1240
|
+
},
|
|
1241
|
+
wgpu::Extent3d {
|
|
1242
|
+
width: copy_w,
|
|
1243
|
+
height: copy_h,
|
|
1244
|
+
depth_or_array_layers: 1,
|
|
1245
|
+
},
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
queue.submit(std::iter::once(encoder.finish()));
|
|
1250
|
+
}
|
|
1251
|
+
let view = texture.create_view(&wgpu::TextureViewDescriptor {
|
|
1252
|
+
label: Some("material_texture_array_view"),
|
|
1253
|
+
dimension: Some(wgpu::TextureViewDimension::D2Array),
|
|
1254
|
+
..Default::default()
|
|
1255
|
+
});
|
|
1256
|
+
self.texture_arrays.push(Some(TextureArray {
|
|
1257
|
+
texture, view, layer_count,
|
|
1258
|
+
}));
|
|
1259
|
+
self.texture_arrays.len() as u32
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/// EN-014 — link a texture array to a material's per-material
|
|
1263
|
+
/// bind-group at one of three slots (0 = albedo, 1 = normal,
|
|
1264
|
+
/// 2 = MR). Pass `array = 0` to revert the slot to the default
|
|
1265
|
+
/// 1×1×1 stub. The per-material BG is rebuilt so subsequent draws
|
|
1266
|
+
/// see the new array view at @binding 14/15/16.
|
|
1267
|
+
///
|
|
1268
|
+
/// `probe_view` is the binding-12 source — the caller (typically
|
|
1269
|
+
/// `Renderer::set_material_texture_array`) resolves the
|
|
1270
|
+
/// material's currently-linked reflection probe and passes its
|
|
1271
|
+
/// `color_view` here, or `&self.default_black_view` to default.
|
|
1272
|
+
/// This indirection keeps EN-011 reflection links live across
|
|
1273
|
+
/// EN-014 rebinds without the MaterialSystem having to reach
|
|
1274
|
+
/// into the Renderer's probe registry. Out-of-range slots /
|
|
1275
|
+
/// unknown handles are no-ops with a diagnostic.
|
|
1276
|
+
pub fn set_material_texture_array(
|
|
1277
|
+
&mut self,
|
|
1278
|
+
device: &wgpu::Device,
|
|
1279
|
+
material: MaterialHandle,
|
|
1280
|
+
slot: u32,
|
|
1281
|
+
array: u32,
|
|
1282
|
+
probe_view: &wgpu::TextureView,
|
|
1283
|
+
) {
|
|
1284
|
+
if material == 0 { return; }
|
|
1285
|
+
let idx = (material - 1) as usize;
|
|
1286
|
+
if idx >= self.pipelines.len() || self.pipelines[idx].is_none() {
|
|
1287
|
+
eprintln!("[texture_array] unknown material handle {material}");
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
if slot >= 3 {
|
|
1291
|
+
eprintln!("[texture_array] slot {slot} out of range (0..=2)");
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if array != 0 {
|
|
1295
|
+
let h = array as usize;
|
|
1296
|
+
if h == 0 || h > self.texture_arrays.len()
|
|
1297
|
+
|| self.texture_arrays[h - 1].is_none()
|
|
1298
|
+
{
|
|
1299
|
+
eprintln!("[texture_array] unknown array handle {array}");
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Grow the link table so `idx` is in bounds. Parallel to
|
|
1305
|
+
// the params + reflection registries but not strictly tied
|
|
1306
|
+
// to either — texture-array links are independent.
|
|
1307
|
+
while self.material_texture_arrays.len() <= idx {
|
|
1308
|
+
self.material_texture_arrays.push([None, None, None]);
|
|
1309
|
+
}
|
|
1310
|
+
let link = if array == 0 { None } else { Some(array) };
|
|
1311
|
+
self.material_texture_arrays[idx][slot as usize] = link;
|
|
1312
|
+
|
|
1313
|
+
// Rebuild the per-material BG. Resolve user_params from
|
|
1314
|
+
// existing state so we don't clobber EN-005 links.
|
|
1315
|
+
while self.material_params_buffers.len() <= idx {
|
|
1316
|
+
self.material_params_buffers.push(None);
|
|
1317
|
+
self.material_per_material_bgs.push(None);
|
|
1318
|
+
}
|
|
1319
|
+
let user_params_buf: &wgpu::Buffer = self.material_params_buffers
|
|
1320
|
+
.get(idx)
|
|
1321
|
+
.and_then(|b| b.as_ref())
|
|
1322
|
+
.unwrap_or(&self._default_user_params_buffer);
|
|
1323
|
+
// EN-012 — preserve any per-material MaterialFactors UBO
|
|
1324
|
+
// across an EN-014 array rebind.
|
|
1325
|
+
let factors_buf: &wgpu::Buffer = self.material_factors_buffers
|
|
1326
|
+
.get(idx)
|
|
1327
|
+
.and_then(|b| b.as_ref())
|
|
1328
|
+
.unwrap_or(&self._default_material_factors_buffer);
|
|
1329
|
+
|
|
1330
|
+
let [albedo_view, normal_view, mr_view] = self.resolve_array_views(idx);
|
|
1331
|
+
|
|
1332
|
+
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
1333
|
+
label: Some("material_per_material_bg_array"),
|
|
1334
|
+
layout: &self.layouts.per_material,
|
|
1335
|
+
entries: &[
|
|
1336
|
+
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1337
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1338
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1339
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1340
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1341
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1342
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1343
|
+
wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1344
|
+
wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::TextureView(&self._default_white_view) },
|
|
1345
|
+
wgpu::BindGroupEntry { binding: 9, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1346
|
+
wgpu::BindGroupEntry { binding: 10, resource: factors_buf.as_entire_binding() },
|
|
1347
|
+
wgpu::BindGroupEntry { binding: 11, resource: user_params_buf.as_entire_binding() },
|
|
1348
|
+
wgpu::BindGroupEntry { binding: 12, resource: wgpu::BindingResource::TextureView(probe_view) },
|
|
1349
|
+
wgpu::BindGroupEntry { binding: 13, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1350
|
+
wgpu::BindGroupEntry { binding: 14, resource: wgpu::BindingResource::TextureView(albedo_view) },
|
|
1351
|
+
wgpu::BindGroupEntry { binding: 15, resource: wgpu::BindingResource::TextureView(normal_view) },
|
|
1352
|
+
wgpu::BindGroupEntry { binding: 16, resource: wgpu::BindingResource::TextureView(mr_view) },
|
|
1353
|
+
wgpu::BindGroupEntry { binding: 17, resource: wgpu::BindingResource::Sampler(&self._default_sampler) },
|
|
1354
|
+
],
|
|
1355
|
+
});
|
|
1356
|
+
self.material_per_material_bgs[idx] = Some(bg);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/// EN-014 — accessor for `material_reflection_probe[idx]`. The
|
|
1360
|
+
/// Renderer wrapper uses this to resolve the currently-linked
|
|
1361
|
+
/// probe handle when rebuilding the BG via
|
|
1362
|
+
/// `set_material_texture_array`. Returns `None` for unset / out-
|
|
1363
|
+
/// of-range / unlinked materials.
|
|
1364
|
+
pub fn material_reflection_probe_handle(&self, material: MaterialHandle) -> Option<u32> {
|
|
1365
|
+
if material == 0 { return None; }
|
|
1366
|
+
let idx = (material as usize).checked_sub(1)?;
|
|
1367
|
+
self.material_reflection_probe.get(idx).copied().flatten()
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/// Convenience — true if either bucket has queued work this frame.
|
|
1371
|
+
pub fn any_commands(&self) -> bool {
|
|
1372
|
+
!self.commands.is_empty() || !self.translucent_commands.is_empty()
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/// Submit a draw against a compiled material. Allocates (or reuses)
|
|
1376
|
+
/// a per-draw UBO slot, writes the MVP / model / tint / skin info,
|
|
1377
|
+
/// and queues the command for dispatch.
|
|
1378
|
+
pub fn submit_draw(
|
|
1379
|
+
&mut self,
|
|
1380
|
+
device: &wgpu::Device,
|
|
1381
|
+
queue: &wgpu::Queue,
|
|
1382
|
+
joint_buffer: &wgpu::Buffer,
|
|
1383
|
+
material: MaterialHandle,
|
|
1384
|
+
mesh_handle: u64,
|
|
1385
|
+
mesh_idx: usize,
|
|
1386
|
+
mvp: [[f32; 4]; 4],
|
|
1387
|
+
model: [[f32; 4]; 4],
|
|
1388
|
+
prev_mvp: [[f32; 4]; 4],
|
|
1389
|
+
tint: [f32; 4],
|
|
1390
|
+
skin_info: [u32; 4],
|
|
1391
|
+
) {
|
|
1392
|
+
let idx = material as usize;
|
|
1393
|
+
if material == 0 || idx > self.pipelines.len() { return; }
|
|
1394
|
+
let bucket = match self.pipelines[idx - 1].as_ref() {
|
|
1395
|
+
Some(p) => p.bucket,
|
|
1396
|
+
None => return,
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
let slot = self.next_draw_slot;
|
|
1400
|
+
self.next_draw_slot += 1;
|
|
1401
|
+
self.ensure_draw_slot(device, joint_buffer, slot);
|
|
1402
|
+
|
|
1403
|
+
let per_draw = PerDrawUniforms { mvp, model, prev_mvp, model_tint: tint, skin_info };
|
|
1404
|
+
queue.write_buffer(&self.per_draw_buffers[slot], 0, bytemuck::bytes_of(&per_draw));
|
|
1405
|
+
|
|
1406
|
+
let cmd = MaterialDrawCommand {
|
|
1407
|
+
material, mesh_handle, mesh_idx, draw_slot: slot,
|
|
1408
|
+
instance: None,
|
|
1409
|
+
};
|
|
1410
|
+
if bucket.is_translucent() {
|
|
1411
|
+
self.translucent_commands.push(cmd);
|
|
1412
|
+
} else {
|
|
1413
|
+
self.commands.push(cmd);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/// EN-001 — submit an instanced draw. Identical to `submit_draw`
|
|
1418
|
+
/// except the engine binds vertex slot 1 to the registered
|
|
1419
|
+
/// instance buffer and emits `draw_indexed(.., 0..count)`. The
|
|
1420
|
+
/// pipeline must have been compiled with `wants_instancing=true`
|
|
1421
|
+
/// (use `compile_material_instanced` on the renderer).
|
|
1422
|
+
///
|
|
1423
|
+
/// `model` / `mvp` here are the instance-local→world fallback
|
|
1424
|
+
/// transform — the per-instance buffer's `instance_pos`/`rot_y`/
|
|
1425
|
+
/// `scale` typically dominate, so callers usually pass identity
|
|
1426
|
+
/// for `model` and the camera VP for `mvp`. `tint` is multiplied
|
|
1427
|
+
/// per-draw (in addition to the per-instance tint).
|
|
1428
|
+
pub fn submit_draw_instanced(
|
|
1429
|
+
&mut self,
|
|
1430
|
+
device: &wgpu::Device,
|
|
1431
|
+
queue: &wgpu::Queue,
|
|
1432
|
+
joint_buffer: &wgpu::Buffer,
|
|
1433
|
+
material: MaterialHandle,
|
|
1434
|
+
mesh_handle: u64,
|
|
1435
|
+
mesh_idx: usize,
|
|
1436
|
+
instance_buffer: u32,
|
|
1437
|
+
instance_count: u32,
|
|
1438
|
+
mvp: [[f32; 4]; 4],
|
|
1439
|
+
model: [[f32; 4]; 4],
|
|
1440
|
+
prev_mvp: [[f32; 4]; 4],
|
|
1441
|
+
tint: [f32; 4],
|
|
1442
|
+
skin_info: [u32; 4],
|
|
1443
|
+
) {
|
|
1444
|
+
let idx = material as usize;
|
|
1445
|
+
if material == 0 || idx > self.pipelines.len() { return; }
|
|
1446
|
+
let bucket = match self.pipelines[idx - 1].as_ref() {
|
|
1447
|
+
Some(p) => p.bucket,
|
|
1448
|
+
None => return,
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
let slot = self.next_draw_slot;
|
|
1452
|
+
self.next_draw_slot += 1;
|
|
1453
|
+
self.ensure_draw_slot(device, joint_buffer, slot);
|
|
1454
|
+
|
|
1455
|
+
let per_draw = PerDrawUniforms { mvp, model, prev_mvp, model_tint: tint, skin_info };
|
|
1456
|
+
queue.write_buffer(&self.per_draw_buffers[slot], 0, bytemuck::bytes_of(&per_draw));
|
|
1457
|
+
|
|
1458
|
+
let cmd = MaterialDrawCommand {
|
|
1459
|
+
material, mesh_handle, mesh_idx, draw_slot: slot,
|
|
1460
|
+
instance: Some(InstanceDrawInfo {
|
|
1461
|
+
buffer_handle: instance_buffer,
|
|
1462
|
+
count: instance_count,
|
|
1463
|
+
}),
|
|
1464
|
+
};
|
|
1465
|
+
if bucket.is_translucent() {
|
|
1466
|
+
self.translucent_commands.push(cmd);
|
|
1467
|
+
} else {
|
|
1468
|
+
self.commands.push(cmd);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/// EN-001 — create a persistent instance buffer from CPU-side
|
|
1473
|
+
/// floats. The data layout matches `InstanceData3D` (9 floats per
|
|
1474
|
+
/// instance: pos.xyz, rot_y, scale, tint.rgba); this method pads
|
|
1475
|
+
/// each instance to 12 floats at upload time so the GPU side gets
|
|
1476
|
+
/// the correct 48-byte stride. Returns a 1-based handle to use
|
|
1477
|
+
/// with `submit_draw_instanced`. Pair with `destroy_instance_buffer`
|
|
1478
|
+
/// when the buffer's no longer needed.
|
|
1479
|
+
pub fn create_instance_buffer(
|
|
1480
|
+
&mut self,
|
|
1481
|
+
device: &wgpu::Device,
|
|
1482
|
+
queue: &wgpu::Queue,
|
|
1483
|
+
raw: &[f32],
|
|
1484
|
+
instance_count: u32,
|
|
1485
|
+
) -> u32 {
|
|
1486
|
+
let count = instance_count as usize;
|
|
1487
|
+
let mut packed: Vec<f32> = Vec::with_capacity(count * 12);
|
|
1488
|
+
for i in 0..count {
|
|
1489
|
+
let off = i * 9;
|
|
1490
|
+
if off + 9 > raw.len() { break; }
|
|
1491
|
+
packed.extend_from_slice(&raw[off..off + 3]); // pos.xyz
|
|
1492
|
+
packed.push(raw[off + 3]); // rot_y
|
|
1493
|
+
packed.push(raw[off + 4]); // scale
|
|
1494
|
+
packed.extend_from_slice(&raw[off + 5..off + 9]); // tint.rgba
|
|
1495
|
+
packed.extend_from_slice(&[0.0, 0.0, 0.0]); // pad to 48 bytes
|
|
1496
|
+
}
|
|
1497
|
+
let size = (packed.len() * std::mem::size_of::<f32>()) as u64;
|
|
1498
|
+
// Empty buffers can't be created (size 0 is invalid in wgpu).
|
|
1499
|
+
// Reserve at least one stride so the BG/binding remains valid.
|
|
1500
|
+
let buffer_size = size.max(48);
|
|
1501
|
+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
1502
|
+
label: Some("material_instance_buffer"),
|
|
1503
|
+
size: buffer_size,
|
|
1504
|
+
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
|
1505
|
+
mapped_at_creation: false,
|
|
1506
|
+
});
|
|
1507
|
+
if !packed.is_empty() {
|
|
1508
|
+
queue.write_buffer(&buffer, 0, bytemuck::cast_slice(&packed));
|
|
1509
|
+
}
|
|
1510
|
+
self.instance_buffers.push(Some(InstanceBuffer { buffer, count: instance_count }));
|
|
1511
|
+
self.instance_buffers.len() as u32
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/// EN-001 — drop an instance buffer slot. The slot is left as
|
|
1515
|
+
/// `None` so previously-issued handles never alias a future
|
|
1516
|
+
/// allocation. No-op for `handle == 0` or out-of-range handles.
|
|
1517
|
+
pub fn destroy_instance_buffer(&mut self, handle: u32) {
|
|
1518
|
+
if handle == 0 { return; }
|
|
1519
|
+
let idx = handle as usize - 1;
|
|
1520
|
+
if idx < self.instance_buffers.len() {
|
|
1521
|
+
self.instance_buffers[idx] = None;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
fn ensure_draw_slot(
|
|
1526
|
+
&mut self,
|
|
1527
|
+
device: &wgpu::Device,
|
|
1528
|
+
joint_buffer: &wgpu::Buffer,
|
|
1529
|
+
slot: usize,
|
|
1530
|
+
) {
|
|
1531
|
+
while self.per_draw_buffers.len() <= slot {
|
|
1532
|
+
let buf = device.create_buffer(&wgpu::BufferDescriptor {
|
|
1533
|
+
label: Some("material_per_draw"),
|
|
1534
|
+
size: std::mem::size_of::<PerDrawUniforms>() as u64,
|
|
1535
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
1536
|
+
mapped_at_creation: false,
|
|
1537
|
+
});
|
|
1538
|
+
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
1539
|
+
label: Some("material_per_draw_bg"),
|
|
1540
|
+
layout: &self.layouts.per_draw,
|
|
1541
|
+
entries: &[
|
|
1542
|
+
wgpu::BindGroupEntry { binding: 0, resource: buf.as_entire_binding() },
|
|
1543
|
+
wgpu::BindGroupEntry { binding: 1, resource: joint_buffer.as_entire_binding() },
|
|
1544
|
+
],
|
|
1545
|
+
});
|
|
1546
|
+
self.per_draw_buffers.push(buf);
|
|
1547
|
+
self.per_draw_bgs.push(bg);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/// Dispatch all queued material draws. Caller owns the render pass;
|
|
1552
|
+
/// this method binds the pipelines + groups + meshes and issues
|
|
1553
|
+
/// indexed draws. `mesh_fetch` is a closure that returns
|
|
1554
|
+
/// `(vertex_buffer, index_buffer, index_count)` for a given
|
|
1555
|
+
/// (mesh_handle, mesh_idx) — lets the renderer hand over its
|
|
1556
|
+
/// `model_gpu_cache` without this module taking a dependency on it.
|
|
1557
|
+
pub fn dispatch<'pass, F>(
|
|
1558
|
+
&'pass self,
|
|
1559
|
+
pass: &mut wgpu::RenderPass<'pass>,
|
|
1560
|
+
mesh_fetch: F,
|
|
1561
|
+
)
|
|
1562
|
+
where F: FnMut(u64, usize) -> Option<(&'pass wgpu::Buffer, &'pass wgpu::Buffer, u32)>
|
|
1563
|
+
{
|
|
1564
|
+
self.dispatch_with_view(pass, &self.per_view_bg, |_| true, false, mesh_fetch);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/// EN-011 — like `dispatch`, but uses a caller-supplied PerView
|
|
1568
|
+
/// bind group (so the planar-reflection pass can render the same
|
|
1569
|
+
/// draws against a mirrored camera UBO) and skips any command
|
|
1570
|
+
/// whose material handle the `accept` predicate rejects (so the
|
|
1571
|
+
/// reflection pass can drop hardcoded-excluded materials —
|
|
1572
|
+
/// foliage, particles, grass — without touching the original
|
|
1573
|
+
/// command list).
|
|
1574
|
+
///
|
|
1575
|
+
/// `accept` is consulted once per command; the engine passes a
|
|
1576
|
+
/// closure that closes over a small `HashSet<MaterialHandle>` of
|
|
1577
|
+
/// excluded material handles.
|
|
1578
|
+
///
|
|
1579
|
+
/// EN-011 V2 — `use_reflection_pipeline` swaps in the material's
|
|
1580
|
+
/// sibling pipeline (cull_mode flipped) when one was compiled.
|
|
1581
|
+
/// Falls back to the main pipeline if no reflection variant
|
|
1582
|
+
/// exists (translucent / cutout materials, where the original
|
|
1583
|
+
/// pipeline already cull-mode = None and no flip is needed).
|
|
1584
|
+
pub fn dispatch_with_view<'pass, F, A>(
|
|
1585
|
+
&'pass self,
|
|
1586
|
+
pass: &mut wgpu::RenderPass<'pass>,
|
|
1587
|
+
per_view_bg: &'pass wgpu::BindGroup,
|
|
1588
|
+
mut accept: A,
|
|
1589
|
+
use_reflection_pipeline: bool,
|
|
1590
|
+
mut mesh_fetch: F,
|
|
1591
|
+
)
|
|
1592
|
+
where
|
|
1593
|
+
F: FnMut(u64, usize) -> Option<(&'pass wgpu::Buffer, &'pass wgpu::Buffer, u32)>,
|
|
1594
|
+
A: FnMut(MaterialHandle) -> bool,
|
|
1595
|
+
{
|
|
1596
|
+
if self.commands.is_empty() { return; }
|
|
1597
|
+
|
|
1598
|
+
let mut last_material: MaterialHandle = 0;
|
|
1599
|
+
for cmd in &self.commands {
|
|
1600
|
+
if !accept(cmd.material) { continue; }
|
|
1601
|
+
if cmd.material != last_material {
|
|
1602
|
+
let mat = match self.pipelines.get(cmd.material as usize - 1) {
|
|
1603
|
+
Some(Some(m)) => m,
|
|
1604
|
+
_ => continue,
|
|
1605
|
+
};
|
|
1606
|
+
let pipeline = if use_reflection_pipeline {
|
|
1607
|
+
mat.reflection_pipeline.as_ref().unwrap_or(&mat.pipeline)
|
|
1608
|
+
} else {
|
|
1609
|
+
&mat.pipeline
|
|
1610
|
+
};
|
|
1611
|
+
pass.set_pipeline(pipeline);
|
|
1612
|
+
pass.set_bind_group(0, &self.per_frame_bg, &[]);
|
|
1613
|
+
pass.set_bind_group(1, per_view_bg, &[]);
|
|
1614
|
+
pass.set_bind_group(2, self.per_material_bg_for(cmd.material), &[]);
|
|
1615
|
+
last_material = cmd.material;
|
|
1616
|
+
}
|
|
1617
|
+
if let Some((vb, ib, icount)) = mesh_fetch(cmd.mesh_handle, cmd.mesh_idx) {
|
|
1618
|
+
pass.set_bind_group(3, &self.per_draw_bgs[cmd.draw_slot], &[]);
|
|
1619
|
+
pass.set_vertex_buffer(0, vb.slice(..));
|
|
1620
|
+
pass.set_index_buffer(ib.slice(..), wgpu::IndexFormat::Uint32);
|
|
1621
|
+
let instance_range = self.bind_instance_buffer(pass, &cmd.instance);
|
|
1622
|
+
if instance_range.end > instance_range.start {
|
|
1623
|
+
pass.draw_indexed(0..icount, 0, instance_range);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/// EN-001 — resolve an instanced draw command's vertex slot 1 binding
|
|
1630
|
+
/// and return the instance range. For non-instanced draws (`info` is
|
|
1631
|
+
/// None) this is a no-op and returns `0..1`. For instanced draws
|
|
1632
|
+
/// with a missing/destroyed buffer slot we return an empty range
|
|
1633
|
+
/// so the caller skips the draw rather than crashing on a stale
|
|
1634
|
+
/// handle.
|
|
1635
|
+
fn bind_instance_buffer<'pass>(
|
|
1636
|
+
&'pass self,
|
|
1637
|
+
pass: &mut wgpu::RenderPass<'pass>,
|
|
1638
|
+
info: &Option<InstanceDrawInfo>,
|
|
1639
|
+
) -> std::ops::Range<u32> {
|
|
1640
|
+
match info {
|
|
1641
|
+
None => 0..1,
|
|
1642
|
+
Some(inst) => {
|
|
1643
|
+
if inst.buffer_handle == 0 { return 0..1; }
|
|
1644
|
+
let slot_idx = inst.buffer_handle as usize - 1;
|
|
1645
|
+
match self.instance_buffers.get(slot_idx).and_then(|s| s.as_ref()) {
|
|
1646
|
+
Some(ib_slot) => {
|
|
1647
|
+
pass.set_vertex_buffer(1, ib_slot.buffer.slice(..));
|
|
1648
|
+
0..inst.count
|
|
1649
|
+
}
|
|
1650
|
+
None => 0..0,
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/// Phase 4b — rebuild the SceneInputs (group 4) bind group with
|
|
1657
|
+
/// the current frame's snapshot textures. Called by the Renderer
|
|
1658
|
+
/// once per frame when translucent draws exist and a SceneColor
|
|
1659
|
+
/// transient has been allocated. `scene_color_view` is the
|
|
1660
|
+
/// copy-to-sample snapshot from `hdr_rt`; `scene_depth_view` is
|
|
1661
|
+
/// the live depth buffer the opaque pass wrote. Other slots
|
|
1662
|
+
/// (impulse, motion vectors) bind to internal stub textures
|
|
1663
|
+
/// until Phase 7 wires them.
|
|
1664
|
+
pub fn update_scene_inputs(
|
|
1665
|
+
&mut self,
|
|
1666
|
+
device: &wgpu::Device,
|
|
1667
|
+
scene_color_view: &wgpu::TextureView,
|
|
1668
|
+
scene_depth_view: Option<&wgpu::TextureView>,
|
|
1669
|
+
impulse_view: Option<(&wgpu::TextureView, &wgpu::Sampler)>,
|
|
1670
|
+
) {
|
|
1671
|
+
let depth_view = scene_depth_view.unwrap_or(&self._scene_stub_depth_view);
|
|
1672
|
+
// Layout entry 5 is NonFiltering — fallback uses the depth
|
|
1673
|
+
// sampler (which is also NonFiltering) rather than the
|
|
1674
|
+
// filtering color sampler so the layout matches either way.
|
|
1675
|
+
let (imp_view, imp_samp): (&wgpu::TextureView, &wgpu::Sampler) = match impulse_view {
|
|
1676
|
+
Some((v, s)) => (v, s),
|
|
1677
|
+
None => (&self._scene_stub_view, &self._scene_depth_sampler),
|
|
1678
|
+
};
|
|
1679
|
+
// Phase 4c — group 4 binding 2 receives a COPY_DST snapshot of
|
|
1680
|
+
// the opaque depth buffer, rather than the live depth-stencil
|
|
1681
|
+
// attachment (wgpu rejects read+write aliasing in the same
|
|
1682
|
+
// pass). Callers that don't need depth pass the stub view
|
|
1683
|
+
// already held here; `Renderer::end_frame_with_scene` acquires
|
|
1684
|
+
// a transient depth texture when any translucent material
|
|
1685
|
+
// declares a depth read.
|
|
1686
|
+
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
1687
|
+
label: Some("scene_inputs_bg"),
|
|
1688
|
+
layout: &self.layouts.scene_inputs,
|
|
1689
|
+
entries: &[
|
|
1690
|
+
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(scene_color_view) },
|
|
1691
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self._scene_color_sampler) },
|
|
1692
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(depth_view) },
|
|
1693
|
+
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self._scene_depth_sampler) },
|
|
1694
|
+
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(imp_view) },
|
|
1695
|
+
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::Sampler(imp_samp) },
|
|
1696
|
+
wgpu::BindGroupEntry { binding: 6, resource: wgpu::BindingResource::TextureView(&self._scene_stub_view) },
|
|
1697
|
+
],
|
|
1698
|
+
});
|
|
1699
|
+
self.scene_inputs_bg = Some(bg);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/// Dispatch translucent-bucket draws (Transparent, Refractive,
|
|
1703
|
+
/// Additive). Caller owns a render pass set up with a single HDR
|
|
1704
|
+
/// attachment (LoadOp::Load) + depth read-only. Refractive
|
|
1705
|
+
/// materials additionally receive the SceneInputs bind group at
|
|
1706
|
+
/// group 4 — `update_scene_inputs` must have been called this
|
|
1707
|
+
/// frame for that to be non-None.
|
|
1708
|
+
pub fn dispatch_translucent<'pass, F>(
|
|
1709
|
+
&'pass self,
|
|
1710
|
+
pass: &mut wgpu::RenderPass<'pass>,
|
|
1711
|
+
mut mesh_fetch: F,
|
|
1712
|
+
)
|
|
1713
|
+
where F: FnMut(u64, usize) -> Option<(&'pass wgpu::Buffer, &'pass wgpu::Buffer, u32)>
|
|
1714
|
+
{
|
|
1715
|
+
if self.translucent_commands.is_empty() { return; }
|
|
1716
|
+
|
|
1717
|
+
let mut last_material: MaterialHandle = 0;
|
|
1718
|
+
let mut last_reads_scene: bool = false;
|
|
1719
|
+
for cmd in &self.translucent_commands {
|
|
1720
|
+
if cmd.material != last_material {
|
|
1721
|
+
let mat = match self.pipelines.get(cmd.material as usize - 1) {
|
|
1722
|
+
Some(Some(m)) => m,
|
|
1723
|
+
_ => continue,
|
|
1724
|
+
};
|
|
1725
|
+
pass.set_pipeline(&mat.pipeline);
|
|
1726
|
+
pass.set_bind_group(0, &self.per_frame_bg, &[]);
|
|
1727
|
+
pass.set_bind_group(1, &self.per_view_bg, &[]);
|
|
1728
|
+
pass.set_bind_group(2, self.per_material_bg_for(cmd.material), &[]);
|
|
1729
|
+
if mat.reads_scene {
|
|
1730
|
+
if let Some(bg) = self.scene_inputs_bg.as_ref() {
|
|
1731
|
+
pass.set_bind_group(4, bg, &[]);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
last_material = cmd.material;
|
|
1735
|
+
last_reads_scene = mat.reads_scene;
|
|
1736
|
+
}
|
|
1737
|
+
// Re-bind group 4 if the material switches its reads_scene
|
|
1738
|
+
// between subsequent draws — rarely happens with a
|
|
1739
|
+
// stable bucket but keeps the state machine honest.
|
|
1740
|
+
let _ = last_reads_scene;
|
|
1741
|
+
|
|
1742
|
+
if let Some((vb, ib, icount)) = mesh_fetch(cmd.mesh_handle, cmd.mesh_idx) {
|
|
1743
|
+
pass.set_bind_group(3, &self.per_draw_bgs[cmd.draw_slot], &[]);
|
|
1744
|
+
pass.set_vertex_buffer(0, vb.slice(..));
|
|
1745
|
+
pass.set_index_buffer(ib.slice(..), wgpu::IndexFormat::Uint32);
|
|
1746
|
+
let instance_range = self.bind_instance_buffer(pass, &cmd.instance);
|
|
1747
|
+
if instance_range.end > instance_range.start {
|
|
1748
|
+
pass.draw_indexed(0..icount, 0, instance_range);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// =====================================================================
|
|
1756
|
+
// Tests
|
|
1757
|
+
// =====================================================================
|
|
1758
|
+
|
|
1759
|
+
#[cfg(test)]
|
|
1760
|
+
mod tests {
|
|
1761
|
+
use super::*;
|
|
1762
|
+
use super::super::formats;
|
|
1763
|
+
use super::super::types::Vertex3D;
|
|
1764
|
+
|
|
1765
|
+
/// Headless wgpu device. See sibling helpers in `transient.rs` /
|
|
1766
|
+
/// `impulse_field.rs` — same fallback adapter pattern. Returns None
|
|
1767
|
+
/// (test skips gracefully) when no GPU is available.
|
|
1768
|
+
fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> {
|
|
1769
|
+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
1770
|
+
backends: wgpu::Backends::all(),
|
|
1771
|
+
..wgpu::InstanceDescriptor::new_without_display_handle()
|
|
1772
|
+
});
|
|
1773
|
+
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
1774
|
+
power_preference: wgpu::PowerPreference::LowPower,
|
|
1775
|
+
compatible_surface: None,
|
|
1776
|
+
force_fallback_adapter: true,
|
|
1777
|
+
})).ok()?;
|
|
1778
|
+
let (device, queue) = pollster::block_on(adapter.request_device(
|
|
1779
|
+
&wgpu::DeviceDescriptor {
|
|
1780
|
+
label: Some("material-test-device"),
|
|
1781
|
+
required_features: wgpu::Features::empty(),
|
|
1782
|
+
required_limits: wgpu::Limits::downlevel_defaults(),
|
|
1783
|
+
..Default::default()
|
|
1784
|
+
},
|
|
1785
|
+
)).ok()?;
|
|
1786
|
+
Some((device, queue))
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/// Minimal translucent (Bucket::Refractive) WGSL material. Writes
|
|
1790
|
+
/// a constant red+0.5α colour through the alpha-blended HDR target.
|
|
1791
|
+
/// Uses #include "material_abi.wgsl" so the same pipeline-layout
|
|
1792
|
+
/// / per-frame / per-view bindings are validated as production
|
|
1793
|
+
/// materials. Vertex stage transforms via `draw.mvp` so we can
|
|
1794
|
+
/// emit a fullscreen-ish triangle from any geometry.
|
|
1795
|
+
const TRANSLUCENT_WGSL: &str = r#"
|
|
1796
|
+
#include "material_abi.wgsl"
|
|
1797
|
+
|
|
1798
|
+
struct VsOut {
|
|
1799
|
+
@builtin(position) clip_position: vec4<f32>,
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
@vertex
|
|
1803
|
+
fn vs_main(in: VertexInput) -> VsOut {
|
|
1804
|
+
var out: VsOut;
|
|
1805
|
+
out.clip_position = draw.mvp * vec4<f32>(in.position, 1.0);
|
|
1806
|
+
return out;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
@fragment
|
|
1810
|
+
fn fs_main(_in: VsOut) -> TranslucentOut {
|
|
1811
|
+
var out: TranslucentOut;
|
|
1812
|
+
// Red, half alpha — alpha-blended onto whatever the load-op set.
|
|
1813
|
+
out.hdr = vec4<f32>(1.0, 0.0, 0.0, 0.5);
|
|
1814
|
+
return out;
|
|
1815
|
+
}
|
|
1816
|
+
"#;
|
|
1817
|
+
|
|
1818
|
+
/// Create a tiny joint buffer so MaterialSystem::new is happy. The
|
|
1819
|
+
/// per_draw layout binds it at @binding(1); the test material
|
|
1820
|
+
/// doesn't read it but the bind group still has to validate.
|
|
1821
|
+
fn make_joint_buffer(device: &wgpu::Device) -> wgpu::Buffer {
|
|
1822
|
+
device.create_buffer(&wgpu::BufferDescriptor {
|
|
1823
|
+
label: Some("test_joint_buffer"),
|
|
1824
|
+
// 1024 mat4s × 64 B = 64 KiB. Same size as production.
|
|
1825
|
+
size: 65536,
|
|
1826
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
1827
|
+
mapped_at_creation: false,
|
|
1828
|
+
})
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/// Build a single fullscreen triangle covering NDC: three vertices
|
|
1832
|
+
/// at (-1,-1), (3,-1), (-1,3). The pipeline's MVP starts as
|
|
1833
|
+
/// identity (we override it below) so the triangle covers the
|
|
1834
|
+
/// whole viewport.
|
|
1835
|
+
fn make_fullscreen_tri(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer, u32) {
|
|
1836
|
+
let mut verts: [Vertex3D; 3] = [Vertex3D::default(); 3];
|
|
1837
|
+
verts[0].position = [-1.0, -1.0, 0.5];
|
|
1838
|
+
verts[1].position = [ 3.0, -1.0, 0.5];
|
|
1839
|
+
verts[2].position = [-1.0, 3.0, 0.5];
|
|
1840
|
+
// The MaterialPipeline's depth-stencil uses Less; the load-op
|
|
1841
|
+
// for a translucent pass clears to 1.0 (far) by default in
|
|
1842
|
+
// production but in this test we use a depth attachment with
|
|
1843
|
+
// the CLEAR op and clear value 1.0, so anything < 1.0 passes.
|
|
1844
|
+
let vb = device.create_buffer(&wgpu::BufferDescriptor {
|
|
1845
|
+
label: Some("test_tri_vb"),
|
|
1846
|
+
size: std::mem::size_of_val(&verts) as u64,
|
|
1847
|
+
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
|
1848
|
+
mapped_at_creation: false,
|
|
1849
|
+
});
|
|
1850
|
+
let indices: [u32; 3] = [0, 1, 2];
|
|
1851
|
+
let ib = device.create_buffer(&wgpu::BufferDescriptor {
|
|
1852
|
+
label: Some("test_tri_ib"),
|
|
1853
|
+
size: std::mem::size_of_val(&indices) as u64,
|
|
1854
|
+
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
|
1855
|
+
mapped_at_creation: false,
|
|
1856
|
+
});
|
|
1857
|
+
(vb, ib, 3)
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/// EN-006 — translucent dispatch path. Compiles a refractive
|
|
1861
|
+
/// material via `MaterialSystem::compile`, submits one draw,
|
|
1862
|
+
/// dispatches into a 64×64 Rgba16Float HDR render target via
|
|
1863
|
+
/// `dispatch_translucent`, then reads back the HDR and verifies
|
|
1864
|
+
/// the alpha-blended red shows through the cyan background.
|
|
1865
|
+
///
|
|
1866
|
+
/// This is the scoped-down version of the acceptance scenario:
|
|
1867
|
+
/// it exercises the dispatch site and pipeline-layout / blend-state
|
|
1868
|
+
/// wiring without standing up the full `Renderer` (which requires
|
|
1869
|
+
/// a surface and ~30 other resources). What this DOES cover:
|
|
1870
|
+
/// - MaterialSystem::compile against a refractive WGSL source
|
|
1871
|
+
/// - dispatch_translucent's pipeline / bind-group binding loop
|
|
1872
|
+
/// - alpha blending via the translucent target's BlendState
|
|
1873
|
+
/// - the per-draw / per-view / per-frame UBO writes
|
|
1874
|
+
/// What it does NOT cover:
|
|
1875
|
+
/// - The scene-color snapshot (test material has reads_scene=false
|
|
1876
|
+
/// so group 4 doesn't bind; the e2e copy_texture_to_texture
|
|
1877
|
+
/// before this dispatch is exercised by the depth-snapshot test
|
|
1878
|
+
/// in transient.rs).
|
|
1879
|
+
/// - Sort order across multiple translucent draws (single-draw test).
|
|
1880
|
+
/// Skipped on adapters where `try_create_device` returns None.
|
|
1881
|
+
#[test]
|
|
1882
|
+
fn dispatch_translucent_alpha_blends_into_hdr() {
|
|
1883
|
+
let Some((device, queue)) = try_create_device() else { return; };
|
|
1884
|
+
let joint_buf = make_joint_buffer(&device);
|
|
1885
|
+
let mut sys = MaterialSystem::new(&device, &queue, &joint_buf);
|
|
1886
|
+
|
|
1887
|
+
// Compile a refractive (translucent) material. Use the engine's
|
|
1888
|
+
// production format constants so the pipeline matches what
|
|
1889
|
+
// Renderer::new would have produced.
|
|
1890
|
+
let handle = sys.compile(
|
|
1891
|
+
&device,
|
|
1892
|
+
TRANSLUCENT_WGSL,
|
|
1893
|
+
FragmentProfile::Translucent,
|
|
1894
|
+
Bucket::Transparent,
|
|
1895
|
+
false, // reads_scene
|
|
1896
|
+
false, // wants_instancing
|
|
1897
|
+
wgpu::TextureFormat::Rgba16Float, // hdr_format
|
|
1898
|
+
wgpu::TextureFormat::Rg8Unorm, // material_format (unused in translucent)
|
|
1899
|
+
wgpu::TextureFormat::Rg16Float, // velocity_format (unused)
|
|
1900
|
+
wgpu::TextureFormat::Rgba8Unorm, // albedo_format (unused)
|
|
1901
|
+
formats::DEPTH_FORMAT,
|
|
1902
|
+
).expect("translucent material compiles");
|
|
1903
|
+
assert!(handle != 0, "compile returns a 1-based handle");
|
|
1904
|
+
|
|
1905
|
+
// Frame uniforms — zeros are fine for a constant-colour shader.
|
|
1906
|
+
let pf = PerFrameUniforms {
|
|
1907
|
+
time: 0.0, delta_time: 0.0, frame_index: 0, _pad0: 0,
|
|
1908
|
+
screen_resolution: [64.0, 64.0], render_resolution: [64.0, 64.0],
|
|
1909
|
+
taa_jitter: [0.0; 2], _pad1: [0.0; 2], wind: [0.0; 4],
|
|
1910
|
+
};
|
|
1911
|
+
let pv = bytemuck::Zeroable::zeroed();
|
|
1912
|
+
sys.update_frame_uniforms(&queue, &pf, &pv);
|
|
1913
|
+
sys.reset_draw_slot();
|
|
1914
|
+
|
|
1915
|
+
// MVP = identity so the fullscreen tri stays in NDC.
|
|
1916
|
+
let identity = [
|
|
1917
|
+
[1.0, 0.0, 0.0, 0.0],
|
|
1918
|
+
[0.0, 1.0, 0.0, 0.0],
|
|
1919
|
+
[0.0, 0.0, 1.0, 0.0],
|
|
1920
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
1921
|
+
];
|
|
1922
|
+
let (vb, ib, icount) = make_fullscreen_tri(&device);
|
|
1923
|
+
|
|
1924
|
+
sys.submit_draw(
|
|
1925
|
+
&device, &queue, &joint_buf,
|
|
1926
|
+
handle, /* mesh_handle */ 1, /* mesh_idx */ 0,
|
|
1927
|
+
identity, identity, identity,
|
|
1928
|
+
[1.0; 4], [0; 4],
|
|
1929
|
+
);
|
|
1930
|
+
assert_eq!(sys.translucent_commands.len(), 1, "draw queued in translucent bucket");
|
|
1931
|
+
|
|
1932
|
+
// Build the HDR + depth render targets for the dispatch.
|
|
1933
|
+
let (rt_w, rt_h) = (64u32, 64u32);
|
|
1934
|
+
let hdr_rt = device.create_texture(&wgpu::TextureDescriptor {
|
|
1935
|
+
label: Some("test_hdr_rt"),
|
|
1936
|
+
size: wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
1937
|
+
mip_level_count: 1, sample_count: 1,
|
|
1938
|
+
dimension: wgpu::TextureDimension::D2,
|
|
1939
|
+
format: wgpu::TextureFormat::Rgba16Float,
|
|
1940
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
|
1941
|
+
view_formats: &[],
|
|
1942
|
+
});
|
|
1943
|
+
let hdr_view = hdr_rt.create_view(&Default::default());
|
|
1944
|
+
let depth_rt = device.create_texture(&wgpu::TextureDescriptor {
|
|
1945
|
+
label: Some("test_depth_rt"),
|
|
1946
|
+
size: wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
1947
|
+
mip_level_count: 1, sample_count: 1,
|
|
1948
|
+
dimension: wgpu::TextureDimension::D2,
|
|
1949
|
+
format: formats::DEPTH_FORMAT,
|
|
1950
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
1951
|
+
view_formats: &[],
|
|
1952
|
+
});
|
|
1953
|
+
let depth_view = depth_rt.create_view(&Default::default());
|
|
1954
|
+
|
|
1955
|
+
// Pre-clear HDR to opaque cyan so we can detect alpha-blended red:
|
|
1956
|
+
// (1, 0, 0, 0.5) over (0, 1, 1, 1) → (0.5, 0.5, 0.5, 1.0).
|
|
1957
|
+
let bg_color = wgpu::Color { r: 0.0, g: 1.0, b: 1.0, a: 1.0 };
|
|
1958
|
+
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
1959
|
+
label: Some("test_translucent_encoder"),
|
|
1960
|
+
});
|
|
1961
|
+
{
|
|
1962
|
+
// Clear HDR + depth in one pass.
|
|
1963
|
+
let _clear = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
1964
|
+
label: Some("test_clear_pass"),
|
|
1965
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
1966
|
+
view: &hdr_view,
|
|
1967
|
+
resolve_target: None,
|
|
1968
|
+
depth_slice: None,
|
|
1969
|
+
ops: wgpu::Operations {
|
|
1970
|
+
load: wgpu::LoadOp::Clear(bg_color),
|
|
1971
|
+
store: wgpu::StoreOp::Store,
|
|
1972
|
+
},
|
|
1973
|
+
})],
|
|
1974
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
1975
|
+
view: &depth_view,
|
|
1976
|
+
depth_ops: Some(wgpu::Operations {
|
|
1977
|
+
load: wgpu::LoadOp::Clear(1.0),
|
|
1978
|
+
store: wgpu::StoreOp::Store,
|
|
1979
|
+
}),
|
|
1980
|
+
stencil_ops: None,
|
|
1981
|
+
}),
|
|
1982
|
+
timestamp_writes: None,
|
|
1983
|
+
occlusion_query_set: None,
|
|
1984
|
+
multiview_mask: None,
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
// Translucent dispatch — Load (don't clear) HDR; depth read-only.
|
|
1988
|
+
{
|
|
1989
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
1990
|
+
label: Some("test_translucent_pass"),
|
|
1991
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
1992
|
+
view: &hdr_view,
|
|
1993
|
+
resolve_target: None,
|
|
1994
|
+
depth_slice: None,
|
|
1995
|
+
ops: wgpu::Operations {
|
|
1996
|
+
load: wgpu::LoadOp::Load,
|
|
1997
|
+
store: wgpu::StoreOp::Store,
|
|
1998
|
+
},
|
|
1999
|
+
})],
|
|
2000
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
2001
|
+
view: &depth_view,
|
|
2002
|
+
depth_ops: Some(wgpu::Operations {
|
|
2003
|
+
load: wgpu::LoadOp::Load,
|
|
2004
|
+
store: wgpu::StoreOp::Store,
|
|
2005
|
+
}),
|
|
2006
|
+
stencil_ops: None,
|
|
2007
|
+
}),
|
|
2008
|
+
timestamp_writes: None,
|
|
2009
|
+
occlusion_query_set: None,
|
|
2010
|
+
multiview_mask: None,
|
|
2011
|
+
});
|
|
2012
|
+
sys.dispatch_translucent(&mut pass, |mh, _idx| {
|
|
2013
|
+
if mh == 1 { Some((&vb, &ib, icount)) } else { None }
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// Read back the HDR target (Rgba16Float = 8 B / texel).
|
|
2018
|
+
let bpr_unpadded = rt_w * 8;
|
|
2019
|
+
let bpr = (bpr_unpadded + 255) & !255;
|
|
2020
|
+
let buf_size = (bpr * rt_h) as u64;
|
|
2021
|
+
let staging = device.create_buffer(&wgpu::BufferDescriptor {
|
|
2022
|
+
label: Some("test_hdr_staging"),
|
|
2023
|
+
size: buf_size,
|
|
2024
|
+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
|
2025
|
+
mapped_at_creation: false,
|
|
2026
|
+
});
|
|
2027
|
+
encoder.copy_texture_to_buffer(
|
|
2028
|
+
wgpu::TexelCopyTextureInfo {
|
|
2029
|
+
texture: &hdr_rt,
|
|
2030
|
+
mip_level: 0,
|
|
2031
|
+
origin: wgpu::Origin3d::ZERO,
|
|
2032
|
+
aspect: wgpu::TextureAspect::All,
|
|
2033
|
+
},
|
|
2034
|
+
wgpu::TexelCopyBufferInfo {
|
|
2035
|
+
buffer: &staging,
|
|
2036
|
+
layout: wgpu::TexelCopyBufferLayout {
|
|
2037
|
+
offset: 0,
|
|
2038
|
+
bytes_per_row: Some(bpr),
|
|
2039
|
+
rows_per_image: Some(rt_h),
|
|
2040
|
+
},
|
|
2041
|
+
},
|
|
2042
|
+
wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
2043
|
+
);
|
|
2044
|
+
queue.submit(std::iter::once(encoder.finish()));
|
|
2045
|
+
|
|
2046
|
+
let slice = staging.slice(..);
|
|
2047
|
+
let (tx, rx) = std::sync::mpsc::channel();
|
|
2048
|
+
slice.map_async(wgpu::MapMode::Read, move |r| { let _ = tx.send(r); });
|
|
2049
|
+
let _ = device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
|
|
2050
|
+
rx.recv().expect("map sender").expect("map failed");
|
|
2051
|
+
let data = slice.get_mapped_range();
|
|
2052
|
+
|
|
2053
|
+
// Sample the centre texel. Rgba16Float = 4 × half = 8 bytes.
|
|
2054
|
+
// Use half::f16 via bytemuck cast through u16 then manual decode.
|
|
2055
|
+
let cx = rt_w / 2;
|
|
2056
|
+
let cy = rt_h / 2;
|
|
2057
|
+
let row_start = (cy * bpr) as usize;
|
|
2058
|
+
let texel_start = row_start + (cx as usize) * 8;
|
|
2059
|
+
let halfs: [u16; 4] = [
|
|
2060
|
+
u16::from_le_bytes([data[texel_start], data[texel_start + 1]]),
|
|
2061
|
+
u16::from_le_bytes([data[texel_start + 2], data[texel_start + 3]]),
|
|
2062
|
+
u16::from_le_bytes([data[texel_start + 4], data[texel_start + 5]]),
|
|
2063
|
+
u16::from_le_bytes([data[texel_start + 6], data[texel_start + 7]]),
|
|
2064
|
+
];
|
|
2065
|
+
drop(data);
|
|
2066
|
+
staging.unmap();
|
|
2067
|
+
|
|
2068
|
+
let r = f16_to_f32(halfs[0]);
|
|
2069
|
+
let g = f16_to_f32(halfs[1]);
|
|
2070
|
+
let b = f16_to_f32(halfs[2]);
|
|
2071
|
+
let _a = f16_to_f32(halfs[3]);
|
|
2072
|
+
|
|
2073
|
+
// Expected SrcAlpha/OneMinusSrcAlpha blend:
|
|
2074
|
+
// src = (1, 0, 0, 0.5)
|
|
2075
|
+
// dst = (0, 1, 1, 1)
|
|
2076
|
+
// out.rgb = src.rgb * src.a + dst.rgb * (1 - src.a)
|
|
2077
|
+
// = (0.5, 0.5, 0.5)
|
|
2078
|
+
// Allow 1/256 tolerance for half-precision round-trip.
|
|
2079
|
+
let eps = 0.02;
|
|
2080
|
+
assert!((r - 0.5).abs() < eps, "red channel = {} (expected ~0.5)", r);
|
|
2081
|
+
assert!((g - 0.5).abs() < eps, "green channel = {} (expected ~0.5)", g);
|
|
2082
|
+
assert!((b - 0.5).abs() < eps, "blue channel = {} (expected ~0.5)", b);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
/// IEEE-754 binary16 → binary32. We don't pull in the `half` crate
|
|
2086
|
+
/// for a single readback; the manual decode is short and exact for
|
|
2087
|
+
/// the values this test produces (no NaN / Inf / subnormal cases).
|
|
2088
|
+
fn f16_to_f32(bits: u16) -> f32 {
|
|
2089
|
+
let sign = (bits >> 15) & 0x1;
|
|
2090
|
+
let exp = (bits >> 10) & 0x1f;
|
|
2091
|
+
let frac = bits & 0x3ff;
|
|
2092
|
+
if exp == 0 {
|
|
2093
|
+
if frac == 0 {
|
|
2094
|
+
return if sign == 1 { -0.0 } else { 0.0 };
|
|
2095
|
+
}
|
|
2096
|
+
// Subnormal — not expected in this test; decode for completeness.
|
|
2097
|
+
let f = (frac as f32) / 1024.0 * (2.0f32).powi(-14);
|
|
2098
|
+
return if sign == 1 { -f } else { f };
|
|
2099
|
+
}
|
|
2100
|
+
if exp == 0x1f {
|
|
2101
|
+
return f32::NAN; // Inf or NaN — unexpected in this test.
|
|
2102
|
+
}
|
|
2103
|
+
let f = (1.0 + (frac as f32) / 1024.0) * (2.0f32).powi(exp as i32 - 15);
|
|
2104
|
+
if sign == 1 { -f } else { f }
|
|
2105
|
+
}
|
|
2106
|
+
}
|