Skip to content
Permalink
Browse files
Cache PannerNode's azimuth, elevation and coneGain
https://bugs.webkit.org/show_bug.cgi?id=231314

Reviewed by Eric Carlson.

Cache PannerNode's azimuth, elevation and coneGain for better performance. I have noticed while profiling
https://downloads.scirra.com/labs/bugs/safaripannerquality/ that PannerNode::process() spends most of its
CPU time under PannerNode::calculateAzimuthElevation(). We shouldn't have to re-calculate those properties
for every rendering quantum.

* Modules/webaudio/AudioListener.cpp:
(WebCore::AudioListener::updateDirtyState):
* Modules/webaudio/AudioListener.h:
(WebCore::AudioListener::isPositionDirty const):
(WebCore::AudioListener::isOrientationDirty const):
(WebCore::AudioListener::isUpVectorDirty const):
* Modules/webaudio/BaseAudioContext.cpp:
(WebCore::BaseAudioContext::handlePreRenderTasks):
* Modules/webaudio/PannerNode.cpp:
(WebCore::PannerNode::process):
(WebCore::PannerNode::processSampleAccurateValues):
(WebCore::PannerNode::setDistanceModelForBindings):
(WebCore::PannerNode::setRefDistanceForBindings):
(WebCore::PannerNode::setMaxDistanceForBindings):
(WebCore::PannerNode::setRolloffFactorForBindings):
(WebCore::PannerNode::setConeOuterGainForBindings):
(WebCore::PannerNode::setConeOuterAngleForBindings):
(WebCore::PannerNode::setConeInnerAngleForBindings):
(WebCore::PannerNode::calculateAzimuthElevation):
(WebCore::PannerNode::azimuthElevation const):
(WebCore::PannerNode::calculateDistanceConeGain):
(WebCore::PannerNode::distanceConeGain):
(WebCore::PannerNode::invalidateCachedPropertiesIfNecessary):
(WebCore::PannerNode::azimuthElevation): Deleted.
* Modules/webaudio/PannerNode.h:
* platform/audio/Cone.cpp:
(WebCore::ConeEffect::gain const):
(WebCore::ConeEffect::gain): Deleted.
* platform/audio/Cone.h:
* platform/audio/Distance.cpp:
(WebCore::DistanceEffect::gain const):
(WebCore::DistanceEffect::linearGain const):
(WebCore::DistanceEffect::inverseGain const):
(WebCore::DistanceEffect::exponentialGain const):
(WebCore::DistanceEffect::gain): Deleted.
(WebCore::DistanceEffect::linearGain): Deleted.
(WebCore::DistanceEffect::inverseGain): Deleted.
(WebCore::DistanceEffect::exponentialGain): Deleted.
* platform/audio/Distance.h:


Canonical link: https://commits.webkit.org/242662@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@283740 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
cdumez committed Oct 7, 2021
1 parent dc0e87b commit 7252835f5b5f106104ad8b3115aefb3dad15dc88
Showing 10 changed files with 168 additions and 38 deletions.
@@ -1,3 +1,55 @@
2021-10-07 Chris Dumez <cdumez@apple.com>

Cache PannerNode's azimuth, elevation and coneGain
https://bugs.webkit.org/show_bug.cgi?id=231314

Reviewed by Eric Carlson.

Cache PannerNode's azimuth, elevation and coneGain for better performance. I have noticed while profiling
https://downloads.scirra.com/labs/bugs/safaripannerquality/ that PannerNode::process() spends most of its
CPU time under PannerNode::calculateAzimuthElevation(). We shouldn't have to re-calculate those properties
for every rendering quantum.

* Modules/webaudio/AudioListener.cpp:
(WebCore::AudioListener::updateDirtyState):
* Modules/webaudio/AudioListener.h:
(WebCore::AudioListener::isPositionDirty const):
(WebCore::AudioListener::isOrientationDirty const):
(WebCore::AudioListener::isUpVectorDirty const):
* Modules/webaudio/BaseAudioContext.cpp:
(WebCore::BaseAudioContext::handlePreRenderTasks):
* Modules/webaudio/PannerNode.cpp:
(WebCore::PannerNode::process):
(WebCore::PannerNode::processSampleAccurateValues):
(WebCore::PannerNode::setDistanceModelForBindings):
(WebCore::PannerNode::setRefDistanceForBindings):
(WebCore::PannerNode::setMaxDistanceForBindings):
(WebCore::PannerNode::setRolloffFactorForBindings):
(WebCore::PannerNode::setConeOuterGainForBindings):
(WebCore::PannerNode::setConeOuterAngleForBindings):
(WebCore::PannerNode::setConeInnerAngleForBindings):
(WebCore::PannerNode::calculateAzimuthElevation):
(WebCore::PannerNode::azimuthElevation const):
(WebCore::PannerNode::calculateDistanceConeGain):
(WebCore::PannerNode::distanceConeGain):
(WebCore::PannerNode::invalidateCachedPropertiesIfNecessary):
(WebCore::PannerNode::azimuthElevation): Deleted.
* Modules/webaudio/PannerNode.h:
* platform/audio/Cone.cpp:
(WebCore::ConeEffect::gain const):
(WebCore::ConeEffect::gain): Deleted.
* platform/audio/Cone.h:
* platform/audio/Distance.cpp:
(WebCore::DistanceEffect::gain const):
(WebCore::DistanceEffect::linearGain const):
(WebCore::DistanceEffect::inverseGain const):
(WebCore::DistanceEffect::exponentialGain const):
(WebCore::DistanceEffect::gain): Deleted.
(WebCore::DistanceEffect::linearGain): Deleted.
(WebCore::DistanceEffect::inverseGain): Deleted.
(WebCore::DistanceEffect::exponentialGain): Deleted.
* platform/audio/Distance.h:

2021-10-07 Michael Catanzaro <mcatanzaro@gnome.org>

Misc compiler warning fixes, October 2021
@@ -123,6 +123,20 @@ void AudioListener::updateValuesIfNeeded(size_t framesToProcess)
}
}

void AudioListener::updateDirtyState()
{
ASSERT(!isMainThread());

auto lastPosition = std::exchange(m_lastPosition, position());
m_isPositionDirty = lastPosition != m_lastPosition;

auto lastOrientation = std::exchange(m_lastOrientation, orientation());
m_isOrientationDirty = lastOrientation != m_lastOrientation;

auto lastUpVector = std::exchange(m_lastUpVector, upVector());
m_isUpVectorDirty = lastUpVector != m_lastUpVector;
}

const float* AudioListener::positionXValues(size_t framesToProcess)
{
updateValuesIfNeeded(framesToProcess);
@@ -87,6 +87,11 @@ class AudioListener : public RefCounted<AudioListener> {

void updateValuesIfNeeded(size_t framesToProcess);

void updateDirtyState();
bool isPositionDirty() const { return m_isPositionDirty; }
bool isOrientationDirty() const { return m_isOrientationDirty; }
bool isUpVectorDirty() const { return m_isUpVectorDirty; }

protected:
explicit AudioListener(BaseAudioContext&);

@@ -116,6 +121,13 @@ class AudioListener : public RefCounted<AudioListener> {
AudioFloatArray m_upXValues;
AudioFloatArray m_upYValues;
AudioFloatArray m_upZValues;

FloatPoint3D m_lastPosition;
FloatPoint3D m_lastOrientation;
FloatPoint3D m_lastUpVector;
bool m_isPositionDirty { false };
bool m_isOrientationDirty { false };
bool m_isUpVectorDirty { false };
};

} // namespace WebCore
@@ -549,6 +549,8 @@ void BaseAudioContext::handlePreRenderTasks(const AudioIOPosition& outputPositio

updateAutomaticPullNodes();
m_outputPosition = outputPosition;

m_listener->updateDirtyState();
}
}

@@ -148,10 +148,10 @@ void PannerNode::process(size_t framesToProcess)
return;
}

invalidateCachedPropertiesIfNecessary();

// Apply the panning effect.
double azimuth;
double elevation;
azimuthElevation(&azimuth, &elevation);
auto [azimuth, elevation] = azimuthElevation();
m_panner->pan(azimuth, elevation, source, destination, framesToProcess);

// Get the distance and cone gain.
@@ -226,10 +226,12 @@ void PannerNode::processSampleAccurateValues(AudioBus* destination, const AudioB
FloatPoint3D listenerFront(forwardX[k], forwardY[k], forwardZ[k]);
FloatPoint3D listenerUp(upX[k], upY[k], upZ[k]);

calculateAzimuthElevation(&azimuth[k], &elevation[k], pannerPosition, listenerPosition, listenerFront, listenerUp);
auto [calculatedAzimuth, calculatedElevation] = calculateAzimuthElevation(pannerPosition, listenerPosition, listenerFront, listenerUp);
azimuth[k] = calculatedAzimuth;
elevation[k] = calculatedElevation;

// Get distance and cone gain
totalGain[k] = calculateDistanceConeGain(pannerPosition, orientation, listenerPosition);
totalGain[k] = calculateDistanceConeGain(pannerPosition, orientation, listenerPosition, m_distanceEffect, m_coneEffect);
}

m_panner->panWithSampleAccurateValues(azimuth, elevation, source, destination, framesToProcess);
@@ -340,7 +342,11 @@ void PannerNode::setDistanceModelForBindings(DistanceModelType model)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_distanceEffect.model() == model)
return;

m_distanceEffect.setModel(model, true);
m_cachedConeGain = std::nullopt;
}

ExceptionOr<void> PannerNode::setRefDistanceForBindings(double refDistance)
@@ -353,7 +359,11 @@ ExceptionOr<void> PannerNode::setRefDistanceForBindings(double refDistance)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_distanceEffect.refDistance() == refDistance)
return { };

m_distanceEffect.setRefDistance(refDistance);
m_cachedConeGain = std::nullopt;
return { };
}

@@ -367,7 +377,11 @@ ExceptionOr<void> PannerNode::setMaxDistanceForBindings(double maxDistance)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_distanceEffect.maxDistance() == maxDistance)
return { };

m_distanceEffect.setMaxDistance(maxDistance);
m_cachedConeGain = std::nullopt;
return { };
}

@@ -381,7 +395,11 @@ ExceptionOr<void> PannerNode::setRolloffFactorForBindings(double rolloffFactor)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_distanceEffect.rolloffFactor() == rolloffFactor)
return { };

m_distanceEffect.setRolloffFactor(rolloffFactor);
m_cachedConeGain = std::nullopt;
return { };
}

@@ -395,7 +413,11 @@ ExceptionOr<void> PannerNode::setConeOuterGainForBindings(double gain)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_coneEffect.outerGain() == gain)
return { };

m_coneEffect.setOuterGain(gain);
m_cachedConeGain = std::nullopt;
return { };
}

@@ -406,7 +428,11 @@ void PannerNode::setConeOuterAngleForBindings(double angle)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_coneEffect.outerAngle() == angle)
return;

m_coneEffect.setOuterAngle(angle);
m_cachedConeGain = std::nullopt;
}

void PannerNode::setConeInnerAngleForBindings(double angle)
@@ -416,7 +442,11 @@ void PannerNode::setConeInnerAngleForBindings(double angle)
// This synchronizes with process().
Locker locker { m_processLock };

if (m_coneEffect.innerAngle() == angle)
return;

m_coneEffect.setInnerAngle(angle);
m_cachedConeGain = std::nullopt;
}

ExceptionOr<void> PannerNode::setChannelCount(unsigned channelCount)
@@ -439,18 +469,14 @@ ExceptionOr<void> PannerNode::setChannelCountMode(ChannelCountMode mode)
return AudioNode::setChannelCountMode(mode);
}

void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation, const FloatPoint3D& position, const FloatPoint3D& listenerPosition, const FloatPoint3D& listenerFront, const FloatPoint3D& listenerUp)
auto PannerNode::calculateAzimuthElevation(const FloatPoint3D& position, const FloatPoint3D& listenerPosition, const FloatPoint3D& listenerFront, const FloatPoint3D& listenerUp) -> AzimuthElevation
{
// FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.

// Calculate the source-listener vector
FloatPoint3D sourceListener = position - listenerPosition;

if (sourceListener.isZero()) {
// degenerate case if source and listener are at the same point
*outAzimuth = 0.0;
*outElevation = 0.0;
return;
return { };
}

sourceListener.normalize();
@@ -492,17 +518,16 @@ void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevat
else if (elevation < -90.0)
elevation = -180.0 - elevation;

if (outAzimuth)
*outAzimuth = azimuth;
if (outElevation)
*outElevation = elevation;
return { azimuth, elevation };
}

void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
auto PannerNode::azimuthElevation() -> const AzimuthElevation&
{
ASSERT(context().isAudioThread());

calculateAzimuthElevation(outAzimuth, outElevation, position(), listener().position(), listener().orientation(), listener().upVector());
auto& listener = this->listener();
if (!m_cachedAzimuthElevation)
m_cachedAzimuthElevation = calculateAzimuthElevation(position(), listener.position(), listener.orientation(), listener.upVector());
return *m_cachedAzimuthElevation;
}

bool PannerNode::requiresTailProcessing() const
@@ -516,22 +541,21 @@ bool PannerNode::requiresTailProcessing() const
return !m_panner || m_panner->requiresTailProcessing();
}

float PannerNode::calculateDistanceConeGain(const FloatPoint3D& sourcePosition, const FloatPoint3D& orientation, const FloatPoint3D& listenerPosition)
float PannerNode::calculateDistanceConeGain(const FloatPoint3D& sourcePosition, const FloatPoint3D& orientation, const FloatPoint3D& listenerPosition, const DistanceEffect& distanceEffect, const ConeEffect& coneEffect)
{
double listenerDistance = sourcePosition.distanceTo(listenerPosition);
double distanceGain = m_distanceEffect.gain(listenerDistance);

// FIXME: could optimize by caching coneGain
double coneGain = m_coneEffect.gain(sourcePosition, orientation, listenerPosition);
double distanceGain = distanceEffect.gain(listenerDistance);
double coneGain = coneEffect.gain(sourcePosition, orientation, listenerPosition);

return float(distanceGain * coneGain);
}

float PannerNode::distanceConeGain()
{
ASSERT(context().isAudioThread());

return calculateDistanceConeGain(position(), orientation(), listener().position());
if (!m_cachedConeGain)
m_cachedConeGain = calculateDistanceConeGain(position(), orientation(), listener().position(), m_distanceEffect, m_coneEffect);
return *m_cachedConeGain;
}

double PannerNode::tailTime() const
@@ -550,6 +574,21 @@ double PannerNode::latencyTime() const
return m_panner ? m_panner->latencyTime() : 0;
}

void PannerNode::invalidateCachedPropertiesIfNecessary()
{
auto lastPosition = std::exchange(m_lastPosition, position());
bool hasPositionChanged = m_lastPosition != lastPosition;
auto lastOrientation = std::exchange(m_lastOrientation, position());
bool hasOrientationChanged = m_lastOrientation != lastOrientation;
auto& listener = this->listener();

if (hasPositionChanged || listener.isPositionDirty() || listener.isOrientationDirty() || listener.isUpVectorDirty())
m_cachedAzimuthElevation = std::nullopt;

if (hasPositionChanged || hasOrientationChanged || listener.isPositionDirty())
m_cachedConeGain = std::nullopt;
}

} // namespace WebCore

#endif // ENABLE(WEB_AUDIO)
@@ -113,15 +113,21 @@ class PannerNode final : public AudioNode {
private:
PannerNode(BaseAudioContext&, const PannerOptions&);

void calculateAzimuthElevation(double* outAzimuth, double* outElevation, const FloatPoint3D& position, const FloatPoint3D& listenerPosition, const FloatPoint3D& listenerForward, const FloatPoint3D& listenerUp) WTF_REQUIRES_LOCK(m_processLock);
float calculateDistanceConeGain(const FloatPoint3D& position, const FloatPoint3D& orientation, const FloatPoint3D& listenerPosition) WTF_REQUIRES_LOCK(m_processLock);
struct AzimuthElevation {
double azimuth { 0. };
double elevation { 0. };
};
static AzimuthElevation calculateAzimuthElevation(const FloatPoint3D& position, const FloatPoint3D& listenerPosition, const FloatPoint3D& listenerForward, const FloatPoint3D& listenerUp);
static float calculateDistanceConeGain(const FloatPoint3D& position, const FloatPoint3D& orientation, const FloatPoint3D& listenerPosition, const DistanceEffect&, const ConeEffect&);

// Returns the combined distance and cone gain attenuation.
float distanceConeGain() WTF_REQUIRES_LOCK(m_processLock);

bool requiresTailProcessing() const final;

void azimuthElevation(double* outAzimuth, double* outElevation) WTF_REQUIRES_LOCK(m_processLock);
void invalidateCachedPropertiesIfNecessary() WTF_REQUIRES_LOCK(m_processLock);

const AzimuthElevation& azimuthElevation() WTF_REQUIRES_LOCK(m_processLock);
void processSampleAccurateValues(AudioBus* destination, const AudioBus* source, size_t framesToProcess) WTF_REQUIRES_LOCK(m_processLock);
bool hasSampleAccurateValues() const WTF_REQUIRES_LOCK(m_processLock);
bool shouldUseARate() const WTF_REQUIRES_LOCK(m_processLock);
@@ -145,6 +151,11 @@ class PannerNode final : public AudioNode {
Ref<AudioParam> m_orientationY WTF_GUARDED_BY_LOCK(m_processLock);
Ref<AudioParam> m_orientationZ WTF_GUARDED_BY_LOCK(m_processLock);

mutable std::optional<AzimuthElevation> m_cachedAzimuthElevation WTF_GUARDED_BY_LOCK(m_processLock);
mutable std::optional<float> m_cachedConeGain WTF_GUARDED_BY_LOCK(m_processLock);
FloatPoint3D m_lastPosition WTF_GUARDED_BY_LOCK(m_processLock);
FloatPoint3D m_lastOrientation WTF_GUARDED_BY_LOCK(m_processLock);

// Synchronize process() with setting of the panning model, source's location
// information, listener, distance parameters and sound cones.
mutable Lock m_processLock;
@@ -37,7 +37,7 @@ namespace WebCore {

ConeEffect::ConeEffect() = default;

double ConeEffect::gain(FloatPoint3D sourcePosition, FloatPoint3D sourceOrientation, FloatPoint3D listenerPosition)
double ConeEffect::gain(FloatPoint3D sourcePosition, FloatPoint3D sourceOrientation, FloatPoint3D listenerPosition) const
{
if (sourceOrientation.isZero() || ((m_innerAngle == 360.0) && (m_outerAngle == 360.0)))
return 1.0; // no cone specified - unity gain
@@ -41,7 +41,7 @@ class ConeEffect final {
ConeEffect();

// Returns scalar gain for the given source/listener positions/orientations
double gain(FloatPoint3D sourcePosition, FloatPoint3D sourceOrientation, FloatPoint3D listenerPosition);
double gain(FloatPoint3D sourcePosition, FloatPoint3D sourceOrientation, FloatPoint3D listenerPosition) const;

// Angles in degrees
void setInnerAngle(double innerAngle) { m_innerAngle = innerAngle; }

0 comments on commit 7252835

Please sign in to comment.