kopia lustrzana https://github.com/jameshball/osci-render
Add partially working blocking recording - very buggy
rodzic
4045cb4c27
commit
e40356e71a
|
@ -1,6 +1,7 @@
|
|||
#include <JuceHeader.h>
|
||||
#include "obj/Camera.h"
|
||||
#include "mathter/Common/Approx.hpp"
|
||||
#include "concurrency/BufferConsumer.h"
|
||||
|
||||
class FrustumTest : public juce::UnitTest {
|
||||
public:
|
||||
|
@ -114,7 +115,77 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class ProducerThread : public juce::Thread {
|
||||
public:
|
||||
ProducerThread(BufferConsumer& consumer) : juce::Thread("Producer Thread"), consumer(consumer) {}
|
||||
|
||||
void run() override {
|
||||
for (int i = 0; i < 1024 * 100; i++) {
|
||||
consumer.write(OsciPoint(counter++));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BufferConsumer& consumer;
|
||||
int counter = 0;
|
||||
};
|
||||
|
||||
class ConsumerThread : public juce::Thread {
|
||||
public:
|
||||
ConsumerThread(BufferConsumer& consumer) : juce::Thread("Consumer Thread"), consumer(consumer) {}
|
||||
|
||||
void run() override {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
consumer.waitUntilFull();
|
||||
auto buffer = consumer.getBuffer();
|
||||
for (auto& point : buffer) {
|
||||
values.push_back(point.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool containsGap() {
|
||||
for (int i = 0; i < values.size() - 1; i++) {
|
||||
if (values[i] + 1 != values[i + 1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
BufferConsumer& consumer;
|
||||
std::vector<double> values;
|
||||
};
|
||||
|
||||
class BufferConsumerTest : public juce::UnitTest {
|
||||
public:
|
||||
BufferConsumerTest() : juce::UnitTest("Buffer Consumer") {}
|
||||
|
||||
void runTest() override {
|
||||
beginTest("All data received");
|
||||
|
||||
BufferConsumer consumer(1024);
|
||||
ProducerThread producer(consumer);
|
||||
ConsumerThread consumerThread(consumer);
|
||||
|
||||
consumer.setBlockOnWrite(true);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
producer.startThread();
|
||||
consumerThread.startThread();
|
||||
|
||||
producer.waitForThreadToExit(-1);
|
||||
consumerThread.waitForThreadToExit(-1);
|
||||
|
||||
expect(!consumerThread.containsGap(), "There was a gap in the data");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static FrustumTest frustumTest;
|
||||
static BufferConsumerTest bufferConsumerTest;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
juce::UnitTestRunner runner;
|
||||
|
|
|
@ -28,7 +28,7 @@ void AudioBackgroundThread::prepare(double sampleRate, int samplesPerBlock) {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioBackgroundThread::setShouldBeRunning(bool shouldBeRunning) {
|
||||
void AudioBackgroundThread::setShouldBeRunning(bool shouldBeRunning, std::function<void()> stopCallback) {
|
||||
if (!isPrepared && shouldBeRunning) {
|
||||
prepare(manager.sampleRate, manager.samplesPerBlock);
|
||||
}
|
||||
|
@ -36,6 +36,9 @@ void AudioBackgroundThread::setShouldBeRunning(bool shouldBeRunning) {
|
|||
this->shouldBeRunning = shouldBeRunning;
|
||||
|
||||
if (!shouldBeRunning && isThreadRunning()) {
|
||||
if (stopCallback) {
|
||||
stopCallback();
|
||||
}
|
||||
stop();
|
||||
} else if (isPrepared && shouldBeRunning && !isThreadRunning()) {
|
||||
start();
|
||||
|
@ -57,6 +60,10 @@ void AudioBackgroundThread::run() {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioBackgroundThread::setBlockOnAudioThread(bool block) {
|
||||
consumer->setBlockOnWrite(block);
|
||||
}
|
||||
|
||||
void AudioBackgroundThread::start() {
|
||||
startThread();
|
||||
}
|
||||
|
|
|
@ -11,8 +11,9 @@ public:
|
|||
~AudioBackgroundThread() override;
|
||||
|
||||
void prepare(double sampleRate, int samplesPerBlock);
|
||||
void setShouldBeRunning(bool shouldBeRunning);
|
||||
void setShouldBeRunning(bool shouldBeRunning, std::function<void()> stopCallback = nullptr);
|
||||
void write(const OsciPoint& point);
|
||||
void setBlockOnAudioThread(bool block);
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../shape/OsciPoint.h"
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include "readerwritercircularbuffer.h"
|
||||
|
||||
// FROM https://gist.github.com/Kuxe/6bdd5b748b5f11b303a5cccbb8c8a731
|
||||
/** General semaphore with N permissions **/
|
||||
|
@ -45,45 +46,82 @@ public:
|
|||
class BufferConsumer {
|
||||
public:
|
||||
BufferConsumer(std::size_t size) {
|
||||
returnBuffer.resize(size);
|
||||
buffer1.resize(size);
|
||||
buffer2.resize(size);
|
||||
queue = std::make_unique<moodycamel::BlockingReaderWriterCircularBuffer<OsciPoint>>(2 * size);
|
||||
}
|
||||
|
||||
~BufferConsumer() {}
|
||||
|
||||
// CONSUMER
|
||||
// loop dequeuing until full
|
||||
// get buffer
|
||||
|
||||
// PRODUCER
|
||||
// enqueue point
|
||||
|
||||
void waitUntilFull() {
|
||||
sema.acquire();
|
||||
if (blockOnWrite) {
|
||||
for (int i = 0; i < returnBuffer.size() && blockOnWrite; i++) {
|
||||
queue->wait_dequeue(returnBuffer[i]);
|
||||
}
|
||||
} else {
|
||||
sema.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
// to be used when the audio thread is being destroyed to
|
||||
// make sure that everything waiting on it stops waiting.
|
||||
void forceNotify() {
|
||||
sema.release();
|
||||
queue->try_enqueue(OsciPoint());
|
||||
}
|
||||
|
||||
void write(OsciPoint point) {
|
||||
if (offset >= buffer->size()) {
|
||||
{
|
||||
juce::SpinLock::ScopedLockType scope(bufferLock);
|
||||
buffer = buffer == &buffer1 ? &buffer2 : &buffer1;
|
||||
if (blockOnWrite) {
|
||||
queue->wait_enqueue(point);
|
||||
} else {
|
||||
if (offset >= buffer->size()) {
|
||||
{
|
||||
juce::SpinLock::ScopedLockType scope(bufferLock);
|
||||
buffer = buffer == &buffer1 ? &buffer2 : &buffer1;
|
||||
}
|
||||
offset = 0;
|
||||
sema.release();
|
||||
}
|
||||
offset = 0;
|
||||
sema.release();
|
||||
|
||||
(*buffer)[offset++] = point;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<OsciPoint>& getBuffer() {
|
||||
if (blockOnWrite) {
|
||||
return returnBuffer;
|
||||
} else {
|
||||
// whatever buffer is not currently being written to
|
||||
juce::SpinLock::ScopedLockType scope(bufferLock);
|
||||
return buffer == &buffer1 ? buffer2 : buffer1;
|
||||
}
|
||||
|
||||
(*buffer)[offset++] = point;
|
||||
}
|
||||
|
||||
// whatever buffer is not currently being written to
|
||||
std::vector<OsciPoint>& getBuffer() {
|
||||
juce::SpinLock::ScopedLockType scope(bufferLock);
|
||||
return buffer == &buffer1 ? buffer2 : buffer1;
|
||||
void setBlockOnWrite(bool block) {
|
||||
blockOnWrite = block;
|
||||
if (blockOnWrite) {
|
||||
sema.release();
|
||||
} else {
|
||||
OsciPoint item;
|
||||
queue->try_dequeue(item);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<moodycamel::BlockingReaderWriterCircularBuffer<OsciPoint>> queue;
|
||||
std::vector<OsciPoint> returnBuffer;
|
||||
std::vector<OsciPoint> buffer1;
|
||||
std::vector<OsciPoint> buffer2;
|
||||
juce::SpinLock bufferLock;
|
||||
std::atomic<bool> blockOnWrite = false;
|
||||
std::vector<OsciPoint>* buffer = &buffer1;
|
||||
Semaphore sema{0};
|
||||
int offset = 0;
|
||||
|
|
|
@ -0,0 +1,772 @@
|
|||
// ©2013-2016 Cameron Desrochers.
|
||||
// Distributed under the simplified BSD license (see the license file that
|
||||
// should have come with this header).
|
||||
// Uses Jeff Preshing's semaphore implementation (under the terms of its
|
||||
// separate zlib license, embedded below).
|
||||
|
||||
#pragma once
|
||||
|
||||
// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation
|
||||
// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment).
|
||||
// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees).
|
||||
// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols.
|
||||
|
||||
#include <cerrno>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
|
||||
// Platform detection
|
||||
#if defined(__INTEL_COMPILER)
|
||||
#define AE_ICC
|
||||
#elif defined(_MSC_VER)
|
||||
#define AE_VCPP
|
||||
#elif defined(__GNUC__)
|
||||
#define AE_GCC
|
||||
#endif
|
||||
|
||||
#if defined(_M_IA64) || defined(__ia64__)
|
||||
#define AE_ARCH_IA64
|
||||
#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__)
|
||||
#define AE_ARCH_X64
|
||||
#elif defined(_M_IX86) || defined(__i386__)
|
||||
#define AE_ARCH_X86
|
||||
#elif defined(_M_PPC) || defined(__powerpc__)
|
||||
#define AE_ARCH_PPC
|
||||
#else
|
||||
#define AE_ARCH_UNKNOWN
|
||||
#endif
|
||||
|
||||
|
||||
// AE_UNUSED
|
||||
#define AE_UNUSED(x) ((void)x)
|
||||
|
||||
// AE_NO_TSAN/AE_TSAN_ANNOTATE_*
|
||||
// For GCC
|
||||
#if defined(__SANITIZE_THREAD__)
|
||||
#define AE_TSAN_IS_ENABLED
|
||||
#endif
|
||||
// For clang
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(thread_sanitizer) && !defined(AE_TSAN_IS_ENABLED)
|
||||
#define AE_TSAN_IS_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef AE_TSAN_IS_ENABLED
|
||||
#if __cplusplus >= 201703L // inline variables require C++17
|
||||
namespace moodycamel { inline int ae_tsan_global; }
|
||||
#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))
|
||||
#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))
|
||||
extern "C" void AnnotateHappensBefore(const char*, int, void*);
|
||||
extern "C" void AnnotateHappensAfter(const char*, int, void*);
|
||||
#else // when we can't work with tsan, attempt to disable its warnings
|
||||
#define AE_NO_TSAN __attribute__((no_sanitize("thread")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef AE_NO_TSAN
|
||||
#define AE_NO_TSAN
|
||||
#endif
|
||||
|
||||
#ifndef AE_TSAN_ANNOTATE_RELEASE
|
||||
#define AE_TSAN_ANNOTATE_RELEASE()
|
||||
#define AE_TSAN_ANNOTATE_ACQUIRE()
|
||||
#endif
|
||||
|
||||
|
||||
// AE_FORCEINLINE
|
||||
#if defined(AE_VCPP) || defined(AE_ICC)
|
||||
#define AE_FORCEINLINE __forceinline
|
||||
#elif defined(AE_GCC)
|
||||
//#define AE_FORCEINLINE __attribute__((always_inline))
|
||||
#define AE_FORCEINLINE inline
|
||||
#else
|
||||
#define AE_FORCEINLINE inline
|
||||
#endif
|
||||
|
||||
|
||||
// AE_ALIGN
|
||||
#if defined(AE_VCPP) || defined(AE_ICC)
|
||||
#define AE_ALIGN(x) __declspec(align(x))
|
||||
#elif defined(AE_GCC)
|
||||
#define AE_ALIGN(x) __attribute__((aligned(x)))
|
||||
#else
|
||||
// Assume GCC compliant syntax...
|
||||
#define AE_ALIGN(x) __attribute__((aligned(x)))
|
||||
#endif
|
||||
|
||||
|
||||
// Portable atomic fences implemented below:
|
||||
|
||||
namespace moodycamel {
|
||||
|
||||
enum memory_order {
|
||||
memory_order_relaxed,
|
||||
memory_order_acquire,
|
||||
memory_order_release,
|
||||
memory_order_acq_rel,
|
||||
memory_order_seq_cst,
|
||||
|
||||
// memory_order_sync: Forces a full sync:
|
||||
// #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad
|
||||
memory_order_sync = memory_order_seq_cst
|
||||
};
|
||||
|
||||
} // end namespace moodycamel
|
||||
|
||||
#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600)
|
||||
// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
|
||||
#define AeFullSync _mm_mfence
|
||||
#define AeLiteSync _mm_mfence
|
||||
#elif defined(AE_ARCH_IA64)
|
||||
#define AeFullSync __mf
|
||||
#define AeLiteSync __mf
|
||||
#elif defined(AE_ARCH_PPC)
|
||||
#include <ppcintrinsics.h>
|
||||
#define AeFullSync __sync
|
||||
#define AeLiteSync __lwsync
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef AE_VCPP
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4365) // Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert`
|
||||
#ifdef __cplusplus_cli
|
||||
#pragma managed(push, off)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace moodycamel {
|
||||
|
||||
AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN
|
||||
{
|
||||
switch (order) {
|
||||
case memory_order_relaxed: break;
|
||||
case memory_order_acquire: _ReadBarrier(); break;
|
||||
case memory_order_release: _WriteBarrier(); break;
|
||||
case memory_order_acq_rel: _ReadWriteBarrier(); break;
|
||||
case memory_order_seq_cst: _ReadWriteBarrier(); break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
// x86/x64 have a strong memory model -- all loads and stores have
|
||||
// acquire and release semantics automatically (so only need compiler
|
||||
// barriers for those).
|
||||
#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64)
|
||||
AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN
|
||||
{
|
||||
switch (order) {
|
||||
case memory_order_relaxed: break;
|
||||
case memory_order_acquire: _ReadBarrier(); break;
|
||||
case memory_order_release: _WriteBarrier(); break;
|
||||
case memory_order_acq_rel: _ReadWriteBarrier(); break;
|
||||
case memory_order_seq_cst:
|
||||
_ReadWriteBarrier();
|
||||
AeFullSync();
|
||||
_ReadWriteBarrier();
|
||||
break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
#else
|
||||
AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN
|
||||
{
|
||||
// Non-specialized arch, use heavier memory barriers everywhere just in case :-(
|
||||
switch (order) {
|
||||
case memory_order_relaxed:
|
||||
break;
|
||||
case memory_order_acquire:
|
||||
_ReadBarrier();
|
||||
AeLiteSync();
|
||||
_ReadBarrier();
|
||||
break;
|
||||
case memory_order_release:
|
||||
_WriteBarrier();
|
||||
AeLiteSync();
|
||||
_WriteBarrier();
|
||||
break;
|
||||
case memory_order_acq_rel:
|
||||
_ReadWriteBarrier();
|
||||
AeLiteSync();
|
||||
_ReadWriteBarrier();
|
||||
break;
|
||||
case memory_order_seq_cst:
|
||||
_ReadWriteBarrier();
|
||||
AeFullSync();
|
||||
_ReadWriteBarrier();
|
||||
break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} // end namespace moodycamel
|
||||
#else
|
||||
// Use standard library of atomics
|
||||
#include <atomic>
|
||||
|
||||
namespace moodycamel {
|
||||
|
||||
AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN
|
||||
{
|
||||
switch (order) {
|
||||
case memory_order_relaxed: break;
|
||||
case memory_order_acquire: std::atomic_signal_fence(std::memory_order_acquire); break;
|
||||
case memory_order_release: std::atomic_signal_fence(std::memory_order_release); break;
|
||||
case memory_order_acq_rel: std::atomic_signal_fence(std::memory_order_acq_rel); break;
|
||||
case memory_order_seq_cst: std::atomic_signal_fence(std::memory_order_seq_cst); break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN
|
||||
{
|
||||
switch (order) {
|
||||
case memory_order_relaxed: break;
|
||||
case memory_order_acquire: AE_TSAN_ANNOTATE_ACQUIRE(); std::atomic_thread_fence(std::memory_order_acquire); break;
|
||||
case memory_order_release: AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_release); break;
|
||||
case memory_order_acq_rel: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_acq_rel); break;
|
||||
case memory_order_seq_cst: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_seq_cst); break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace moodycamel
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli))
|
||||
#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
|
||||
#endif
|
||||
|
||||
#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
|
||||
#include <atomic>
|
||||
#endif
|
||||
#include <utility>
|
||||
|
||||
// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY:
|
||||
// Provides basic support for atomic variables -- no memory ordering guarantees are provided.
|
||||
// The guarantee of atomicity is only made for types that already have atomic load and store guarantees
|
||||
// at the hardware level -- on most platforms this generally means aligned pointers and integers (only).
|
||||
namespace moodycamel {
|
||||
template<typename T>
|
||||
class weak_atomic
|
||||
{
|
||||
public:
|
||||
AE_NO_TSAN weak_atomic() : value() { }
|
||||
#ifdef AE_VCPP
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning
|
||||
#endif
|
||||
template<typename U> AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) { }
|
||||
#ifdef __cplusplus_cli
|
||||
// Work around bug with universal reference/nullptr combination that only appears when /clr is on
|
||||
AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) { }
|
||||
#endif
|
||||
AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) { }
|
||||
AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) { }
|
||||
#ifdef AE_VCPP
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); }
|
||||
|
||||
|
||||
#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
|
||||
template<typename U> AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward<U>(x); return *this; }
|
||||
AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; }
|
||||
|
||||
AE_FORCEINLINE T load() const AE_NO_TSAN { return value; }
|
||||
|
||||
AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN
|
||||
{
|
||||
#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
|
||||
if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
|
||||
#if defined(_M_AMD64)
|
||||
else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
|
||||
#endif
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
assert(false && "T must be either a 32 or 64 bit type");
|
||||
return value;
|
||||
}
|
||||
|
||||
AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN
|
||||
{
|
||||
#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
|
||||
if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
|
||||
#if defined(_M_AMD64)
|
||||
else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
|
||||
#endif
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
assert(false && "T must be either a 32 or 64 bit type");
|
||||
return value;
|
||||
}
|
||||
#else
|
||||
template<typename U>
|
||||
AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN
|
||||
{
|
||||
value.store(std::forward<U>(x), std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN
|
||||
{
|
||||
value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); }
|
||||
|
||||
AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN
|
||||
{
|
||||
return value.fetch_add(increment, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN
|
||||
{
|
||||
return value.fetch_add(increment, std::memory_order_release);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
private:
|
||||
#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
|
||||
// No std::atomic support, but still need to circumvent compiler optimizations.
|
||||
// `volatile` will make memory access slow, but is guaranteed to be reliable.
|
||||
volatile T value;
|
||||
#else
|
||||
std::atomic<T> value;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // end namespace moodycamel
|
||||
|
||||
|
||||
|
||||
// Portable single-producer, single-consumer semaphore below:
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Avoid including windows.h in a header; we only need a handful of
|
||||
// items, so we'll redeclare them here (this is relatively safe since
|
||||
// the API generally has to remain stable between Windows versions).
|
||||
// I know this is an ugly hack but it still beats polluting the global
|
||||
// namespace with thousands of generic names or adding a .cpp for nothing.
|
||||
extern "C" {
|
||||
struct _SECURITY_ATTRIBUTES;
|
||||
__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
|
||||
__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
|
||||
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
|
||||
__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
|
||||
}
|
||||
#elif defined(__MACH__)
|
||||
#include <mach/mach.h>
|
||||
#elif defined(__unix__)
|
||||
#include <semaphore.h>
|
||||
#elif defined(FREERTOS)
|
||||
#include <FreeRTOS.h>
|
||||
#include <semphr.h>
|
||||
#include <task.h>
|
||||
#endif
|
||||
|
||||
namespace moodycamel
|
||||
{
|
||||
// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's
|
||||
// portable + lightweight semaphore implementations, originally from
|
||||
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
|
||||
// LICENSE:
|
||||
// Copyright (c) 2015 Jeff Preshing
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
namespace spsc_sema
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
class Semaphore
|
||||
{
|
||||
private:
|
||||
void* m_hSema;
|
||||
|
||||
Semaphore(const Semaphore& other);
|
||||
Semaphore& operator=(const Semaphore& other);
|
||||
|
||||
public:
|
||||
AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema()
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
const long maxLong = 0x7fffffff;
|
||||
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
|
||||
assert(m_hSema);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
CloseHandle(m_hSema);
|
||||
}
|
||||
|
||||
bool wait() AE_NO_TSAN
|
||||
{
|
||||
const unsigned long infinite = 0xffffffff;
|
||||
return WaitForSingleObject(m_hSema, infinite) == 0;
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
return WaitForSingleObject(m_hSema, 0) == 0;
|
||||
}
|
||||
|
||||
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
|
||||
{
|
||||
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;
|
||||
}
|
||||
|
||||
void signal(int count = 1) AE_NO_TSAN
|
||||
{
|
||||
while (!ReleaseSemaphore(m_hSema, count, nullptr));
|
||||
}
|
||||
};
|
||||
#elif defined(__MACH__)
|
||||
//---------------------------------------------------------
|
||||
// Semaphore (Apple iOS and OSX)
|
||||
// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
|
||||
//---------------------------------------------------------
|
||||
class Semaphore
|
||||
{
|
||||
private:
|
||||
semaphore_t m_sema;
|
||||
|
||||
Semaphore(const Semaphore& other);
|
||||
Semaphore& operator=(const Semaphore& other);
|
||||
|
||||
public:
|
||||
AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
|
||||
assert(rc == KERN_SUCCESS);
|
||||
AE_UNUSED(rc);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
semaphore_destroy(mach_task_self(), m_sema);
|
||||
}
|
||||
|
||||
bool wait() AE_NO_TSAN
|
||||
{
|
||||
return semaphore_wait(m_sema) == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
return timed_wait(0);
|
||||
}
|
||||
|
||||
bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN
|
||||
{
|
||||
mach_timespec_t ts;
|
||||
ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
|
||||
ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
|
||||
|
||||
// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
|
||||
kern_return_t rc = semaphore_timedwait(m_sema, ts);
|
||||
return rc == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
void signal() AE_NO_TSAN
|
||||
{
|
||||
while (semaphore_signal(m_sema) != KERN_SUCCESS);
|
||||
}
|
||||
|
||||
void signal(int count) AE_NO_TSAN
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
while (semaphore_signal(m_sema) != KERN_SUCCESS);
|
||||
}
|
||||
}
|
||||
};
|
||||
#elif defined(__unix__)
|
||||
//---------------------------------------------------------
|
||||
// Semaphore (POSIX, Linux)
|
||||
//---------------------------------------------------------
|
||||
class Semaphore
|
||||
{
|
||||
private:
|
||||
sem_t m_sema;
|
||||
|
||||
Semaphore(const Semaphore& other);
|
||||
Semaphore& operator=(const Semaphore& other);
|
||||
|
||||
public:
|
||||
AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
|
||||
assert(rc == 0);
|
||||
AE_UNUSED(rc);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
sem_destroy(&m_sema);
|
||||
}
|
||||
|
||||
bool wait() AE_NO_TSAN
|
||||
{
|
||||
// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
|
||||
int rc;
|
||||
do
|
||||
{
|
||||
rc = sem_wait(&m_sema);
|
||||
}
|
||||
while (rc == -1 && errno == EINTR);
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
int rc;
|
||||
do {
|
||||
rc = sem_trywait(&m_sema);
|
||||
} while (rc == -1 && errno == EINTR);
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
|
||||
{
|
||||
struct timespec ts;
|
||||
const int usecs_in_1_sec = 1000000;
|
||||
const int nsecs_in_1_sec = 1000000000;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec);
|
||||
ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000;
|
||||
// sem_timedwait bombs if you have more than 1e9 in tv_nsec
|
||||
// so we have to clean things up before passing it in
|
||||
if (ts.tv_nsec >= nsecs_in_1_sec) {
|
||||
ts.tv_nsec -= nsecs_in_1_sec;
|
||||
++ts.tv_sec;
|
||||
}
|
||||
|
||||
int rc;
|
||||
do {
|
||||
rc = sem_timedwait(&m_sema, &ts);
|
||||
} while (rc == -1 && errno == EINTR);
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
void signal() AE_NO_TSAN
|
||||
{
|
||||
while (sem_post(&m_sema) == -1);
|
||||
}
|
||||
|
||||
void signal(int count) AE_NO_TSAN
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
while (sem_post(&m_sema) == -1);
|
||||
}
|
||||
}
|
||||
};
|
||||
#elif defined(FREERTOS)
|
||||
//---------------------------------------------------------
|
||||
// Semaphore (FreeRTOS)
|
||||
//---------------------------------------------------------
|
||||
class Semaphore
|
||||
{
|
||||
private:
|
||||
SemaphoreHandle_t m_sema;
|
||||
|
||||
Semaphore(const Semaphore& other);
|
||||
Semaphore& operator=(const Semaphore& other);
|
||||
|
||||
public:
|
||||
AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull), static_cast<UBaseType_t>(initialCount));
|
||||
assert(m_sema);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
vSemaphoreDelete(m_sema);
|
||||
}
|
||||
|
||||
bool wait() AE_NO_TSAN
|
||||
{
|
||||
return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE;
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
// Note: In an ISR context, if this causes a task to unblock,
|
||||
// the caller won't know about it
|
||||
if (xPortIsInsideInterrupt())
|
||||
return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE;
|
||||
return xSemaphoreTake(m_sema, 0) == pdTRUE;
|
||||
}
|
||||
|
||||
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
|
||||
{
|
||||
std::uint64_t msecs = usecs / 1000;
|
||||
TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS);
|
||||
if (ticks == 0)
|
||||
return try_wait();
|
||||
return xSemaphoreTake(m_sema, ticks) == pdTRUE;
|
||||
}
|
||||
|
||||
void signal() AE_NO_TSAN
|
||||
{
|
||||
// Note: In an ISR context, if this causes a task to unblock,
|
||||
// the caller won't know about it
|
||||
BaseType_t rc;
|
||||
if (xPortIsInsideInterrupt())
|
||||
rc = xSemaphoreGiveFromISR(m_sema, NULL);
|
||||
else
|
||||
rc = xSemaphoreGive(m_sema);
|
||||
assert(rc == pdTRUE);
|
||||
AE_UNUSED(rc);
|
||||
}
|
||||
|
||||
void signal(int count) AE_NO_TSAN
|
||||
{
|
||||
while (count-- > 0)
|
||||
signal();
|
||||
}
|
||||
};
|
||||
#else
|
||||
#error Unsupported platform! (No semaphore wrapper available)
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------
|
||||
// LightweightSemaphore
|
||||
//---------------------------------------------------------
|
||||
class LightweightSemaphore
|
||||
{
|
||||
public:
|
||||
typedef std::make_signed<std::size_t>::type ssize_t;
|
||||
|
||||
private:
|
||||
weak_atomic<ssize_t> m_count;
|
||||
Semaphore m_sema;
|
||||
|
||||
bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN
|
||||
{
|
||||
ssize_t oldCount;
|
||||
// Is there a better way to set the initial spin count?
|
||||
// If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
|
||||
// as threads start hitting the kernel semaphore.
|
||||
int spin = 1024;
|
||||
while (--spin >= 0)
|
||||
{
|
||||
if (m_count.load() > 0)
|
||||
{
|
||||
m_count.fetch_add_acquire(-1);
|
||||
return true;
|
||||
}
|
||||
compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop.
|
||||
}
|
||||
oldCount = m_count.fetch_add_acquire(-1);
|
||||
if (oldCount > 0)
|
||||
return true;
|
||||
if (timeout_usecs < 0)
|
||||
{
|
||||
if (m_sema.wait())
|
||||
return true;
|
||||
}
|
||||
if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs)))
|
||||
return true;
|
||||
// At this point, we've timed out waiting for the semaphore, but the
|
||||
// count is still decremented indicating we may still be waiting on
|
||||
// it. So we have to re-adjust the count, but only if the semaphore
|
||||
// wasn't signaled enough times for us too since then. If it was, we
|
||||
// need to release the semaphore too.
|
||||
while (true)
|
||||
{
|
||||
oldCount = m_count.fetch_add_release(1);
|
||||
if (oldCount < 0)
|
||||
return false; // successfully restored things to the way they were
|
||||
// Oh, the producer thread just signaled the semaphore after all. Try again:
|
||||
oldCount = m_count.fetch_add_acquire(-1);
|
||||
if (oldCount > 0 && m_sema.try_wait())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema()
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
}
|
||||
|
||||
bool tryWait() AE_NO_TSAN
|
||||
{
|
||||
if (m_count.load() > 0)
|
||||
{
|
||||
m_count.fetch_add_acquire(-1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wait() AE_NO_TSAN
|
||||
{
|
||||
return tryWait() || waitWithPartialSpinning();
|
||||
}
|
||||
|
||||
bool wait(std::int64_t timeout_usecs) AE_NO_TSAN
|
||||
{
|
||||
return tryWait() || waitWithPartialSpinning(timeout_usecs);
|
||||
}
|
||||
|
||||
void signal(ssize_t count = 1) AE_NO_TSAN
|
||||
{
|
||||
assert(count >= 0);
|
||||
ssize_t oldCount = m_count.fetch_add_release(count);
|
||||
assert(oldCount >= -1);
|
||||
if (oldCount < 0)
|
||||
{
|
||||
m_sema.signal(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t availableApprox() const AE_NO_TSAN
|
||||
{
|
||||
ssize_t count = m_count.load();
|
||||
return count > 0 ? static_cast<std::size_t>(count) : 0;
|
||||
}
|
||||
};
|
||||
} // end namespace spsc_sema
|
||||
} // end namespace moodycamel
|
||||
|
||||
#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))
|
||||
#pragma warning(pop)
|
||||
#ifdef __cplusplus_cli
|
||||
#pragma managed(pop)
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,323 @@
|
|||
// ©2020 Cameron Desrochers.
|
||||
// Distributed under the simplified BSD license (see the license file that
|
||||
// should have come with this header).
|
||||
|
||||
// Provides a C++11 implementation of a single-producer, single-consumer wait-free concurrent
|
||||
// circular buffer (fixed-size queue).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
// Note that this implementation is fully modern C++11 (not compatible with old MSVC versions)
|
||||
// but we still include atomicops.h for its LightweightSemaphore implementation.
|
||||
#ifdef MOODYCAMEL_ATOMICOPS
|
||||
#include "atomicops.h"
|
||||
#endif
|
||||
|
||||
#ifndef MOODYCAMEL_CACHE_LINE_SIZE
|
||||
#define MOODYCAMEL_CACHE_LINE_SIZE 64
|
||||
#endif
|
||||
|
||||
namespace moodycamel {
|
||||
|
||||
template<typename T>
|
||||
class BlockingReaderWriterCircularBuffer
|
||||
{
|
||||
public:
|
||||
typedef T value_type;
|
||||
|
||||
public:
|
||||
explicit BlockingReaderWriterCircularBuffer(std::size_t capacity)
|
||||
: maxcap(capacity), mask(), rawData(), data(),
|
||||
slots_(new spsc_sema::LightweightSemaphore(static_cast<spsc_sema::LightweightSemaphore::ssize_t>(capacity))),
|
||||
items(new spsc_sema::LightweightSemaphore(0)),
|
||||
nextSlot(0), nextItem(0)
|
||||
{
|
||||
// Round capacity up to power of two to compute modulo mask.
|
||||
// Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
--capacity;
|
||||
capacity |= capacity >> 1;
|
||||
capacity |= capacity >> 2;
|
||||
capacity |= capacity >> 4;
|
||||
for (std::size_t i = 1; i < sizeof(std::size_t); i <<= 1)
|
||||
capacity |= capacity >> (i << 3);
|
||||
mask = capacity++;
|
||||
rawData = static_cast<char*>(std::malloc(capacity * sizeof(T) + std::alignment_of<T>::value - 1));
|
||||
data = align_for<T>(rawData);
|
||||
}
|
||||
|
||||
BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer&& other)
|
||||
: maxcap(0), mask(0), rawData(nullptr), data(nullptr),
|
||||
slots_(new spsc_sema::LightweightSemaphore(0)),
|
||||
items(new spsc_sema::LightweightSemaphore(0)),
|
||||
nextSlot(), nextItem()
|
||||
{
|
||||
swap(other);
|
||||
}
|
||||
|
||||
BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer const&) = delete;
|
||||
|
||||
// Note: The queue should not be accessed concurrently while it's
|
||||
// being deleted. It's up to the user to synchronize this.
|
||||
~BlockingReaderWriterCircularBuffer()
|
||||
{
|
||||
for (std::size_t i = 0, n = items->availableApprox(); i != n; ++i)
|
||||
reinterpret_cast<T*>(data)[(nextItem + i) & mask].~T();
|
||||
std::free(rawData);
|
||||
}
|
||||
|
||||
BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer&& other) noexcept
|
||||
{
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer const&) = delete;
|
||||
|
||||
// Swaps the contents of this buffer with the contents of another.
|
||||
// Not thread-safe.
|
||||
void swap(BlockingReaderWriterCircularBuffer& other) noexcept
|
||||
{
|
||||
std::swap(maxcap, other.maxcap);
|
||||
std::swap(mask, other.mask);
|
||||
std::swap(rawData, other.rawData);
|
||||
std::swap(data, other.data);
|
||||
std::swap(slots_, other.slots_);
|
||||
std::swap(items, other.items);
|
||||
std::swap(nextSlot, other.nextSlot);
|
||||
std::swap(nextItem, other.nextItem);
|
||||
}
|
||||
|
||||
// Enqueues a single item (by copying it).
|
||||
// Fails if not enough room to enqueue.
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
bool try_enqueue(T const& item)
|
||||
{
|
||||
if (!slots_->tryWait())
|
||||
return false;
|
||||
inner_enqueue(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enqueues a single item (by moving it, if possible).
|
||||
// Fails if not enough room to enqueue.
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
bool try_enqueue(T&& item)
|
||||
{
|
||||
if (!slots_->tryWait())
|
||||
return false;
|
||||
inner_enqueue(std::move(item));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's enough space to enqueue the given item,
|
||||
// then enqueues it (via copy).
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
void wait_enqueue(T const& item)
|
||||
{
|
||||
while (!slots_->wait());
|
||||
inner_enqueue(item);
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's enough space to enqueue the given item,
|
||||
// then enqueues it (via move, if possible).
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
void wait_enqueue(T&& item)
|
||||
{
|
||||
while (!slots_->wait());
|
||||
inner_enqueue(std::move(item));
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's enough space to enqueue the given item,
|
||||
// or the timeout expires. Returns false without enqueueing the item if the timeout
|
||||
// expires, otherwise enqueues the item (via copy) and returns true.
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
bool wait_enqueue_timed(T const& item, std::int64_t timeout_usecs)
|
||||
{
|
||||
if (!slots_->wait(timeout_usecs))
|
||||
return false;
|
||||
inner_enqueue(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's enough space to enqueue the given item,
|
||||
// or the timeout expires. Returns false without enqueueing the item if the timeout
|
||||
// expires, otherwise enqueues the item (via move, if possible) and returns true.
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
bool wait_enqueue_timed(T&& item, std::int64_t timeout_usecs)
|
||||
{
|
||||
if (!slots_->wait(timeout_usecs))
|
||||
return false;
|
||||
inner_enqueue(std::move(item));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's enough space to enqueue the given item,
|
||||
// or the timeout expires. Returns false without enqueueing the item if the timeout
|
||||
// expires, otherwise enqueues the item (via copy) and returns true.
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
template<typename Rep, typename Period>
|
||||
inline bool wait_enqueue_timed(T const& item, std::chrono::duration<Rep, Period> const& timeout)
|
||||
{
|
||||
return wait_enqueue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's enough space to enqueue the given item,
|
||||
// or the timeout expires. Returns false without enqueueing the item if the timeout
|
||||
// expires, otherwise enqueues the item (via move, if possible) and returns true.
|
||||
// Thread-safe when called by producer thread.
|
||||
// No exception guarantee (state will be corrupted) if constructor of T throws.
|
||||
template<typename Rep, typename Period>
|
||||
inline bool wait_enqueue_timed(T&& item, std::chrono::duration<Rep, Period> const& timeout)
|
||||
{
|
||||
return wait_enqueue_timed(std::move(item), std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||
}
|
||||
|
||||
// Attempts to dequeue a single item.
|
||||
// Returns false if the buffer is empty.
|
||||
// Thread-safe when called by consumer thread.
|
||||
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
|
||||
template<typename U>
|
||||
bool try_dequeue(U& item)
|
||||
{
|
||||
if (!items->tryWait())
|
||||
return false;
|
||||
inner_dequeue(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocks the current thread until there's something to dequeue, then dequeues it.
|
||||
// Thread-safe when called by consumer thread.
|
||||
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
|
||||
template<typename U>
|
||||
void wait_dequeue(U& item)
|
||||
{
|
||||
while (!items->wait());
|
||||
inner_dequeue(item);
|
||||
}
|
||||
|
||||
// Blocks the current thread until either there's something to dequeue
|
||||
// or the timeout expires. Returns false without setting `item` if the
|
||||
// timeout expires, otherwise assigns to `item` and returns true.
|
||||
// Thread-safe when called by consumer thread.
|
||||
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
|
||||
template<typename U>
|
||||
bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
|
||||
{
|
||||
if (!items->wait(timeout_usecs))
|
||||
return false;
|
||||
inner_dequeue(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocks the current thread until either there's something to dequeue
|
||||
// or the timeout expires. Returns false without setting `item` if the
|
||||
// timeout expires, otherwise assigns to `item` and returns true.
|
||||
// Thread-safe when called by consumer thread.
|
||||
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
|
||||
template<typename U, typename Rep, typename Period>
|
||||
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
|
||||
{
|
||||
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||
}
|
||||
|
||||
// Returns a pointer to the next element in the queue (the one that would
|
||||
// be removed next by a call to `try_dequeue` or `try_pop`). If the queue
|
||||
// appears empty at the time the method is called, returns nullptr instead.
|
||||
// Thread-safe when called by consumer thread.
|
||||
inline T* peek()
|
||||
{
|
||||
if (!items->availableApprox())
|
||||
return nullptr;
|
||||
return inner_peek();
|
||||
}
|
||||
|
||||
// Pops the next element from the queue, if there is one.
|
||||
// Thread-safe when called by consumer thread.
|
||||
inline bool try_pop()
|
||||
{
|
||||
if (!items->tryWait())
|
||||
return false;
|
||||
inner_pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a (possibly outdated) snapshot of the total number of elements currently in the buffer.
|
||||
// Thread-safe.
|
||||
inline std::size_t size_approx() const
|
||||
{
|
||||
return items->availableApprox();
|
||||
}
|
||||
|
||||
// Returns the maximum number of elements that this circular buffer can hold at once.
|
||||
// Thread-safe.
|
||||
inline std::size_t max_capacity() const
|
||||
{
|
||||
return maxcap;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename U>
|
||||
void inner_enqueue(U&& item)
|
||||
{
|
||||
std::size_t i = nextSlot++;
|
||||
new (reinterpret_cast<T*>(data) + (i & mask)) T(std::forward<U>(item));
|
||||
items->signal();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
void inner_dequeue(U& item)
|
||||
{
|
||||
std::size_t i = nextItem++;
|
||||
T& element = reinterpret_cast<T*>(data)[i & mask];
|
||||
item = std::move(element);
|
||||
element.~T();
|
||||
slots_->signal();
|
||||
}
|
||||
|
||||
T* inner_peek()
|
||||
{
|
||||
return reinterpret_cast<T*>(data) + (nextItem & mask);
|
||||
}
|
||||
|
||||
void inner_pop()
|
||||
{
|
||||
std::size_t i = nextItem++;
|
||||
reinterpret_cast<T*>(data)[i & mask].~T();
|
||||
slots_->signal();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
static inline char* align_for(char* ptr)
|
||||
{
|
||||
const std::size_t alignment = std::alignment_of<U>::value;
|
||||
return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t maxcap; // actual (non-power-of-two) capacity
|
||||
std::size_t mask; // circular buffer capacity mask (for cheap modulo)
|
||||
char* rawData; // raw circular buffer memory
|
||||
char* data; // circular buffer memory aligned to element alignment
|
||||
std::unique_ptr<spsc_sema::LightweightSemaphore> slots_; // number of slots currently free (named with underscore to accommodate Qt's 'slots' macro)
|
||||
std::unique_ptr<spsc_sema::LightweightSemaphore> items; // number of elements currently enqueued
|
||||
char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(char*) * 2 - sizeof(std::size_t) * 2 - sizeof(std::unique_ptr<spsc_sema::LightweightSemaphore>) * 2];
|
||||
std::size_t nextSlot; // index of next free slot to enqueue into
|
||||
char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(std::size_t)];
|
||||
std::size_t nextItem; // index of next element to dequeue from
|
||||
};
|
||||
|
||||
}
|
|
@ -14,17 +14,16 @@
|
|||
VisualiserComponent::VisualiserComponent(AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent, bool visualiserOnly) : settings(settings), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent", threadManager), parent(parent) {
|
||||
|
||||
addAndMakeVisible(record);
|
||||
//record.setPulseAnimation(true);
|
||||
record.setPulseAnimation(true);
|
||||
record.onClick = [this] {
|
||||
toggleRecording();
|
||||
//stopwatch.stop();
|
||||
//stopwatch.reset();
|
||||
/*
|
||||
stopwatch.stop();
|
||||
stopwatch.reset();
|
||||
|
||||
if (record.getToggleState()) {
|
||||
stopwatch.start();
|
||||
}
|
||||
*/
|
||||
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
||||
|
||||
resized();
|
||||
};
|
||||
|
||||
|
@ -63,6 +62,9 @@ VisualiserComponent::VisualiserComponent(AudioBackgroundThreadManager& threadMan
|
|||
|
||||
VisualiserComponent::~VisualiserComponent() {
|
||||
openGLContext.detach();
|
||||
setShouldBeRunning(false, [this] {
|
||||
renderingSemaphore.release();
|
||||
});
|
||||
}
|
||||
|
||||
void VisualiserComponent::setFullScreenCallback(std::function<void(FullScreenMode)> callback) {
|
||||
|
@ -80,24 +82,40 @@ void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent& event) {
|
|||
enableFullScreen();
|
||||
}
|
||||
|
||||
void VisualiserComponent::setBuffer(const std::vector<OsciPoint>& buffer) {
|
||||
juce::CriticalSection::ScopedLockType lock(samplesLock);
|
||||
|
||||
xSamples.clear();
|
||||
ySamples.clear();
|
||||
zSamples.clear();
|
||||
for (auto& point : buffer) {
|
||||
OsciPoint smoothPoint = settings.parameters.smoothEffect->apply(0, point);
|
||||
xSamples.push_back(smoothPoint.x);
|
||||
ySamples.push_back(smoothPoint.y);
|
||||
zSamples.push_back(smoothPoint.z);
|
||||
void VisualiserComponent::runTask(const std::vector<OsciPoint>& points) {
|
||||
{
|
||||
juce::CriticalSection::ScopedLockType lock(samplesLock);
|
||||
|
||||
xSamples.clear();
|
||||
ySamples.clear();
|
||||
zSamples.clear();
|
||||
for (auto& point : points) {
|
||||
OsciPoint smoothPoint = settings.parameters.smoothEffect->apply(0, point);
|
||||
xSamples.push_back(smoothPoint.x);
|
||||
ySamples.push_back(smoothPoint.y);
|
||||
zSamples.push_back(smoothPoint.z);
|
||||
}
|
||||
|
||||
sampleBufferCount++;
|
||||
|
||||
if (settings.parameters.upsamplingEnabled->getBoolValue()) {
|
||||
int newResampledSize = xSamples.size() * RESAMPLE_RATIO;
|
||||
|
||||
smoothedXSamples.resize(newResampledSize);
|
||||
smoothedYSamples.resize(newResampledSize);
|
||||
smoothedZSamples.resize(newResampledSize);
|
||||
smoothedZSamples.resize(newResampledSize);
|
||||
|
||||
xResampler.process(xSamples.data(), smoothedXSamples.data(), xSamples.size());
|
||||
yResampler.process(ySamples.data(), smoothedYSamples.data(), ySamples.size());
|
||||
zResampler.process(zSamples.data(), smoothedZSamples.data(), zSamples.size());
|
||||
}
|
||||
}
|
||||
|
||||
// this just triggers a repaint
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void VisualiserComponent::runTask(const std::vector<OsciPoint>& points) {
|
||||
setBuffer(points);
|
||||
// wait for rendering to complete
|
||||
renderingSemaphore.acquire();
|
||||
}
|
||||
|
||||
int VisualiserComponent::prepareTask(double sampleRate, int bufferSize) {
|
||||
|
@ -113,6 +131,7 @@ int VisualiserComponent::prepareTask(double sampleRate, int bufferSize) {
|
|||
|
||||
void VisualiserComponent::setPaused(bool paused) {
|
||||
active = !paused;
|
||||
renderingSemaphore.release();
|
||||
setShouldBeRunning(active);
|
||||
repaint();
|
||||
}
|
||||
|
@ -137,11 +156,7 @@ bool VisualiserComponent::keyPressed(const juce::KeyPress& key) {
|
|||
void VisualiserComponent::setFullScreen(bool fullScreen) {}
|
||||
|
||||
void VisualiserComponent::toggleRecording() {
|
||||
chooser = std::make_unique<juce::FileChooser>("Choose a .wav file to render...", juce::File(), "*.wav;*.flac");
|
||||
auto chooserFlags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles;
|
||||
chooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc) {
|
||||
audioFile = fc.getResult();
|
||||
});
|
||||
setBlockOnAudioThread(record.getToggleState());
|
||||
}
|
||||
|
||||
void VisualiserComponent::haltRecording() {
|
||||
|
@ -262,46 +277,30 @@ void VisualiserComponent::openGLContextClosing() {
|
|||
}
|
||||
|
||||
void VisualiserComponent::handleAsyncUpdate() {
|
||||
if (settings.parameters.upsamplingEnabled->getBoolValue()) {
|
||||
juce::CriticalSection::ScopedLockType lock(samplesLock);
|
||||
|
||||
int newResampledSize = xSamples.size() * RESAMPLE_RATIO;
|
||||
|
||||
smoothedXSamples.resize(newResampledSize);
|
||||
smoothedYSamples.resize(newResampledSize);
|
||||
smoothedZSamples.resize(newResampledSize);
|
||||
smoothedZSamples.resize(newResampledSize);
|
||||
|
||||
xResampler.process(xSamples.data(), smoothedXSamples.data(), xSamples.size());
|
||||
yResampler.process(ySamples.data(), smoothedYSamples.data(), ySamples.size());
|
||||
zResampler.process(zSamples.data(), smoothedZSamples.data(), zSamples.size());
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void VisualiserComponent::renderOpenGL() {
|
||||
if (openGLContext.isActive()) {
|
||||
if (sampleRate != oldSampleRate) {
|
||||
oldSampleRate = sampleRate;
|
||||
setupArrays(RESAMPLE_RATIO * sampleRate / FRAME_RATE);
|
||||
}
|
||||
time += 0.01f;
|
||||
intensity = settings.getIntensity() * (41000.0f / sampleRate);
|
||||
if (active) {
|
||||
juce::OpenGLHelpers::clear(juce::Colours::black);
|
||||
|
||||
// we have a new buffer to render
|
||||
if (sampleBufferCount != prevSampleBufferCount) {
|
||||
prevSampleBufferCount = sampleBufferCount;
|
||||
juce::CriticalSection::ScopedLockType lock(samplesLock);
|
||||
|
||||
if (audioFile != juce::File{}) {
|
||||
renderAudioFile(audioFile, FILE_RENDER_QOI);
|
||||
audioFile = juce::File{};
|
||||
}
|
||||
|
||||
if (settings.parameters.upsamplingEnabled->getBoolValue()) {
|
||||
renderScope(smoothedXSamples, smoothedYSamples, smoothedZSamples);
|
||||
} else {
|
||||
renderScope(xSamples, ySamples, zSamples);
|
||||
}
|
||||
renderingSemaphore.release();
|
||||
}
|
||||
|
||||
// render texture to screen
|
||||
activateTargetTexture(std::nullopt);
|
||||
setShader(texturedShader.get());
|
||||
drawTexture(renderTexture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,7 +379,6 @@ void VisualiserComponent::setupTextures() {
|
|||
screenTexture = createScreenTexture();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind
|
||||
|
||||
}
|
||||
|
||||
Texture VisualiserComponent::makeTexture(int width, int height) {
|
||||
|
@ -402,13 +400,13 @@ Texture VisualiserComponent::makeTexture(int width, int height) {
|
|||
return { textureID, width, height };
|
||||
}
|
||||
|
||||
void VisualiserComponent::drawLineTexture(const std::vector<float>& xP, const std::vector<float>& yP, const std::vector<float>& zP) {
|
||||
void VisualiserComponent::drawLineTexture(const std::vector<float>& xPoints, const std::vector<float>& yPoints, const std::vector<float>& zPoints) {
|
||||
using namespace juce::gl;
|
||||
|
||||
fadeAmount = juce::jmin(1.0, std::pow(0.5, settings.getPersistence()) * 0.4);
|
||||
activateTargetTexture(lineTexture);
|
||||
fade();
|
||||
drawLine(xP, yP, zP);
|
||||
drawLine(xPoints, yPoints, zPoints);
|
||||
glBindTexture(GL_TEXTURE_2D, targetTexture.value().id);
|
||||
}
|
||||
|
||||
|
@ -550,21 +548,21 @@ void VisualiserComponent::setNormalBlending() {
|
|||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
void VisualiserComponent::drawLine(const std::vector<float>& xP, const std::vector<float>& yP, const std::vector<float>& zP) {
|
||||
void VisualiserComponent::drawLine(const std::vector<float>& xPoints, const std::vector<float>& yPoints, const std::vector<float>& zPoints) {
|
||||
using namespace juce::gl;
|
||||
|
||||
setAdditiveBlending();
|
||||
|
||||
int nPoints = xP.size();
|
||||
int nPoints = xPoints.size();
|
||||
|
||||
// Without this, there's an access violation that seems to occur only on some systems
|
||||
if (scratchVertices.size() != nPoints * 12) scratchVertices.resize(nPoints * 12);
|
||||
|
||||
for (int i = 0; i < nPoints; ++i) {
|
||||
int p = i * 12;
|
||||
scratchVertices[p] = scratchVertices[p + 3] = scratchVertices[p + 6] = scratchVertices[p + 9] = xP[i];
|
||||
scratchVertices[p + 1] = scratchVertices[p + 4] = scratchVertices[p + 7] = scratchVertices[p + 10] = yP[i];
|
||||
scratchVertices[p + 2] = scratchVertices[p + 5] = scratchVertices[p + 8] = scratchVertices[p + 11] = zP[i];
|
||||
scratchVertices[p] = scratchVertices[p + 3] = scratchVertices[p + 6] = scratchVertices[p + 9] = xPoints[i];
|
||||
scratchVertices[p + 1] = scratchVertices[p + 4] = scratchVertices[p + 7] = scratchVertices[p + 10] = yPoints[i];
|
||||
scratchVertices[p + 2] = scratchVertices[p + 5] = scratchVertices[p + 8] = scratchVertices[p + 11] = zPoints[i];
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
|
||||
|
@ -599,7 +597,7 @@ void VisualiserComponent::drawLine(const std::vector<float>& xP, const std::vect
|
|||
lineShader->setUniform("uNEdges", (GLfloat) nEdges);
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
|
||||
int nEdgesThisTime = xP.size() - 1;
|
||||
int nEdgesThisTime = xPoints.size() - 1;
|
||||
glDrawElements(GL_TRIANGLES, nEdgesThisTime * 6, GL_UNSIGNED_INT, 0);
|
||||
|
||||
glDisableVertexAttribArray(glGetAttribLocation(lineShader->getProgramID(), "aStart"));
|
||||
|
@ -626,6 +624,7 @@ void VisualiserComponent::fade() {
|
|||
|
||||
void VisualiserComponent::drawCRT() {
|
||||
using namespace juce::gl;
|
||||
|
||||
setNormalBlending();
|
||||
|
||||
activateTargetTexture(blur1Texture);
|
||||
|
@ -665,19 +664,15 @@ void VisualiserComponent::drawCRT() {
|
|||
setShader(outputShader.get());
|
||||
float brightness = std::pow(2, settings.getBrightness() - 2);
|
||||
outputShader->setUniform("uExposure", brightness);
|
||||
outputShader->setUniform("uSaturation", (float)settings.getSaturation());
|
||||
outputShader->setUniform("uNoise", (float)settings.getNoise());
|
||||
outputShader->setUniform("uSaturation", (float) settings.getSaturation());
|
||||
outputShader->setUniform("uNoise", (float) settings.getNoise());
|
||||
outputShader->setUniform("uTime", time);
|
||||
outputShader->setUniform("uGlow", (float)settings.getGlow());
|
||||
outputShader->setUniform("uGlow", (float) settings.getGlow());
|
||||
outputShader->setUniform("uResizeForCanvas", lineTexture.width / 1024.0f);
|
||||
juce::Colour colour = juce::Colour::fromHSV(settings.getHue() / 360.0f, 1.0, 1.0, 1.0);
|
||||
outputShader->setUniform("uColour", colour.getFloatRed(), colour.getFloatGreen(), colour.getFloatBlue());
|
||||
activateTargetTexture(renderTexture);
|
||||
drawTexture(lineTexture, blur1Texture, blur3Texture, screenTexture);
|
||||
|
||||
activateTargetTexture(std::nullopt);
|
||||
setShader(texturedShader.get());
|
||||
drawTexture(renderTexture);
|
||||
}
|
||||
|
||||
Texture VisualiserComponent::createScreenTexture() {
|
||||
|
@ -781,16 +776,24 @@ void VisualiserComponent::paint(juce::Graphics& g) {
|
|||
}
|
||||
}
|
||||
|
||||
void VisualiserComponent::renderScope(const std::vector<float>& xp, const std::vector<float>& yp, const std::vector<float>& zp) {
|
||||
void VisualiserComponent::renderScope(const std::vector<float>& xPoints, const std::vector<float>& yPoints, const std::vector<float>& zPoints) {
|
||||
time += 0.01f;
|
||||
|
||||
if (graticuleEnabled != settings.getGraticuleEnabled() || smudgesEnabled != settings.getSmudgesEnabled()) {
|
||||
graticuleEnabled = settings.getGraticuleEnabled();
|
||||
smudgesEnabled = settings.getSmudgesEnabled();
|
||||
screenTexture = createScreenTexture();
|
||||
}
|
||||
|
||||
if (sampleRate != oldSampleRate) {
|
||||
oldSampleRate = sampleRate;
|
||||
setupArrays(RESAMPLE_RATIO * sampleRate / FRAME_RATE);
|
||||
}
|
||||
intensity = settings.getIntensity() * (41000.0f / sampleRate);
|
||||
|
||||
renderScale = (float)openGLContext.getRenderingScale();
|
||||
|
||||
drawLineTexture(xp, yp, zp);
|
||||
drawLineTexture(xPoints, yPoints, zPoints);
|
||||
checkGLErrors("drawLineTexture");
|
||||
drawCRT();
|
||||
checkGLErrors("drawCRT");
|
||||
|
@ -880,11 +883,10 @@ int VisualiserComponent::renderAudioFile(juce::File& sourceAudio, int method, in
|
|||
saveTextureToQOI(renderTexture, destDir.getChildFile(fileName + ".qoi"));
|
||||
break;
|
||||
};
|
||||
time += 0.01f;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
delete reader;
|
||||
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ public:
|
|||
void enableFullScreen();
|
||||
void setFullScreenCallback(std::function<void(FullScreenMode)> callback);
|
||||
void mouseDoubleClick(const juce::MouseEvent& event) override;
|
||||
void setBuffer(const std::vector<OsciPoint>& buffer);
|
||||
void resized() override;
|
||||
void paint(juce::Graphics& g) override;
|
||||
int prepareTask(double sampleRate, int samplesPerBlock) override;
|
||||
|
@ -84,6 +83,8 @@ private:
|
|||
StopwatchComponent stopwatch;
|
||||
SvgButton record{"Record", BinaryData::record_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)};
|
||||
|
||||
Semaphore renderingSemaphore{0};
|
||||
|
||||
void popoutWindow();
|
||||
|
||||
// OPENGL
|
||||
|
@ -110,6 +111,8 @@ private:
|
|||
std::vector<float> smoothedXSamples;
|
||||
std::vector<float> smoothedYSamples;
|
||||
std::vector<float> smoothedZSamples;
|
||||
int sampleBufferCount = 0;
|
||||
int prevSampleBufferCount = 0;
|
||||
|
||||
std::vector<float> scratchVertices;
|
||||
std::vector<float> fullScreenQuad;
|
||||
|
|
|
@ -1,9 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<JUCERPROJECT id="Dt5Xh7" name="osci-render-test" projectType="consoleapp"
|
||||
useAppConfig="0" addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
|
||||
useAppConfig="0" addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1"
|
||||
defines="MOODYCAMEL_ATOMICOPS=1">
|
||||
<MAINGROUP id="ztPpnM" name="osci-render-test">
|
||||
<GROUP id="{4373FA2A-1E20-AAA6-40E0-740C13D88B75}" name="Source">
|
||||
<GROUP id="{6D2F76BF-0825-85C1-CE15-B08BF1AA1218}" name="concurrency">
|
||||
<FILE id="QdcNi7" name="atomicops.h" compile="0" resource="0" file="Source/concurrency/atomicops.h"/>
|
||||
<FILE id="iTTsXj" name="AudioBackgroundThread.cpp" compile="1" resource="0"
|
||||
file="Source/concurrency/AudioBackgroundThread.cpp"/>
|
||||
<FILE id="kyWcaF" name="AudioBackgroundThread.h" compile="0" resource="0"
|
||||
file="Source/concurrency/AudioBackgroundThread.h"/>
|
||||
<FILE id="kfeXov" name="AudioBackgroundThreadManager.cpp" compile="1"
|
||||
resource="0" file="Source/concurrency/AudioBackgroundThreadManager.cpp"/>
|
||||
<FILE id="TZuY5S" name="AudioBackgroundThreadManager.h" compile="0"
|
||||
resource="0" file="Source/concurrency/AudioBackgroundThreadManager.h"/>
|
||||
<FILE id="nqi7hn" name="BufferConsumer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/BufferConsumer.h"/>
|
||||
<FILE id="fTCFX5" name="readerwritercircularbuffer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/readerwritercircularbuffer.h"/>
|
||||
</GROUP>
|
||||
<GROUP id="{DB7C86A4-CC9B-5846-B0C3-6EB553450542}" name="mathter">
|
||||
<GROUP id="{3743CC14-52E9-72AB-1A61-DA053869B50F}" name="Common">
|
||||
<FILE id="aQA6tH" name="Approx.hpp" compile="0" resource="0" file="Source/mathter/Common/Approx.hpp"/>
|
||||
|
@ -102,6 +118,14 @@
|
|||
<FILE id="V3Q6n2" name="Frustum.cpp" compile="1" resource="0" file="Source/obj/Frustum.cpp"/>
|
||||
<FILE id="m9wauB" name="Frustum.h" compile="0" resource="0" file="Source/obj/Frustum.h"/>
|
||||
</GROUP>
|
||||
<GROUP id="{4C47E086-E440-AB0D-BB6E-B419AEBA3583}" name="shape">
|
||||
<FILE id="rGoWAa" name="Line.cpp" compile="1" resource="0" file="Source/shape/Line.cpp"/>
|
||||
<FILE id="Q4nxsV" name="Line.h" compile="0" resource="0" file="Source/shape/Line.h"/>
|
||||
<FILE id="HZzR9I" name="OsciPoint.cpp" compile="1" resource="0" file="Source/shape/OsciPoint.cpp"/>
|
||||
<FILE id="XRWdZW" name="OsciPoint.h" compile="0" resource="0" file="Source/shape/OsciPoint.h"/>
|
||||
<FILE id="Fb0uH1" name="Shape.cpp" compile="1" resource="0" file="Source/shape/Shape.cpp"/>
|
||||
<FILE id="PGbdTP" name="Shape.h" compile="0" resource="0" file="Source/shape/Shape.h"/>
|
||||
</GROUP>
|
||||
<FILE id="bQ1rDR" name="TestMain.cpp" compile="1" resource="0" file="Source/TestMain.cpp"/>
|
||||
</GROUP>
|
||||
</MAINGROUP>
|
||||
|
|
|
@ -184,6 +184,8 @@
|
|||
<FILE id="F5kUMH" name="BlockingQueue.h" compile="0" resource="0" file="Source/concurrency/BlockingQueue.h"/>
|
||||
<FILE id="WQ2W15" name="BufferConsumer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/BufferConsumer.h"/>
|
||||
<FILE id="L9aCHY" name="readerwritercircularbuffer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/readerwritercircularbuffer.h"/>
|
||||
</GROUP>
|
||||
<GROUP id="{A3E24187-62A5-AB8D-8837-14043B89A640}" name="gpla">
|
||||
<FILE id="KvDV8j" name="LineArtParser.cpp" compile="1" resource="0"
|
||||
|
|
Ładowanie…
Reference in New Issue