JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
SpecularPathCache.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2026 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
22#include "JPLSpatial/Core.h"
29
30#include <algorithm>
31#include <functional>
32#include <memory_resource>
33#include <span>
34#include <ranges>
35#include <unordered_map>
36#include <utility>
37#include <vector>
38
39namespace JPL
40{
41 //======================================================================
43 // Specular path cache is used to cache specular reflection paths from
44 // previous frames, thereby reducing the cost involved in finding paths
45 // on future frames, allowing fewer rays to be traced,
46 // and improving performance
47 template<class Vec3>
49 {
50 public:
52 using SPMap = std::pmr::unordered_map<SpecularPathId, SpecularPathData<Vec3>>;
53 using SPEntry = std::pair<SpecularPathId, SpecularPathData<Vec3>>;
54 using SPArray = std::pmr::vector<SPEntry>;
55
56 static constexpr std::size_t cMaxOrder = 16; // TODO: make sure to use the same value across specular path classes
57
58 //static_assert(sizeof(SpecularPathData<Vec3>) == JPL_CACHE_LINE_SIZE); // for no current reason
59
60 public:
61 SpecularPathCache() = default;
63
64 [[nodiscard]] JPL_INLINE std::size_t GetNumPaths() const { return mValidPaths.size() + mInvalidPaths.size(); }
65 [[nodiscard]] JPL_INLINE std::size_t GetNumValidPaths() const { return mValidPaths.size(); }
66 [[nodiscard]] JPL_INLINE std::size_t GetNumInvalidPaths() const { return mInvalidPaths.size(); }
67
68 // TODO: we might want to switch to some kind of sparse array with HashTable
69 // because this Contains check is quite hot
71
72 // Add path to cache
73 inline void Add(SpecularPathId path,
74 std::span<const int> nodes,
75 const Vec3& imageSource, // last image source vector relative to receiver
76 const EnergyBands& energy,
77 bool isPathValid);
78
79 // TODO: take into account that Reciever's movement requires only revalidation, while Source's movement requires ISs regeneration
80
81 // Paths should be revalidated at the beginning of each simulation frame
82 // in case any source, listener, or scene geometry moved during the time interval.
83 //
84 // The paths that were invalid on the previous frame are removed from the cache,
85 // while the valid paths are revalidated and stored again in the cache.
86 template<class SceneType>
87 void Validate(const SceneType& scene);
88
89 JPL_INLINE std::span<const SPEntry> GetValidPaths() const { return mValidPaths; }
90 JPL_INLINE std::span<const SPEntry> GetInvalidPaths() const { return mInvalidPaths; }
91
93 {
94 ClearInvalidPaths();
95 ClearValidPaths();
96 }
97
98 private:
99 // Delete previously invalidated paths
100 JPL_INLINE void ClearInvalidPaths()
101 {
102 for (auto&& [pathKey, path] : mInvalidPaths)
103 {
104 DeallocatePathData(path);
105 }
106 mInvalidPaths.clear();
107 }
108
109 // Delete valid paths
110 JPL_INLINE void ClearValidPaths()
111 {
112 for (auto&& [pathKey, path] : mValidPaths)
113 {
114 DeallocatePathData(path);
115 }
116 mValidPaths.clear();
117 }
118
119 JPL_INLINE void DeallocatePathData(SpecularPathData<Vec3>& path)
120 {
121 GetDefaultPmrAllocator<int>().deallocate(path.Nodes.data(), path.Nodes.size());
122 }
123
124 private:
125 // Note: we need specular path cache per source
126 // (technically per source-listner pair, to compute IR later)
127
128 // TODO: maybe use hash-table to speed up `Contains` queries that we do a lot
129 SPArray mValidPaths{ GetDefaultMemoryResource() };
130 SPArray mInvalidPaths{ GetDefaultMemoryResource() };
131 };
132} // namespace JPL
133
134//==============================================================================
135//
136// Code beyond this point is implementation detail...
137//
138//==============================================================================
139
140namespace JPL
141{
142 template<class Vec3>
144 {
145 // TODO: we might want to use frame allocator (just let the user to provide one)
147 for (SPArray& pathsArray : { std::ref(mValidPaths), std::ref(mInvalidPaths) })
148 {
149 for (auto&& [id, data] : pathsArray)
150 {
151 allocator.deallocate(data.Nodes.data(), data.Nodes.size());
152 }
153 }
154 }
155
156 template<class Vec3>
158 {
159 auto hasId = [pathId](const SPEntry& entry) { return entry.first == pathId; };
160 return std::ranges::find_if(mValidPaths, hasId) != std::ranges::end(mValidPaths)
161 || std::ranges::find_if(mInvalidPaths, hasId) != std::ranges::end(mInvalidPaths);
162 }
163
164 template<class Vec3>
165 inline void SpecularPathCache<Vec3>::Add(SpecularPathId path, std::span<const int> nodes, const Vec3& imageSource, const EnergyBands& energy, bool isPathValid)
166 {
167 JPL_ASSERT(not Contains(path));
168
169 // TODO: frame allocator (?)
170 int* nodeData = GetDefaultPmrAllocator<int>().allocate(nodes.size());
171 std::copy(nodes.begin(), nodes.end(), nodeData);
172
173 auto& pathsArray = isPathValid ? mValidPaths : mInvalidPaths;
174
175 pathsArray.emplace_back(path,
177 .Nodes = std::span(nodeData, nodes.size()),
178 .Energy = energy,
179 .ImageSource = imageSource,
180 .bValid = isPathValid
181 });
182 }
183
184 template<class Vec3>
185 template<class SceneType>
187 {
188 // TODO:
189 // if we could store paths as a tree,
190 // then if first node is invalidated,
191 // we could eliminate a whole branch
192
193 // Delete previously invalidated paths
194 ClearInvalidPaths();
195
196 // Validated previously valid paths
197 for (auto&& [pathKey, path] : mValidPaths)
198 {
199 JPL_ASSERT(path.Nodes.size() > 2,
200 "Path contains only source and listener verices, no reflections.");
201
202 JPL_ASSERT(path.Nodes.size() <= cMaxOrder + 2,
203 "Path is deeper than max order allowed.");
204
205 // The position of both, source or receiver may change,
206 // in case of backtracing, ImageSource[0] is the listener, so we need to use the updated
207 // positions of listener and sources and revalidate image sources here via the same surfaces
209 }
210
211 // Move invalidated paths to invalid array
212 {
213 auto isValid = [](const SPEntry& entry)
214 {
215 return entry.second.bValid;
216 };
217
218 auto invalidRange = std::ranges::partition(mValidPaths, isValid);
219
220#if 0
221 //? do we need this, or should we just immediately delete the invalid paths
222 mInvalidPaths.reserve(mInvalidPaths.size() + invalidRange.size());
223 std::ranges::move(invalidRange, std::back_inserter(mInvalidPaths));
224#else
225 for (auto&& [pathKey, path] : invalidRange)
226 {
227 DeallocatePathData(path);
228 }
229#endif
230 mValidPaths.erase(invalidRange.begin(), mValidPaths.end());
231 }
232 }
233
234} // namespace JPL
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
A very experimental scene interface for specular ray tracing.
Definition SceneInterface.h:35
static bool ValidatePath(const SceneType &scene, std::span< const int > triCache, std::span< const Vec3 > imageSources, const Vec3 &receiver)
Definition SceneInterface.h:82
A very experimental specular path cache.
Definition SpecularPathCache.h:49
std::pmr::unordered_map< SpecularPathId, SpecularPathData< Vec3 > > SPMap
Definition SpecularPathCache.h:52
JPL_INLINE bool Contains(SpecularPathId pathId) const
Definition SpecularPathCache.h:157
JPL_INLINE std::size_t GetNumPaths() const
Definition SpecularPathCache.h:64
std::pair< SpecularPathId, SpecularPathData< Vec3 > > SPEntry
Definition SpecularPathCache.h:53
JPL_INLINE std::span< const SPEntry > GetValidPaths() const
Definition SpecularPathCache.h:89
void Add(SpecularPathId path, std::span< const int > nodes, const Vec3 &imageSource, const EnergyBands &energy, bool isPathValid)
Definition SpecularPathCache.h:165
JPL_INLINE std::size_t GetNumInvalidPaths() const
Definition SpecularPathCache.h:66
JPL_INLINE std::span< const SPEntry > GetInvalidPaths() const
Definition SpecularPathCache.h:90
static constexpr std::size_t cMaxOrder
Definition SpecularPathCache.h:56
JPL_INLINE std::size_t GetNumValidPaths() const
Definition SpecularPathCache.h:65
JPL_INLINE void Clear()
Definition SpecularPathCache.h:92
std::pmr::vector< SPEntry > SPArray
Definition SpecularPathCache.h:54
void Validate(const SceneType &scene)
Definition SpecularPathCache.h:186
~SpecularPathCache() noexcept
Definition SpecularPathCache.h:143
Definition AcousticMaterial.h:36
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition ChannelMap.h:268
Definition SpecularPath.h:57
std::span< int > Nodes
Definition SpecularPath.h:59
A very experimental specular path definition.
Definition SpecularPath.h:40
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97