|
@@ -0,0 +1,224 @@
|
|
|
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
|
+From: Raymond Toy <[email protected]>
|
|
|
+Date: Tue, 2 Mar 2021 15:15:29 +0000
|
|
|
+Subject: Convert AudioParam NaN values to the default value
|
|
|
+
|
|
|
+If any output value of an AudioParam (including the intrinsic values
|
|
|
+and any inputs to the AudioParam), should be NaN, replace the NaN
|
|
|
+value with the associated defaultValue.
|
|
|
+
|
|
|
+This causes some slowdowns so SIMD/NEON code was added to mitigate the
|
|
|
+degradation. There is still some slowdown, but the worst case is now
|
|
|
+about 7% slower on x86 and 10% on arm. Generally, the slowdown is less
|
|
|
+than 2% and 5%, respectively. (Perversely, some results got faster,
|
|
|
+and the differences are statistically significant.)
|
|
|
+
|
|
|
+Full details can be found at
|
|
|
+https://docs.google.com/spreadsheets/d/1EhbLHm-9cUoEO5aj1vYemVBLQ3Dh4dCJPPLTfZPrZt4/edit?usp=sharing
|
|
|
+
|
|
|
+Manually tested the test case from the bug and the issue no longer
|
|
|
+occurs.
|
|
|
+
|
|
|
+(cherry picked from commit ab1862017b5717271a28376659944dddc602195c)
|
|
|
+
|
|
|
+(cherry picked from commit eb0c0353bf245885797d8ce0d1b864d88a381fbb)
|
|
|
+
|
|
|
+Bug: 1170531
|
|
|
+Change-Id: I00d902b40a9ef9da990c6d68b664b1dcfc31b091
|
|
|
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2658724
|
|
|
+Commit-Queue: Raymond Toy <[email protected]>
|
|
|
+Reviewed-by: Hongchan Choi <[email protected]>
|
|
|
+Cr-Original-Original-Commit-Position: refs/heads/master@{#851733}
|
|
|
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2686369
|
|
|
+Reviewed-by: Raymond Toy <[email protected]>
|
|
|
+Cr-Original-Commit-Position: refs/branch-heads/4389@{#880}
|
|
|
+Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830}
|
|
|
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2727697
|
|
|
+Reviewed-by: Victor-Gabriel Savu <[email protected]>
|
|
|
+Commit-Queue: Artem Sumaneev <[email protected]>
|
|
|
+Cr-Commit-Position: refs/branch-heads/4240@{#1551}
|
|
|
+Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218}
|
|
|
+
|
|
|
+diff --git a/third_party/blink/renderer/modules/webaudio/audio_param.cc b/third_party/blink/renderer/modules/webaudio/audio_param.cc
|
|
|
+index c5d329479a412d52ee39167ff841b1cea417a217..135588f56ebceabd3e0a12f9f506955bb58b20ca 100644
|
|
|
+--- a/third_party/blink/renderer/modules/webaudio/audio_param.cc
|
|
|
++++ b/third_party/blink/renderer/modules/webaudio/audio_param.cc
|
|
|
+@@ -25,6 +25,7 @@
|
|
|
+
|
|
|
+ #include "third_party/blink/renderer/modules/webaudio/audio_param.h"
|
|
|
+
|
|
|
++#include "build/build_config.h"
|
|
|
+ #include "third_party/blink/renderer/core/inspector/console_message.h"
|
|
|
+ #include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h"
|
|
|
+ #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
|
|
|
+@@ -235,6 +236,49 @@ void AudioParamHandler::CalculateSampleAccurateValues(
|
|
|
+ CalculateFinalValues(values, number_of_values, IsAudioRate());
|
|
|
+ }
|
|
|
+
|
|
|
++// Replace NaN values in |values| with |default_value|.
|
|
|
++static void HandleNaNValues(float* values,
|
|
|
++ unsigned number_of_values,
|
|
|
++ float default_value) {
|
|
|
++ unsigned k = 0;
|
|
|
++#if defined(ARCH_CPU_X86_FAMILY)
|
|
|
++ if (number_of_values >= 4) {
|
|
|
++ __m128 defaults = _mm_set1_ps(default_value);
|
|
|
++ for (k = 0; k < number_of_values; k += 4) {
|
|
|
++ __m128 v = _mm_loadu_ps(values + k);
|
|
|
++ // cmpuord returns all 1's if v is NaN for each elmeent of v.
|
|
|
++ __m128 isnan = _mm_cmpunord_ps(v, v);
|
|
|
++ // Replace NaN parts with default.
|
|
|
++ __m128 result = _mm_and_ps(isnan, defaults);
|
|
|
++ // Merge in the parts that aren't NaN
|
|
|
++ result = _mm_or_ps(_mm_andnot_ps(isnan, v), result);
|
|
|
++ _mm_storeu_ps(values + k, result);
|
|
|
++ }
|
|
|
++ }
|
|
|
++#elif defined(CPU_ARM_NEON)
|
|
|
++ if (number_of_values >= 4) {
|
|
|
++ uint32x4_t defaults = static_cast<uint32x4_t>(vdupq_n_f32(default_value));
|
|
|
++ for (k = 0; k < number_of_values; k += 4) {
|
|
|
++ float32x4_t v = vld1q_f32(values + k);
|
|
|
++ // Returns true (all ones) if v is not NaN
|
|
|
++ uint32x4_t is_not_nan = vceqq_f32(v, v);
|
|
|
++ // Get the parts that are not NaN
|
|
|
++ uint32x4_t result = vandq_u32(is_not_nan, v);
|
|
|
++ // Replace the parts that are NaN with the default and merge with previous
|
|
|
++ // result. (Note: vbic_u32(x, y) = x and not y)
|
|
|
++ result = vorrq_u32(result, vbicq_u32(defaults, is_not_nan));
|
|
|
++ vst1q_f32(values + k, static_cast<float32x4_t>(result));
|
|
|
++ }
|
|
|
++ }
|
|
|
++#endif
|
|
|
++
|
|
|
++ for (; k < number_of_values; ++k) {
|
|
|
++ if (std::isnan(values[k])) {
|
|
|
++ values[k] = default_value;
|
|
|
++ }
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
+ void AudioParamHandler::CalculateFinalValues(float* values,
|
|
|
+ unsigned number_of_values,
|
|
|
+ bool sample_accurate) {
|
|
|
+@@ -297,10 +341,21 @@ void AudioParamHandler::CalculateFinalValues(float* values,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+- // Clamp the values now to the nominal range
|
|
|
+ float min_value = MinValue();
|
|
|
+ float max_value = MaxValue();
|
|
|
+
|
|
|
++ if (NumberOfRenderingConnections() > 0) {
|
|
|
++ // AudioParams by themselves don't produce NaN because of the finite min
|
|
|
++ // and max values. But an input to an AudioParam could have NaNs.
|
|
|
++ //
|
|
|
++ // NaN values in AudioParams must be replaced by the AudioParam's
|
|
|
++ // defaultValue. Then these values must be clamped to lie in the nominal
|
|
|
++ // range between the AudioParam's minValue and maxValue.
|
|
|
++ //
|
|
|
++ // See https://webaudio.github.io/web-audio-api/#computation-of-value.
|
|
|
++ HandleNaNValues(values, number_of_values, DefaultValue());
|
|
|
++ }
|
|
|
++
|
|
|
+ vector_math::Vclip(values, 1, &min_value, &max_value, values, 1,
|
|
|
+ number_of_values);
|
|
|
+ }
|
|
|
+diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/nan-param.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/nan-param.html
|
|
|
+new file mode 100644
|
|
|
+index 0000000000000000000000000000000000000000..e9b8f0accbd1b0359275615f3ef12bd7e9317c4f
|
|
|
+--- /dev/null
|
|
|
++++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/nan-param.html
|
|
|
+@@ -0,0 +1,92 @@
|
|
|
++<!doctype html>
|
|
|
++<html>
|
|
|
++ <head>
|
|
|
++ <title>Test Flushing of NaN to Zero in AudioParams</title>
|
|
|
++ <script src="/resources/testharness.js"></script>
|
|
|
++ <script src="/resources/testharnessreport.js"></script>
|
|
|
++ <script src="/webaudio/resources/audit-util.js"></script>
|
|
|
++ <script src="/webaudio/resources/audit.js"></script>
|
|
|
++ </head>
|
|
|
++
|
|
|
++ <body>
|
|
|
++ <script>
|
|
|
++ let audit = Audit.createTaskRunner();
|
|
|
++
|
|
|
++ // See
|
|
|
++ // https://webaudio.github.io/web-audio-api/#computation-of-value.
|
|
|
++ //
|
|
|
++ // The computed value must replace NaN values in the output with
|
|
|
++ // the default value of the param.
|
|
|
++ audit.define('AudioParam NaN', async (task, should) => {
|
|
|
++ // For testing, we only need a small number of frames; and
|
|
|
++ // a low sample rate is perfectly fine. Use two channels.
|
|
|
++ // The first channel is for the AudioParam output. The
|
|
|
++ // second channel is for the AudioParam input.
|
|
|
++ let context = new OfflineAudioContext(
|
|
|
++ {numberOfChannels: 2, length: 256, sampleRate: 8192});
|
|
|
++ let merger = new ChannelMergerNode(
|
|
|
++ context, {numberOfInputs: context.destination.channelCount});
|
|
|
++ merger.connect(context.destination);
|
|
|
++
|
|
|
++ // A constant source with a huge value.
|
|
|
++ let mod = new ConstantSourceNode(context, {offset: 1e30});
|
|
|
++
|
|
|
++ // Gain nodes with a huge positive gain and huge negative
|
|
|
++ // gain. Combined with the huge offset in |mod|, the
|
|
|
++ // output of the gain nodes are +Infinity and -Infinity.
|
|
|
++ let gainPos = new GainNode(context, {gain: 1e30});
|
|
|
++ let gainNeg = new GainNode(context, {gain: -1e30});
|
|
|
++
|
|
|
++ mod.connect(gainPos);
|
|
|
++ mod.connect(gainNeg);
|
|
|
++
|
|
|
++ // Connect these to the second merger channel. This is a
|
|
|
++ // sanity check that the AudioParam input really is NaN.
|
|
|
++ gainPos.connect(merger, 0, 1);
|
|
|
++ gainNeg.connect(merger, 0, 1);
|
|
|
++
|
|
|
++ // Source whose AudioParam is connected to the graph
|
|
|
++ // that produces NaN values. Use a non-default value offset
|
|
|
++ // just in case something is wrong we get default for some
|
|
|
++ // other reason.
|
|
|
++ let src = new ConstantSourceNode(context, {offset: 100});
|
|
|
++
|
|
|
++ gainPos.connect(src.offset);
|
|
|
++ gainNeg.connect(src.offset);
|
|
|
++
|
|
|
++ // AudioParam output goes to channel 1 of the destination.
|
|
|
++ src.connect(merger, 0, 0);
|
|
|
++
|
|
|
++ // Let's go!
|
|
|
++ mod.start();
|
|
|
++ src.start();
|
|
|
++
|
|
|
++ let buffer = await context.startRendering();
|
|
|
++
|
|
|
++ let input = buffer.getChannelData(1);
|
|
|
++ let output = buffer.getChannelData(0);
|
|
|
++
|
|
|
++ // Have to test manually for NaN values in the input because
|
|
|
++ // NaN fails all comparisons.
|
|
|
++ let isNaN = true;
|
|
|
++ for (let k = 0; k < input.length; ++k) {
|
|
|
++ if (!Number.isNaN(input[k])) {
|
|
|
++ isNaN = false;
|
|
|
++ break;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ should(isNaN, 'AudioParam input contains only NaN').beTrue();
|
|
|
++
|
|
|
++ // Output of the AudioParam should have all NaN values
|
|
|
++ // replaced by the default.
|
|
|
++ should(output, 'AudioParam output')
|
|
|
++ .beConstantValueOf(src.offset.defaultValue);
|
|
|
++
|
|
|
++ task.done();
|
|
|
++ });
|
|
|
++
|
|
|
++ audit.run();
|
|
|
++ </script>
|
|
|
++ </body>
|
|
|
++</html>
|