+/*-------------------------------------------------------------------------
+ * drawElements C++ Base Library
+ * -----------------------------
+ *
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Cross-thread barrier.
+ *//*--------------------------------------------------------------------*/
+
+#include "deSpinBarrier.hpp"
+#include "deThread.hpp"
+#include "deRandom.hpp"
+#include "deInt32.h"
+
+#include <vector>
+
+namespace de
+{
+
+SpinBarrier::SpinBarrier (deInt32 numThreads)
+ : m_numThreads (numThreads)
+ , m_numEntered (0)
+ , m_numLeaving (0)
+{
+ DE_ASSERT(numThreads > 0);
+}
+
+SpinBarrier::~SpinBarrier (void)
+{
+ DE_ASSERT(m_numEntered == 0 && m_numLeaving == 0);
+}
+
+void SpinBarrier::sync (WaitMode mode)
+{
+ DE_ASSERT(mode == WAIT_MODE_YIELD || mode == WAIT_MODE_BUSY);
+
+ deMemoryReadWriteFence();
+
+ if (m_numLeaving > 0)
+ {
+ for (;;)
+ {
+ if (m_numLeaving == 0)
+ break;
+
+ if (mode == WAIT_MODE_YIELD)
+ deYield();
+ }
+ }
+
+ if (deAtomicIncrement32(&m_numEntered) == m_numThreads)
+ {
+ m_numLeaving = m_numThreads;
+ deMemoryReadWriteFence();
+ m_numEntered = 0;
+ }
+ else
+ {
+ for (;;)
+ {
+ if (m_numEntered == 0)
+ break;
+
+ if (mode == WAIT_MODE_YIELD)
+ deYield();
+ }
+ }
+
+ deAtomicDecrement32(&m_numLeaving);
+ deMemoryReadWriteFence();
+}
+
+namespace
+{
+
+void singleThreadTest (SpinBarrier::WaitMode mode)
+{
+ SpinBarrier barrier(1);
+
+ barrier.sync(mode);
+ barrier.sync(mode);
+ barrier.sync(mode);
+}
+
+class TestThread : public de::Thread
+{
+public:
+ TestThread (SpinBarrier& barrier, volatile deInt32* sharedVar, int numThreads, int threadNdx, bool busyOk)
+ : m_barrier (barrier)
+ , m_sharedVar (sharedVar)
+ , m_numThreads (numThreads)
+ , m_threadNdx (threadNdx)
+ , m_busyOk (busyOk)
+ {
+ }
+
+ void run (void)
+ {
+ const int numIters = 10000;
+ de::Random rnd (deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx));
+
+ for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
+ {
+ // Phase 1: count up
+ deAtomicIncrement32(m_sharedVar);
+
+ // Verify
+ m_barrier.sync(getWaitMode(rnd));
+
+ DE_TEST_ASSERT(*m_sharedVar == m_numThreads);
+
+ m_barrier.sync(getWaitMode(rnd));
+
+ // Phase 2: count down
+ deAtomicDecrement32(m_sharedVar);
+
+ // Verify
+ m_barrier.sync(getWaitMode(rnd));
+
+ DE_TEST_ASSERT(*m_sharedVar == 0);
+
+ m_barrier.sync(getWaitMode(rnd));
+ }
+ }
+
+private:
+ SpinBarrier& m_barrier;
+ volatile deInt32* m_sharedVar;
+ int m_numThreads;
+ int m_threadNdx;
+ bool m_busyOk;
+
+ SpinBarrier::WaitMode getWaitMode (de::Random& rnd)
+ {
+ if (m_busyOk && rnd.getBool())
+ return SpinBarrier::WAIT_MODE_BUSY;
+ else
+ return SpinBarrier::WAIT_MODE_YIELD;
+ }
+};
+
+void multiThreadTest (int numThreads)
+{
+ SpinBarrier barrier (numThreads);
+ volatile deInt32 sharedVar = 0;
+ std::vector<TestThread*> threads (numThreads, static_cast<TestThread*>(DE_NULL));
+
+ // Going over logical cores with busy-waiting will cause priority inversion and make tests take
+ // excessive amount of time. Use busy waiting only when number of threads is at most one per
+ // core.
+ const bool busyOk = (deUint32)numThreads <= deGetNumAvailableLogicalCores();
+
+ for (int ndx = 0; ndx < numThreads; ndx++)
+ {
+ threads[ndx] = new TestThread(barrier, &sharedVar, numThreads, ndx, busyOk);
+ DE_TEST_ASSERT(threads[ndx]);
+ threads[ndx]->start();
+ }
+
+ for (int ndx = 0; ndx < numThreads; ndx++)
+ {
+ threads[ndx]->join();
+ delete threads[ndx];
+ }
+
+ DE_TEST_ASSERT(sharedVar == 0);
+}
+
+} // namespace
+
+void SpinBarrier_selfTest (void)
+{
+ singleThreadTest(SpinBarrier::WAIT_MODE_YIELD);
+ singleThreadTest(SpinBarrier::WAIT_MODE_BUSY);
+ multiThreadTest(1);
+ multiThreadTest(2);
+ multiThreadTest(4);
+ multiThreadTest(8);
+ multiThreadTest(16);
+}
+
+} // de